Compare commits
23 Commits
jm/modular
...
jm/buffer-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ff292742c | ||
|
|
e084c9f013 | ||
|
|
f08640c53b | ||
|
|
2b391ac15d | ||
|
|
99ef8651aa | ||
|
|
d72968d86b | ||
|
|
7bdccce3d5 | ||
|
|
3df685e114 | ||
|
|
4069aa8274 | ||
|
|
e5a7609588 | ||
|
|
69e0cd2fc4 | ||
|
|
ab6b1feaec | ||
|
|
3a43274881 | ||
|
|
dc8d0587ec | ||
|
|
23c0d52c29 | ||
|
|
c32baa7cd8 | ||
|
|
1153a9ebaf | ||
|
|
2805dddf05 | ||
|
|
baf047f556 | ||
|
|
6745a10508 | ||
|
|
7799f4ff5b | ||
|
|
edcdc8a814 | ||
|
|
5ad623c29b |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: bnjbvr/cargo-machete@v0.6.2
|
||||
- uses: bnjbvr/cargo-machete@v0.7.0
|
||||
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -10,8 +10,14 @@ GitHub with a [breaking change] label.
|
||||
|
||||
This is a quick summary of the sections below:
|
||||
|
||||
- [v0.29.0](#unreleased)
|
||||
- `Terminal`, `Buffer`, `Cell`, `Frame` are no longer `Sync` / `RefUnwindSafe`
|
||||
- Removed public fields from `Rect` iterators
|
||||
- `Line` now implements `From<Cow<str>`
|
||||
- `Table::highlight_style` is now `Table::row_highlight_style`
|
||||
- `Tabs::select` now accepts `Into<Option<usize>>`
|
||||
- [v0.28.0](#v0280)
|
||||
⁻ `Backend::size` returns `Size` instead of `Rect`
|
||||
- `Backend::size` returns `Size` instead of `Rect`
|
||||
- `Backend` trait migrates to `get/set_cursor_position`
|
||||
- Ratatui now requires Crossterm 0.28.0
|
||||
- `Axis::labels` now accepts `IntoIterator<Into<Line>>`
|
||||
@@ -65,6 +71,77 @@ This is a quick summary of the sections below:
|
||||
- MSRV is now 1.63.0
|
||||
- `List` no longer ignores empty strings
|
||||
|
||||
## Unreleased
|
||||
|
||||
### `Terminal`, `Buffer`, `Cell`, `Frame` are no longer `Sync` / `RefUnwindSafe` [#1339]
|
||||
|
||||
[#1339]: https://github.com/ratatui/ratatui/pull/1339
|
||||
|
||||
In #1339, we added a cache of the Cell width which uses a std::cell::Cell. This causes `Cell` and
|
||||
all types that contain this (`Terminal`, `Buffer`, `Frame`, `CompletedFrame`, `TestBackend`) to no
|
||||
longer be `Sync`
|
||||
|
||||
This change is unlikely to cause problems as these types likely should not be sent between threads
|
||||
regardless due to their interaction with various things which mutated externally (e.g. stdio).
|
||||
|
||||
### Removed public fields from `Rect` iterators ([#1358])
|
||||
|
||||
[#1358]: https://github.com/ratatui/ratatui/pull/1358
|
||||
|
||||
The `pub` modifier has been removed from fields on the `layout::rect::Columns` and
|
||||
`layout::rect::Rows`. These fields were not intended to be public and should not have been accessed
|
||||
directly.
|
||||
|
||||
### `Rect::area()` now returns u32 instead of u16 ([#1378])
|
||||
|
||||
[#1378]: https://github.com/ratatui/ratatui/pull/1378
|
||||
|
||||
This is likely to impact anything which relies on `Rect::area` maxing out at u16::MAX. It can now
|
||||
return up to u16::MAX * u16::MAX (2^32 - 2^17 + 1).
|
||||
|
||||
### `Line` now implements `From<Cow<str>` ([#1373])
|
||||
|
||||
[#1373]: https://github.com/ratatui/ratatui/pull/1373
|
||||
|
||||
As this adds an extra conversion, ambiguous inferred expressions may no longer compile.
|
||||
|
||||
```rust
|
||||
// given:
|
||||
struct Foo { ... }
|
||||
impl From<Foo> for String { ... }
|
||||
impl From<Foo> for Cow<str> { ... }
|
||||
|
||||
let foo = Foo { ... };
|
||||
let line = Line::from(foo); // now fails due to now ambiguous inferred type
|
||||
// replace with e.g.
|
||||
let line = Line::from(String::from(foo));
|
||||
```
|
||||
|
||||
### `Tabs::select()` now accepts `Into<Option<usize>>` ([#1413])
|
||||
|
||||
[#1413]: https://github.com/ratatui/ratatui/pull/1413
|
||||
|
||||
Previously `Tabs::select()` accepted `usize`, but it now accepts `Into<Option<usize>>`. This breaks
|
||||
any code already using parameter type inference:
|
||||
|
||||
```diff
|
||||
let selected = 1u8;
|
||||
- let tabs = Tabs::new(["A", "B"]).select(selected.into())
|
||||
+ let tabs = Tabs::new(["A", "B"]).select(selected as usize)
|
||||
```
|
||||
|
||||
### `Table::highlight_style` is now `Table::row_highlight_style` ([#1331])
|
||||
|
||||
[#1331]: https://github.com/ratatui/ratatui/pull/1331
|
||||
|
||||
The `Table::highlight_style` is now deprecated in favor of `Table::row_highlight_style`.
|
||||
|
||||
Also, the serialized output of the `TableState` will now include the "selected_column" field.
|
||||
Software that manually parse the serialized the output (with anything other than the `Serialize`
|
||||
implementation on `TableState`) may have to be refactored if the "selected_column" field is not
|
||||
accounted for. This does not affect users who rely on the `Deserialize`, or `Serialize`
|
||||
implementation on the state.
|
||||
|
||||
## v0.28.0
|
||||
|
||||
### `Backend::size` returns `Size` instead of `Rect` ([#1254])
|
||||
@@ -134,7 +211,7 @@ are also named terminal, and confusion about module exports for newer Rust users
|
||||
|
||||
This change simplifies the trait and makes it easier to implement.
|
||||
|
||||
### `Frame::size` is deprecated and renamed to `Frame::area`
|
||||
### `Frame::size` is deprecated and renamed to `Frame::area` ([#1293])
|
||||
|
||||
[#1293]: https://github.com/ratatui/ratatui/pull/1293
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ cassowary = "0.3"
|
||||
compact_str = "0.8.0"
|
||||
crossterm = { version = "0.28.1", optional = true }
|
||||
document-features = { version = "0.2.7", optional = true }
|
||||
indoc = "2"
|
||||
instability = "0.3.1"
|
||||
itertools = "0.13"
|
||||
lru = "0.12.0"
|
||||
@@ -53,11 +54,11 @@ fakeit = "1.1"
|
||||
font8x8 = "0.3.1"
|
||||
futures = "0.3.30"
|
||||
indoc = "2"
|
||||
octocrab = "0.40.0"
|
||||
octocrab = "0.41.0"
|
||||
pretty_assertions = "1.4.0"
|
||||
rand = "0.8.5"
|
||||
rand_chacha = "0.3.1"
|
||||
rstest = "0.22.0"
|
||||
rstest = "0.23.0"
|
||||
serde_json = "1.0.109"
|
||||
tokio = { version = "1.39.2", features = [
|
||||
"rt",
|
||||
@@ -137,6 +138,10 @@ macros = []
|
||||
## enables conversions from colors in the [`palette`] crate to [`Color`](crate::style::Color).
|
||||
palette = ["dep:palette"]
|
||||
|
||||
## Use terminal scrolling regions to make some operations less prone to
|
||||
## flickering. (i.e. Terminal::insert_before).
|
||||
scrolling-regions = []
|
||||
|
||||
## enables all widgets.
|
||||
all-widgets = ["widget-calendar"]
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ pub mod main {
|
||||
pub mod paragraph;
|
||||
pub mod rect;
|
||||
pub mod sparkline;
|
||||
pub mod table;
|
||||
}
|
||||
pub use main::*;
|
||||
|
||||
@@ -18,5 +19,6 @@ criterion::criterion_main!(
|
||||
list::benches,
|
||||
paragraph::benches,
|
||||
rect::benches,
|
||||
sparkline::benches
|
||||
sparkline::benches,
|
||||
table::benches,
|
||||
);
|
||||
|
||||
@@ -2,8 +2,7 @@ use criterion::{criterion_group, Bencher, BenchmarkId, Criterion};
|
||||
use rand::Rng;
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
prelude::Direction,
|
||||
layout::{Direction, Rect},
|
||||
widgets::{Bar, BarChart, BarGroup, Widget},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
use std::iter::zip;
|
||||
|
||||
use criterion::{black_box, BenchmarkId, Criterion};
|
||||
use ratatui::{
|
||||
buffer::{Buffer, Cell},
|
||||
layout::Rect,
|
||||
text::Line,
|
||||
widgets::Widget,
|
||||
};
|
||||
|
||||
criterion::criterion_group!(benches, empty, filled, with_lines);
|
||||
criterion::criterion_group!(benches, empty, filled, with_lines, diff);
|
||||
|
||||
const fn rect(size: u16) -> Rect {
|
||||
Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: size,
|
||||
height: size,
|
||||
}
|
||||
Rect::new(0, 0, size, size)
|
||||
}
|
||||
|
||||
fn empty(c: &mut Criterion) {
|
||||
@@ -63,3 +61,37 @@ fn with_lines(c: &mut Criterion) {
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn diff(c: &mut Criterion) {
|
||||
const AREA: Rect = Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 200,
|
||||
height: 50,
|
||||
};
|
||||
c.bench_function("buffer/diff", |b| {
|
||||
let buffer_1 = create_random_buffer(AREA);
|
||||
let buffer_2 = create_random_buffer(AREA);
|
||||
b.iter(|| {
|
||||
let _ = black_box(&buffer_1).diff(black_box(&buffer_2));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn create_random_buffer(area: Rect) -> Buffer {
|
||||
const PARAGRAPH_COUNT: i64 = 15;
|
||||
const SENTENCE_COUNT: i64 = 5;
|
||||
const WORD_COUNT: i64 = 20;
|
||||
const SEPARATOR: &str = "\n\n";
|
||||
let paragraphs = fakeit::words::paragraph(
|
||||
PARAGRAPH_COUNT,
|
||||
SENTENCE_COUNT,
|
||||
WORD_COUNT,
|
||||
SEPARATOR.to_string(),
|
||||
);
|
||||
let mut buffer = Buffer::empty(area);
|
||||
for (line, row) in zip(paragraphs.lines(), area.rows()) {
|
||||
Line::from(line).render(row, &mut buffer);
|
||||
}
|
||||
buffer
|
||||
}
|
||||
|
||||
69
benches/main/table.rs
Normal file
69
benches/main/table.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use criterion::{criterion_group, BatchSize, Bencher, BenchmarkId, Criterion};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Rect},
|
||||
widgets::{Row, StatefulWidget, Table, TableState, Widget},
|
||||
};
|
||||
|
||||
/// Benchmark for rendering a table.
|
||||
/// It only benchmarks the render with a different number of rows, and columns.
|
||||
fn table(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("table");
|
||||
|
||||
for row_count in [64, 2048, 16384] {
|
||||
for col_count in [2, 4, 8] {
|
||||
let bench_sizes = format!("{row_count}x{col_count}");
|
||||
let rows: Vec<Row> = (0..row_count)
|
||||
.map(|_| Row::new((0..col_count).map(|_| fakeit::words::quote())))
|
||||
.collect();
|
||||
|
||||
// Render default table
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("render", &bench_sizes),
|
||||
&Table::new(rows.clone(), [] as [Constraint; 0]),
|
||||
render,
|
||||
);
|
||||
|
||||
// Render with an offset to the middle of the table and a selected row
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("render_scroll_half", &bench_sizes),
|
||||
&Table::new(rows, [] as [Constraint; 0]).highlight_symbol(">>"),
|
||||
|b, table| {
|
||||
render_stateful(
|
||||
b,
|
||||
table,
|
||||
TableState::default()
|
||||
.with_offset(row_count / 2)
|
||||
.with_selected(Some(row_count / 2)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn render(bencher: &mut Bencher, table: &Table) {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 200, 50));
|
||||
bencher.iter_batched(
|
||||
|| table.to_owned(),
|
||||
|bench_table| {
|
||||
Widget::render(bench_table, buffer.area, &mut buffer);
|
||||
},
|
||||
BatchSize::LargeInput,
|
||||
);
|
||||
}
|
||||
|
||||
fn render_stateful(bencher: &mut Bencher, table: &Table, mut state: TableState) {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 200, 50));
|
||||
bencher.iter_batched(
|
||||
|| table.to_owned(),
|
||||
|bench_table| {
|
||||
StatefulWidget::render(bench_table, buffer.area, &mut buffer, &mut state);
|
||||
},
|
||||
BatchSize::LargeInput,
|
||||
);
|
||||
}
|
||||
|
||||
criterion_group!(benches, table);
|
||||
@@ -11,6 +11,7 @@ allow = [
|
||||
"MIT",
|
||||
"Unicode-DFS-2016",
|
||||
"WTFPL",
|
||||
"Zlib",
|
||||
]
|
||||
|
||||
[advisories]
|
||||
|
||||
@@ -244,7 +244,7 @@ impl Widget for &PullRequestListWidget {
|
||||
.block(block)
|
||||
.highlight_spacing(HighlightSpacing::Always)
|
||||
.highlight_symbol(">>")
|
||||
.highlight_style(Style::new().on_blue());
|
||||
.row_highlight_style(Style::new().on_blue());
|
||||
|
||||
StatefulWidget::render(table, area, buf, &mut state.table_state);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ struct App {
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn new() -> Self {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
|
||||
@@ -164,7 +164,7 @@ fn render_ingredients(selected_row: usize, area: Rect, buf: &mut Buffer) {
|
||||
Table::new(rows, [Constraint::Length(7), Constraint::Length(30)])
|
||||
.block(Block::new().style(theme.ingredients))
|
||||
.header(Row::new(vec!["Qty", "Ingredient"]).style(theme.ingredients_header))
|
||||
.highlight_style(Style::new().light_yellow()),
|
||||
.row_highlight_style(Style::new().light_yellow()),
|
||||
area,
|
||||
buf,
|
||||
&mut state,
|
||||
|
||||
@@ -60,7 +60,7 @@ fn render_hops(selected_row: usize, area: Rect, buf: &mut Buffer) {
|
||||
StatefulWidget::render(
|
||||
Table::new(rows, [Constraint::Max(100), Constraint::Length(15)])
|
||||
.header(Row::new(vec!["Host", "Address"]).set_style(THEME.traceroute.header))
|
||||
.highlight_style(THEME.traceroute.selected)
|
||||
.row_highlight_style(THEME.traceroute.selected)
|
||||
.block(block),
|
||||
area,
|
||||
buf,
|
||||
|
||||
@@ -13,57 +13,42 @@
|
||||
//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
|
||||
|
||||
use std::{
|
||||
io::{self},
|
||||
thread::sleep,
|
||||
time::Duration,
|
||||
use std::env::args;
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, Event};
|
||||
use ratatui::{
|
||||
layout::{Constraint, Layout},
|
||||
widgets::{RatatuiLogo, RatatuiLogoSize},
|
||||
DefaultTerminal, TerminalOptions, Viewport,
|
||||
};
|
||||
|
||||
use indoc::indoc;
|
||||
use itertools::izip;
|
||||
use ratatui::{widgets::Paragraph, TerminalOptions, Viewport};
|
||||
|
||||
/// A fun example of using half block characters to draw a logo
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn logo() -> String {
|
||||
let r = indoc! {"
|
||||
▄▄▄
|
||||
█▄▄▀
|
||||
█ █
|
||||
"};
|
||||
let a = indoc! {"
|
||||
▄▄
|
||||
█▄▄█
|
||||
█ █
|
||||
"};
|
||||
let t = indoc! {"
|
||||
▄▄▄
|
||||
█
|
||||
█
|
||||
"};
|
||||
let u = indoc! {"
|
||||
▄ ▄
|
||||
█ █
|
||||
▀▄▄▀
|
||||
"};
|
||||
let i = indoc! {"
|
||||
▄
|
||||
█
|
||||
█
|
||||
"};
|
||||
izip!(r.lines(), a.lines(), t.lines(), u.lines(), i.lines())
|
||||
.map(|(r, a, t, u, i)| format!("{r:5}{a:5}{t:4}{a:5}{t:4}{u:5}{i:5}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let mut terminal = ratatui::init_with_options(TerminalOptions {
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
let terminal = ratatui::init_with_options(TerminalOptions {
|
||||
viewport: Viewport::Inline(3),
|
||||
});
|
||||
terminal.draw(|frame| frame.render_widget(Paragraph::new(logo()), frame.area()))?;
|
||||
sleep(Duration::from_secs(5));
|
||||
let size = match args().nth(1).as_deref() {
|
||||
Some("small") => RatatuiLogoSize::Small,
|
||||
Some("tiny") => RatatuiLogoSize::Tiny,
|
||||
_ => RatatuiLogoSize::default(),
|
||||
};
|
||||
let result = run(terminal, size);
|
||||
ratatui::restore();
|
||||
println!();
|
||||
Ok(())
|
||||
result
|
||||
}
|
||||
|
||||
fn run(mut terminal: DefaultTerminal, size: RatatuiLogoSize) -> Result<()> {
|
||||
loop {
|
||||
terminal.draw(|frame| {
|
||||
use Constraint::{Fill, Length};
|
||||
let [top, bottom] = Layout::vertical([Length(1), Fill(1)]).areas(frame.area());
|
||||
frame.render_widget("Powered by", top);
|
||||
frame.render_widget(RatatuiLogo::new(size), bottom);
|
||||
})?;
|
||||
if matches!(event::read()?, Event::Key(_)) {
|
||||
break Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,13 @@
|
||||
//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::KeyModifiers;
|
||||
use itertools::Itertools;
|
||||
use ratatui::{
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Constraint, Layout, Margin, Rect},
|
||||
style::{self, Color, Modifier, Style, Stylize},
|
||||
text::{Line, Text},
|
||||
text::Text,
|
||||
widgets::{
|
||||
Block, BorderType, Cell, HighlightSpacing, Paragraph, Row, Scrollbar, ScrollbarOrientation,
|
||||
ScrollbarState, Table, TableState,
|
||||
@@ -35,8 +36,10 @@ const PALETTES: [tailwind::Palette; 4] = [
|
||||
tailwind::INDIGO,
|
||||
tailwind::RED,
|
||||
];
|
||||
const INFO_TEXT: &str =
|
||||
"(Esc) quit | (↑) move up | (↓) move down | (→) next color | (←) previous color";
|
||||
const INFO_TEXT: [&str; 2] = [
|
||||
"(Esc) quit | (↑) move up | (↓) move down | (←) move left | (→) move right",
|
||||
"(Shift + →) next color | (Shift + ←) previous color",
|
||||
];
|
||||
|
||||
const ITEM_HEIGHT: usize = 4;
|
||||
|
||||
@@ -52,7 +55,9 @@ struct TableColors {
|
||||
header_bg: Color,
|
||||
header_fg: Color,
|
||||
row_fg: Color,
|
||||
selected_style_fg: Color,
|
||||
selected_row_style_fg: Color,
|
||||
selected_column_style_fg: Color,
|
||||
selected_cell_style_fg: Color,
|
||||
normal_row_color: Color,
|
||||
alt_row_color: Color,
|
||||
footer_border_color: Color,
|
||||
@@ -65,7 +70,9 @@ impl TableColors {
|
||||
header_bg: color.c900,
|
||||
header_fg: tailwind::SLATE.c200,
|
||||
row_fg: tailwind::SLATE.c200,
|
||||
selected_style_fg: color.c400,
|
||||
selected_row_style_fg: color.c400,
|
||||
selected_column_style_fg: color.c400,
|
||||
selected_cell_style_fg: color.c600,
|
||||
normal_row_color: tailwind::SLATE.c950,
|
||||
alt_row_color: tailwind::SLATE.c900,
|
||||
footer_border_color: color.c400,
|
||||
@@ -118,8 +125,7 @@ impl App {
|
||||
items: data_vec,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) {
|
||||
pub fn next_row(&mut self) {
|
||||
let i = match self.state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.items.len() - 1 {
|
||||
@@ -134,7 +140,7 @@ impl App {
|
||||
self.scroll_state = self.scroll_state.position(i * ITEM_HEIGHT);
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
pub fn previous_row(&mut self) {
|
||||
let i = match self.state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
@@ -149,6 +155,14 @@ impl App {
|
||||
self.scroll_state = self.scroll_state.position(i * ITEM_HEIGHT);
|
||||
}
|
||||
|
||||
pub fn next_column(&mut self) {
|
||||
self.state.select_next_column();
|
||||
}
|
||||
|
||||
pub fn previous_column(&mut self) {
|
||||
self.state.select_previous_column();
|
||||
}
|
||||
|
||||
pub fn next_color(&mut self) {
|
||||
self.color_index = (self.color_index + 1) % PALETTES.len();
|
||||
}
|
||||
@@ -168,12 +182,17 @@ impl App {
|
||||
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
let shift_pressed = key.modifiers.contains(KeyModifiers::SHIFT);
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
|
||||
KeyCode::Char('j') | KeyCode::Down => self.next(),
|
||||
KeyCode::Char('k') | KeyCode::Up => self.previous(),
|
||||
KeyCode::Char('l') | KeyCode::Right => self.next_color(),
|
||||
KeyCode::Char('h') | KeyCode::Left => self.previous_color(),
|
||||
KeyCode::Char('j') | KeyCode::Down => self.next_row(),
|
||||
KeyCode::Char('k') | KeyCode::Up => self.previous_row(),
|
||||
KeyCode::Char('l') | KeyCode::Right if shift_pressed => self.next_color(),
|
||||
KeyCode::Char('h') | KeyCode::Left if shift_pressed => {
|
||||
self.previous_color();
|
||||
}
|
||||
KeyCode::Char('l') | KeyCode::Right => self.next_column(),
|
||||
KeyCode::Char('h') | KeyCode::Left => self.previous_column(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -182,7 +201,7 @@ impl App {
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame) {
|
||||
let vertical = &Layout::vertical([Constraint::Min(5), Constraint::Length(3)]);
|
||||
let vertical = &Layout::vertical([Constraint::Min(5), Constraint::Length(4)]);
|
||||
let rects = vertical.split(frame.area());
|
||||
|
||||
self.set_colors();
|
||||
@@ -196,9 +215,13 @@ impl App {
|
||||
let header_style = Style::default()
|
||||
.fg(self.colors.header_fg)
|
||||
.bg(self.colors.header_bg);
|
||||
let selected_style = Style::default()
|
||||
let selected_row_style = Style::default()
|
||||
.add_modifier(Modifier::REVERSED)
|
||||
.fg(self.colors.selected_style_fg);
|
||||
.fg(self.colors.selected_row_style_fg);
|
||||
let selected_col_style = Style::default().fg(self.colors.selected_column_style_fg);
|
||||
let selected_cell_style = Style::default()
|
||||
.add_modifier(Modifier::REVERSED)
|
||||
.fg(self.colors.selected_cell_style_fg);
|
||||
|
||||
let header = ["Name", "Address", "Email"]
|
||||
.into_iter()
|
||||
@@ -229,7 +252,9 @@ impl App {
|
||||
],
|
||||
)
|
||||
.header(header)
|
||||
.highlight_style(selected_style)
|
||||
.row_highlight_style(selected_row_style)
|
||||
.column_highlight_style(selected_col_style)
|
||||
.cell_highlight_style(selected_cell_style)
|
||||
.highlight_symbol(Text::from(vec![
|
||||
"".into(),
|
||||
bar.into(),
|
||||
@@ -256,7 +281,7 @@ impl App {
|
||||
}
|
||||
|
||||
fn render_footer(&self, frame: &mut Frame, area: Rect) {
|
||||
let info_footer = Paragraph::new(Line::from(INFO_TEXT))
|
||||
let info_footer = Paragraph::new(Text::from_iter(INFO_TEXT))
|
||||
.style(
|
||||
Style::new()
|
||||
.fg(self.colors.row_fg)
|
||||
|
||||
@@ -10,3 +10,5 @@ Enter
|
||||
Sleep 2s
|
||||
Show
|
||||
Sleep 2s
|
||||
Hide
|
||||
Escape
|
||||
|
||||
@@ -80,7 +80,7 @@ impl App {
|
||||
/// This allows the `App` type to be rendered as a widget. The `App` type owns several other widgets
|
||||
/// that are rendered as part of the app. The `Widget` trait is implemented on a mutable reference
|
||||
/// to the `App` type, which allows this to be rendered without consuming the `App` type, and allows
|
||||
/// the sub-widgets to be mutatable.
|
||||
/// the sub-widgets to be mutable.
|
||||
impl Widget for &mut App {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let constraints = Constraint::from_lengths([1, 1, 2, 1]);
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
//! ```rust,no_run
|
||||
//! use std::io::stdout;
|
||||
//!
|
||||
//! use ratatui::prelude::*;
|
||||
//! use ratatui::{backend::CrosstermBackend, Terminal};
|
||||
//!
|
||||
//! let backend = CrosstermBackend::new(stdout());
|
||||
//! let mut terminal = Terminal::new(backend)?;
|
||||
@@ -187,8 +187,10 @@ pub trait Backend {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::backend::{Backend, TestBackend};
|
||||
/// # use ratatui::backend::{TestBackend};
|
||||
/// # let mut backend = TestBackend::new(80, 25);
|
||||
/// use ratatui::backend::Backend;
|
||||
///
|
||||
/// backend.hide_cursor()?;
|
||||
/// // do something with hidden cursor
|
||||
/// backend.show_cursor()?;
|
||||
@@ -222,9 +224,10 @@ pub trait Backend {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::backend::{Backend, TestBackend};
|
||||
/// # use ratatui::layout::Position;
|
||||
/// # use ratatui::backend::{TestBackend};
|
||||
/// # let mut backend = TestBackend::new(80, 25);
|
||||
/// use ratatui::{backend::Backend, layout::Position};
|
||||
///
|
||||
/// backend.set_cursor_position(Position { x: 10, y: 20 })?;
|
||||
/// assert_eq!(backend.get_cursor_position()?, Position { x: 10, y: 20 });
|
||||
/// # std::io::Result::Ok(())
|
||||
@@ -254,8 +257,10 @@ pub trait Backend {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::backend::{Backend, TestBackend};
|
||||
/// # use ratatui::backend::{TestBackend};
|
||||
/// # let mut backend = TestBackend::new(80, 25);
|
||||
/// use ratatui::backend::Backend;
|
||||
///
|
||||
/// backend.clear()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
@@ -270,8 +275,10 @@ pub trait Backend {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::{prelude::*, backend::{TestBackend, ClearType}};
|
||||
/// # use ratatui::{backend::{TestBackend}};
|
||||
/// # let mut backend = TestBackend::new(80, 25);
|
||||
/// use ratatui::backend::{Backend, ClearType};
|
||||
///
|
||||
/// backend.clear_region(ClearType::All)?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
@@ -302,8 +309,10 @@ pub trait Backend {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, backend::TestBackend};
|
||||
/// let backend = TestBackend::new(80, 25);
|
||||
/// # use ratatui::{backend::{TestBackend}};
|
||||
/// # let backend = TestBackend::new(80, 25);
|
||||
/// use ratatui::{backend::Backend, layout::Size};
|
||||
///
|
||||
/// assert_eq!(backend.size()?, Size::new(80, 25));
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
@@ -318,6 +327,64 @@ pub trait Backend {
|
||||
|
||||
/// Flush any buffered content to the terminal screen.
|
||||
fn flush(&mut self) -> io::Result<()>;
|
||||
|
||||
/// Scroll a region of the screen upwards, where a region is specified by a (half-open) range
|
||||
/// of rows.
|
||||
///
|
||||
/// Each row in the region is replaced by the row `line_count` rows below it, except the bottom
|
||||
/// `line_count` rows, which are replaced by empty rows. If `line_count` is equal to or larger
|
||||
/// than the number of rows in the region, then all rows are replaced with empty rows.
|
||||
///
|
||||
/// If the region includes row 0, then `line_count` rows are copied into the bottom of the
|
||||
/// scrollback buffer. These rows are first taken from the old contents of the region, starting
|
||||
/// from the top. If there aren't sufficient rows in the region, then the remainder are empty
|
||||
/// rows.
|
||||
///
|
||||
/// The position of the cursor afterwards is undefined.
|
||||
///
|
||||
/// The behavior is designed to match what ANSI terminals do when scrolling regions are
|
||||
/// established. With ANSI terminals, a scrolling region can be established with the "^[[X;Yr"
|
||||
/// sequence, where X and Y define the lines of the region. The scrolling region can be reset
|
||||
/// to be the whole screen with the "^[[r" sequence.
|
||||
///
|
||||
/// When a scrolling region is established in an ANSI terminal, various operations' behaviors
|
||||
/// are changed in such a way that the scrolling region acts like a "virtual screen". In
|
||||
/// particular, the scrolling sequence "^[[NS", which scrolls lines up by a count of N.
|
||||
///
|
||||
/// On an ANSI terminal, this method will probably translate to something like:
|
||||
/// "^[[X;Yr^[[NS^[[r". That is, set the scrolling region, scroll up, then reset the scrolling
|
||||
/// region.
|
||||
///
|
||||
/// For examples of how this function is expected to work, refer to the tests for
|
||||
/// [`TestBackend::scroll_region_up`].
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn scroll_region_up(&mut self, region: std::ops::Range<u16>, line_count: u16)
|
||||
-> io::Result<()>;
|
||||
|
||||
/// Scroll a region of the screen downwards, where a region is specified by a (half-open) range
|
||||
/// of rows.
|
||||
///
|
||||
/// Each row in the region is replaced by the row `line_count` rows above it, except the top
|
||||
/// `line_count` rows, which are replaced by empty rows. If `line_count` is equal to or larger
|
||||
/// than the number of rows in the region, then all rows are replaced with empty rows.
|
||||
///
|
||||
/// The position of the cursor afterwards is undefined.
|
||||
///
|
||||
/// See the documentation for [`Self::scroll_region_down`] for more information about how this
|
||||
/// is expected to be implemented for ANSI terminals. All of that applies, except the ANSI
|
||||
/// sequence to scroll down is "^[[NT".
|
||||
///
|
||||
/// This function is asymmetrical with regards to the scrollback buffer. The reason is that
|
||||
/// this how terminals seem to implement things.
|
||||
///
|
||||
/// For examples of how this function is expected to work, refer to the tests for
|
||||
/// [`TestBackend::scroll_region_down`].
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn scroll_region_down(
|
||||
&mut self,
|
||||
region: std::ops::Range<u16>,
|
||||
line_count: u16,
|
||||
) -> io::Result<()>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -44,15 +44,11 @@ use crate::{
|
||||
/// ```rust,no_run
|
||||
/// use std::io::{stderr, stdout};
|
||||
///
|
||||
/// use ratatui::{
|
||||
/// crossterm::{
|
||||
/// terminal::{
|
||||
/// disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
/// },
|
||||
/// ExecutableCommand,
|
||||
/// },
|
||||
/// prelude::*,
|
||||
/// use crossterm::{
|
||||
/// terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
/// ExecutableCommand,
|
||||
/// };
|
||||
/// use ratatui::{backend::CrosstermBackend, Terminal};
|
||||
///
|
||||
/// let mut backend = CrosstermBackend::new(stdout());
|
||||
/// // or
|
||||
@@ -101,8 +97,10 @@ where
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use std::io::stdout;
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use std::io::stdout;
|
||||
///
|
||||
/// use ratatui::backend::CrosstermBackend;
|
||||
///
|
||||
/// let backend = CrosstermBackend::new(stdout());
|
||||
/// ```
|
||||
pub const fn new(writer: W) -> Self {
|
||||
@@ -276,6 +274,32 @@ where
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.writer.flush()
|
||||
}
|
||||
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn scroll_region_up(&mut self, region: std::ops::Range<u16>, amount: u16) -> io::Result<()> {
|
||||
queue!(
|
||||
self.writer,
|
||||
ScrollUpInRegion {
|
||||
first_row: region.start,
|
||||
last_row: region.end.saturating_sub(1),
|
||||
lines_to_scroll: amount,
|
||||
}
|
||||
)?;
|
||||
self.writer.flush()
|
||||
}
|
||||
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn scroll_region_down(&mut self, region: std::ops::Range<u16>, amount: u16) -> io::Result<()> {
|
||||
queue!(
|
||||
self.writer,
|
||||
ScrollDownInRegion {
|
||||
first_row: region.start,
|
||||
last_row: region.end.saturating_sub(1),
|
||||
lines_to_scroll: amount,
|
||||
}
|
||||
)?;
|
||||
self.writer.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for CColor {
|
||||
@@ -487,6 +511,102 @@ impl From<ContentStyle> for Style {
|
||||
}
|
||||
}
|
||||
|
||||
/// A command that scrolls the terminal screen a given number of rows up in a specific scrolling
|
||||
/// region.
|
||||
///
|
||||
/// This will hopefully be replaced by a struct in crossterm proper. There are two outstanding
|
||||
/// crossterm PRs that will address this:
|
||||
/// - [918](https://github.com/crossterm-rs/crossterm/pull/918)
|
||||
/// - [923](https://github.com/crossterm-rs/crossterm/pull/923)
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
struct ScrollUpInRegion {
|
||||
/// The first row of the scrolling region.
|
||||
pub first_row: u16,
|
||||
|
||||
/// The last row of the scrolling region.
|
||||
pub last_row: u16,
|
||||
|
||||
/// The number of lines to scroll up by.
|
||||
pub lines_to_scroll: u16,
|
||||
}
|
||||
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
impl crate::crossterm::Command for ScrollUpInRegion {
|
||||
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
|
||||
if self.lines_to_scroll != 0 {
|
||||
// Set a scrolling region that contains just the desired lines.
|
||||
write!(
|
||||
f,
|
||||
crate::crossterm::csi!("{};{}r"),
|
||||
self.first_row.saturating_add(1),
|
||||
self.last_row.saturating_add(1)
|
||||
)?;
|
||||
// Scroll the region by the desired count.
|
||||
write!(f, crate::crossterm::csi!("{}S"), self.lines_to_scroll)?;
|
||||
// Reset the scrolling region to be the whole screen.
|
||||
write!(f, crate::crossterm::csi!("r"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> io::Result<()> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
"ScrollUpInRegion command not supported for winapi",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// A command that scrolls the terminal screen a given number of rows down in a specific scrolling
|
||||
/// region.
|
||||
///
|
||||
/// This will hopefully be replaced by a struct in crossterm proper. There are two outstanding
|
||||
/// crossterm PRs that will address this:
|
||||
/// - [918](https://github.com/crossterm-rs/crossterm/pull/918)
|
||||
/// - [923](https://github.com/crossterm-rs/crossterm/pull/923)
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
struct ScrollDownInRegion {
|
||||
/// The first row of the scrolling region.
|
||||
pub first_row: u16,
|
||||
|
||||
/// The last row of the scrolling region.
|
||||
pub last_row: u16,
|
||||
|
||||
/// The number of lines to scroll down by.
|
||||
pub lines_to_scroll: u16,
|
||||
}
|
||||
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
impl crate::crossterm::Command for ScrollDownInRegion {
|
||||
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
|
||||
if self.lines_to_scroll != 0 {
|
||||
// Set a scrolling region that contains just the desired lines.
|
||||
write!(
|
||||
f,
|
||||
crate::crossterm::csi!("{};{}r"),
|
||||
self.first_row.saturating_add(1),
|
||||
self.last_row.saturating_add(1)
|
||||
)?;
|
||||
// Scroll the region by the desired count.
|
||||
write!(f, crate::crossterm::csi!("{}T"), self.lines_to_scroll)?;
|
||||
// Reset the scrolling region to be the whole screen.
|
||||
write!(f, crate::crossterm::csi!("r"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> io::Result<()> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
"ScrollDownInRegion command not supported for winapi",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -40,8 +40,9 @@ use crate::{
|
||||
/// use std::io::{stderr, stdout};
|
||||
///
|
||||
/// use ratatui::{
|
||||
/// prelude::*,
|
||||
/// backend::TermionBackend,
|
||||
/// termion::{raw::IntoRawMode, screen::IntoAlternateScreen},
|
||||
/// Terminal,
|
||||
/// };
|
||||
///
|
||||
/// let writer = stdout().into_raw_mode()?.into_alternate_screen()?;
|
||||
@@ -84,8 +85,10 @@ where
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use std::io::stdout;
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use std::io::stdout;
|
||||
///
|
||||
/// use ratatui::backend::TermionBackend;
|
||||
///
|
||||
/// let backend = TermionBackend::new(stdout());
|
||||
/// ```
|
||||
pub const fn new(writer: W) -> Self {
|
||||
@@ -236,6 +239,30 @@ where
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.writer.flush()
|
||||
}
|
||||
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn scroll_region_up(&mut self, region: std::ops::Range<u16>, amount: u16) -> io::Result<()> {
|
||||
write!(
|
||||
self.writer,
|
||||
"{}{}{}",
|
||||
SetRegion(region.start.saturating_add(1), region.end),
|
||||
termion::scroll::Up(amount),
|
||||
ResetRegion,
|
||||
)?;
|
||||
self.writer.flush()
|
||||
}
|
||||
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn scroll_region_down(&mut self, region: std::ops::Range<u16>, amount: u16) -> io::Result<()> {
|
||||
write!(
|
||||
self.writer,
|
||||
"{}{}{}",
|
||||
SetRegion(region.start.saturating_add(1), region.end),
|
||||
termion::scroll::Down(amount),
|
||||
ResetRegion,
|
||||
)?;
|
||||
self.writer.flush()
|
||||
}
|
||||
}
|
||||
struct Fg(Color);
|
||||
|
||||
@@ -465,6 +492,26 @@ impl From<termion::style::Reset> for Modifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set scrolling region.
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct SetRegion(pub u16, pub u16);
|
||||
|
||||
impl fmt::Display for SetRegion {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "\x1B[{};{}r", self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset scrolling region.
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct ResetRegion;
|
||||
|
||||
impl fmt::Display for ResetRegion {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "\x1B[r")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -38,7 +38,7 @@ use crate::{
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use ratatui::prelude::*;
|
||||
/// use ratatui::{backend::TermwizBackend, Terminal};
|
||||
///
|
||||
/// let backend = TermwizBackend::new()?;
|
||||
/// let mut terminal = Terminal::new(backend)?;
|
||||
@@ -78,7 +78,8 @@ impl TermwizBackend {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::backend::TermwizBackend;
|
||||
///
|
||||
/// let backend = TermwizBackend::new()?;
|
||||
/// # Ok::<(), Box<dyn std::error::Error>>(())
|
||||
/// ```
|
||||
@@ -249,6 +250,52 @@ impl Backend for TermwizBackend {
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn scroll_region_up(&mut self, region: std::ops::Range<u16>, amount: u16) -> io::Result<()> {
|
||||
// termwiz doesn't have a command to just set the scrolling region. Instead, setting the
|
||||
// scrolling region and scrolling are combined. However, this has the side-effect of
|
||||
// leaving the scrolling region set. To reset the scrolling region, termwiz advises one to
|
||||
// make a scrolling-region scroll command that contains the entire screen, but scrolls by 0
|
||||
// lines. See [`Change::ScrollRegionUp`] for more details.
|
||||
let (_, rows) = self.buffered_terminal.dimensions();
|
||||
self.buffered_terminal.add_changes(vec![
|
||||
Change::ScrollRegionUp {
|
||||
first_row: region.start as usize,
|
||||
region_size: region.len(),
|
||||
scroll_count: amount as usize,
|
||||
},
|
||||
Change::ScrollRegionUp {
|
||||
first_row: 0,
|
||||
region_size: rows,
|
||||
scroll_count: 0,
|
||||
},
|
||||
]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn scroll_region_down(&mut self, region: std::ops::Range<u16>, amount: u16) -> io::Result<()> {
|
||||
// termwiz doesn't have a command to just set the scrolling region. Instead, setting the
|
||||
// scrolling region and scrolling are combined. However, this has the side-effect of
|
||||
// leaving the scrolling region set. To reset the scrolling region, termwiz advises one to
|
||||
// make a scrolling-region scroll command that contains the entire screen, but scrolls by 0
|
||||
// lines. See [`Change::ScrollRegionDown`] for more details.
|
||||
let (_, rows) = self.buffered_terminal.dimensions();
|
||||
self.buffered_terminal.add_changes(vec![
|
||||
Change::ScrollRegionDown {
|
||||
first_row: region.start as usize,
|
||||
region_size: region.len(),
|
||||
scroll_count: amount as usize,
|
||||
},
|
||||
Change::ScrollRegionDown {
|
||||
first_row: 0,
|
||||
region_size: rows,
|
||||
scroll_count: 0,
|
||||
},
|
||||
]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CellAttributes> for Style {
|
||||
|
||||
@@ -6,8 +6,6 @@ use std::{
|
||||
io, iter,
|
||||
};
|
||||
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{
|
||||
backend::{Backend, ClearType, WindowSize},
|
||||
buffer::{Buffer, Cell},
|
||||
@@ -24,7 +22,7 @@ use crate::{
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{backend::TestBackend, prelude::*};
|
||||
/// use ratatui::backend::{Backend, TestBackend};
|
||||
///
|
||||
/// let mut backend = TestBackend::new(10, 2);
|
||||
/// backend.clear()?;
|
||||
@@ -52,13 +50,13 @@ fn buffer_view(buffer: &Buffer) -> String {
|
||||
let mut overwritten = vec![];
|
||||
let mut skip: usize = 0;
|
||||
view.push('"');
|
||||
for (x, c) in cells.iter().enumerate() {
|
||||
for (x, cell) in cells.iter().enumerate() {
|
||||
if skip == 0 {
|
||||
view.push_str(c.symbol());
|
||||
view.push_str(cell.symbol());
|
||||
} else {
|
||||
overwritten.push((x, c.symbol()));
|
||||
overwritten.push((x, cell.symbol()));
|
||||
}
|
||||
skip = std::cmp::max(skip, c.symbol().width()).saturating_sub(1);
|
||||
skip = std::cmp::max(skip, cell.width()).saturating_sub(1);
|
||||
}
|
||||
view.push('"');
|
||||
if !overwritten.is_empty() {
|
||||
@@ -80,6 +78,28 @@ impl TestBackend {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `TestBackend` with the specified lines as the initial screen state.
|
||||
///
|
||||
/// The backend's screen size is determined from the initial lines.
|
||||
#[must_use]
|
||||
pub fn with_lines<'line, Lines>(lines: Lines) -> Self
|
||||
where
|
||||
Lines: IntoIterator,
|
||||
Lines::Item: Into<crate::text::Line<'line>>,
|
||||
{
|
||||
let buffer = Buffer::with_lines(lines);
|
||||
let scrollback = Buffer::empty(Rect {
|
||||
width: buffer.area.width,
|
||||
..Rect::ZERO
|
||||
});
|
||||
Self {
|
||||
buffer,
|
||||
scrollback,
|
||||
cursor: false,
|
||||
pos: (0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the internal buffer of the `TestBackend`.
|
||||
pub const fn buffer(&self) -> &Buffer {
|
||||
&self.buffer
|
||||
@@ -247,7 +267,7 @@ impl Backend for TestBackend {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear_region(&mut self, clear_type: super::ClearType) -> io::Result<()> {
|
||||
fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
|
||||
let region = match clear_type {
|
||||
ClearType::All => return self.clear(),
|
||||
ClearType::AfterCursor => {
|
||||
@@ -343,6 +363,77 @@ impl Backend for TestBackend {
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn scroll_region_up(&mut self, region: std::ops::Range<u16>, scroll_by: u16) -> io::Result<()> {
|
||||
let width: usize = self.buffer.area.width.into();
|
||||
let cell_region_start = width * region.start.min(self.buffer.area.height) as usize;
|
||||
let cell_region_end = width * region.end.min(self.buffer.area.height) as usize;
|
||||
let cell_region_len = cell_region_end - cell_region_start;
|
||||
let cells_to_scroll_by = width * scroll_by as usize;
|
||||
|
||||
// Deal with the simple case where nothing needs to be copied into scrollback.
|
||||
if cell_region_start > 0 {
|
||||
if cells_to_scroll_by >= cell_region_len {
|
||||
// The scroll amount is large enough to clear the whole region.
|
||||
self.buffer.content[cell_region_start..cell_region_end].fill_with(Default::default);
|
||||
} else {
|
||||
// Scroll up by rotating, then filling in the bottom with empty cells.
|
||||
self.buffer.content[cell_region_start..cell_region_end]
|
||||
.rotate_left(cells_to_scroll_by);
|
||||
self.buffer.content[cell_region_end - cells_to_scroll_by..cell_region_end]
|
||||
.fill_with(Default::default);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// The rows inserted into the scrollback will first come from the buffer, and if that is
|
||||
// insufficient, will then be blank rows.
|
||||
let cells_from_region = cell_region_len.min(cells_to_scroll_by);
|
||||
append_to_scrollback(
|
||||
&mut self.scrollback,
|
||||
self.buffer.content.splice(
|
||||
0..cells_from_region,
|
||||
iter::repeat_with(Default::default).take(cells_from_region),
|
||||
),
|
||||
);
|
||||
if cells_to_scroll_by < cell_region_len {
|
||||
// Rotate the remaining cells to the front of the region.
|
||||
self.buffer.content[cell_region_start..cell_region_end].rotate_left(cells_from_region);
|
||||
} else {
|
||||
// Splice cleared out the region. Insert empty rows in scrollback.
|
||||
append_to_scrollback(
|
||||
&mut self.scrollback,
|
||||
iter::repeat_with(Default::default).take(cells_to_scroll_by - cell_region_len),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn scroll_region_down(
|
||||
&mut self,
|
||||
region: std::ops::Range<u16>,
|
||||
scroll_by: u16,
|
||||
) -> io::Result<()> {
|
||||
let width: usize = self.buffer.area.width.into();
|
||||
let cell_region_start = width * region.start.min(self.buffer.area.height) as usize;
|
||||
let cell_region_end = width * region.end.min(self.buffer.area.height) as usize;
|
||||
let cell_region_len = cell_region_end - cell_region_start;
|
||||
let cells_to_scroll_by = width * scroll_by as usize;
|
||||
|
||||
if cells_to_scroll_by >= cell_region_len {
|
||||
// The scroll amount is large enough to clear the whole region.
|
||||
self.buffer.content[cell_region_start..cell_region_end].fill_with(Default::default);
|
||||
} else {
|
||||
// Scroll up by rotating, then filling in the top with empty cells.
|
||||
self.buffer.content[cell_region_start..cell_region_end]
|
||||
.rotate_right(cells_to_scroll_by);
|
||||
self.buffer.content[cell_region_start..cell_region_start + cells_to_scroll_by]
|
||||
.fill_with(Default::default);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Append the provided cells to the bottom of a scrollback buffer. The number of cells must be a
|
||||
@@ -492,8 +583,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn clear_region_all() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines([
|
||||
let mut backend = TestBackend::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
@@ -513,8 +603,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn clear_region_after_cursor() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines([
|
||||
let mut backend = TestBackend::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
@@ -537,8 +626,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn clear_region_before_cursor() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines([
|
||||
let mut backend = TestBackend::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
@@ -561,8 +649,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn clear_region_current_line() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines([
|
||||
let mut backend = TestBackend::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
@@ -585,8 +672,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn clear_region_until_new_line() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines([
|
||||
let mut backend = TestBackend::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
@@ -609,8 +695,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn append_lines_not_at_last_line() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines([
|
||||
let mut backend = TestBackend::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
@@ -648,8 +733,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn append_lines_at_last_line() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines([
|
||||
let mut backend = TestBackend::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
@@ -681,8 +765,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn append_multiple_lines_not_at_last_line() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines([
|
||||
let mut backend = TestBackend::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
@@ -711,8 +794,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn append_multiple_lines_past_last_line() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines([
|
||||
let mut backend = TestBackend::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
@@ -739,8 +821,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn append_multiple_lines_where_cursor_at_end_appends_height_lines() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines([
|
||||
let mut backend = TestBackend::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
@@ -773,8 +854,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn append_multiple_lines_where_cursor_appends_height_lines() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines([
|
||||
let mut backend = TestBackend::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
@@ -799,8 +879,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn append_multiple_lines_where_cursor_at_end_appends_more_than_height_lines() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines([
|
||||
let mut backend = TestBackend::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
@@ -916,4 +995,81 @@ mod tests {
|
||||
let mut backend = TestBackend::new(10, 2);
|
||||
backend.flush().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
mod scrolling_regions {
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
|
||||
const A: &str = "aaaa";
|
||||
const B: &str = "bbbb";
|
||||
const C: &str = "cccc";
|
||||
const D: &str = "dddd";
|
||||
const E: &str = "eeee";
|
||||
const S: &str = " ";
|
||||
|
||||
#[rstest]
|
||||
#[case([A, B, C, D, E], 0..5, 0, [], [A, B, C, D, E])]
|
||||
#[case([A, B, C, D, E], 0..5, 2, [A, B], [C, D, E, S, S])]
|
||||
#[case([A, B, C, D, E], 0..5, 5, [A, B, C, D, E], [S, S, S, S, S])]
|
||||
#[case([A, B, C, D, E], 0..5, 7, [A, B, C, D, E, S, S], [S, S, S, S, S])]
|
||||
#[case([A, B, C, D, E], 0..3, 0, [], [A, B, C, D, E])]
|
||||
#[case([A, B, C, D, E], 0..3, 2, [A, B], [C, S, S, D, E])]
|
||||
#[case([A, B, C, D, E], 0..3, 3, [A, B, C], [S, S, S, D, E])]
|
||||
#[case([A, B, C, D, E], 0..3, 4, [A, B, C, S], [S, S, S, D, E])]
|
||||
#[case([A, B, C, D, E], 1..4, 0, [], [A, B, C, D, E])]
|
||||
#[case([A, B, C, D, E], 1..4, 2, [], [A, D, S, S, E])]
|
||||
#[case([A, B, C, D, E], 1..4, 3, [], [A, S, S, S, E])]
|
||||
#[case([A, B, C, D, E], 1..4, 4, [], [A, S, S, S, E])]
|
||||
#[case([A, B, C, D, E], 0..0, 0, [], [A, B, C, D, E])]
|
||||
#[case([A, B, C, D, E], 0..0, 2, [S, S], [A, B, C, D, E])]
|
||||
#[case([A, B, C, D, E], 2..2, 0, [], [A, B, C, D, E])]
|
||||
#[case([A, B, C, D, E], 2..2, 2, [], [A, B, C, D, E])]
|
||||
fn scroll_region_up<const L: usize, const M: usize, const N: usize>(
|
||||
#[case] initial_screen: [&'static str; L],
|
||||
#[case] range: std::ops::Range<u16>,
|
||||
#[case] scroll_by: u16,
|
||||
#[case] expected_scrollback: [&'static str; M],
|
||||
#[case] expected_buffer: [&'static str; N],
|
||||
) {
|
||||
let mut backend = TestBackend::with_lines(initial_screen);
|
||||
backend.scroll_region_up(range, scroll_by).unwrap();
|
||||
if expected_scrollback.is_empty() {
|
||||
backend.assert_scrollback_empty();
|
||||
} else {
|
||||
backend.assert_scrollback_lines(expected_scrollback);
|
||||
}
|
||||
backend.assert_buffer_lines(expected_buffer);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case([A, B, C, D, E], 0..5, 0, [A, B, C, D, E])]
|
||||
#[case([A, B, C, D, E], 0..5, 2, [S, S, A, B, C])]
|
||||
#[case([A, B, C, D, E], 0..5, 5, [S, S, S, S, S])]
|
||||
#[case([A, B, C, D, E], 0..5, 7, [S, S, S, S, S])]
|
||||
#[case([A, B, C, D, E], 0..3, 0, [A, B, C, D, E])]
|
||||
#[case([A, B, C, D, E], 0..3, 2, [S, S, A, D, E])]
|
||||
#[case([A, B, C, D, E], 0..3, 3, [S, S, S, D, E])]
|
||||
#[case([A, B, C, D, E], 0..3, 4, [S, S, S, D, E])]
|
||||
#[case([A, B, C, D, E], 1..4, 0, [A, B, C, D, E])]
|
||||
#[case([A, B, C, D, E], 1..4, 2, [A, S, S, B, E])]
|
||||
#[case([A, B, C, D, E], 1..4, 3, [A, S, S, S, E])]
|
||||
#[case([A, B, C, D, E], 1..4, 4, [A, S, S, S, E])]
|
||||
#[case([A, B, C, D, E], 0..0, 0, [A, B, C, D, E])]
|
||||
#[case([A, B, C, D, E], 0..0, 2, [A, B, C, D, E])]
|
||||
#[case([A, B, C, D, E], 2..2, 0, [A, B, C, D, E])]
|
||||
#[case([A, B, C, D, E], 2..2, 2, [A, B, C, D, E])]
|
||||
fn scroll_region_down<const M: usize, const N: usize>(
|
||||
#[case] initial_screen: [&'static str; M],
|
||||
#[case] range: std::ops::Range<u16>,
|
||||
#[case] scroll_by: u16,
|
||||
#[case] expected_buffer: [&'static str; N],
|
||||
) {
|
||||
let mut backend = TestBackend::with_lines(initial_screen);
|
||||
backend.scroll_region_down(range, scroll_by).unwrap();
|
||||
backend.assert_scrollback_empty();
|
||||
backend.assert_buffer_lines(expected_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,11 @@ macro_rules! assert_buffer_eq {
|
||||
#[allow(deprecated)]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Color, Style},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn assert_buffer_eq_does_not_panic_on_equal_buffers() {
|
||||
|
||||
@@ -6,7 +6,12 @@ use std::{
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{buffer::Cell, layout::Position, prelude::*};
|
||||
use crate::{
|
||||
buffer::Cell,
|
||||
layout::{Position, Rect},
|
||||
style::Style,
|
||||
text::{Line, Span},
|
||||
};
|
||||
|
||||
/// A buffer that maps to the desired content of the terminal after the draw call
|
||||
///
|
||||
@@ -74,7 +79,7 @@ impl Buffer {
|
||||
/// Returns a Buffer with all cells set to the default one
|
||||
#[must_use]
|
||||
pub fn empty(area: Rect) -> Self {
|
||||
Self::filled(area, Cell::EMPTY)
|
||||
Self::filled(area, Cell::empty())
|
||||
}
|
||||
|
||||
/// Returns a Buffer with all cells initialized with the attributes of the given Cell
|
||||
@@ -163,7 +168,11 @@ impl Buffer {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, buffer::Cell, layout::Position};
|
||||
/// use ratatui::{
|
||||
/// buffer::{Buffer, Cell},
|
||||
/// layout::{Position, Rect},
|
||||
/// };
|
||||
///
|
||||
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
|
||||
///
|
||||
/// assert_eq!(buffer.cell(Position::new(0, 0)), Some(&Cell::default()));
|
||||
@@ -190,7 +199,11 @@ impl Buffer {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, buffer::Cell, layout::Position};
|
||||
/// use ratatui::{
|
||||
/// buffer::{Buffer, Cell},
|
||||
/// layout::{Position, Rect},
|
||||
/// style::{Color, Style},
|
||||
/// };
|
||||
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
|
||||
///
|
||||
/// if let Some(cell) = buffer.cell_mut(Position::new(0, 0)) {
|
||||
@@ -214,7 +227,8 @@ impl Buffer {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{buffer::Buffer, layout::Rect};
|
||||
///
|
||||
/// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10));
|
||||
/// // Global coordinates to the top corner of this buffer's area
|
||||
/// assert_eq!(buffer.index_of(200, 100), 0);
|
||||
@@ -225,7 +239,8 @@ impl Buffer {
|
||||
/// Panics when given an coordinate that is outside of this Buffer's area.
|
||||
///
|
||||
/// ```should_panic
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{buffer::Buffer, layout::Rect};
|
||||
///
|
||||
/// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10));
|
||||
/// // Top coordinate is outside of the buffer in global coordinate space, as the Buffer's area
|
||||
/// // starts at (200, 100).
|
||||
@@ -254,9 +269,10 @@ impl Buffer {
|
||||
return None;
|
||||
}
|
||||
// remove offset
|
||||
let y = position.y - self.area.y;
|
||||
let x = position.x - self.area.x;
|
||||
Some((y * self.area.width + x) as usize)
|
||||
let y = (position.y - self.area.y) as usize;
|
||||
let x = (position.x - self.area.x) as usize;
|
||||
let width = self.area.width as usize;
|
||||
Some(y * width + x)
|
||||
}
|
||||
|
||||
/// Returns the (global) coordinates of a cell given its index
|
||||
@@ -266,7 +282,8 @@ impl Buffer {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{buffer::Buffer, layout::Rect};
|
||||
///
|
||||
/// let rect = Rect::new(200, 100, 10, 10);
|
||||
/// let buffer = Buffer::empty(rect);
|
||||
/// assert_eq!(buffer.pos_of(0), (200, 100));
|
||||
@@ -278,7 +295,8 @@ impl Buffer {
|
||||
/// Panics when given an index that is outside the Buffer's content.
|
||||
///
|
||||
/// ```should_panic
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{buffer::Buffer, layout::Rect};
|
||||
///
|
||||
/// let rect = Rect::new(0, 0, 10, 10); // 100 cells in total
|
||||
/// let buffer = Buffer::empty(rect);
|
||||
/// // Index 100 is the 101th cell, which lies outside of the area of this Buffer.
|
||||
@@ -377,6 +395,8 @@ impl Buffer {
|
||||
///
|
||||
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
pub fn set_style<S: Into<Style>>(&mut self, area: Rect, style: S) {
|
||||
let style = style.into();
|
||||
let area = self.area.intersection(area);
|
||||
@@ -394,7 +414,7 @@ impl Buffer {
|
||||
if self.content.len() > length {
|
||||
self.content.truncate(length);
|
||||
} else {
|
||||
self.content.resize(length, Cell::EMPTY);
|
||||
self.content.resize(length, Cell::empty());
|
||||
}
|
||||
self.area = area;
|
||||
}
|
||||
@@ -409,7 +429,7 @@ impl Buffer {
|
||||
/// Merge an other buffer into this one
|
||||
pub fn merge(&mut self, other: &Self) {
|
||||
let area = self.area.union(other.area);
|
||||
self.content.resize(area.area() as usize, Cell::EMPTY);
|
||||
self.content.resize(area.area() as usize, Cell::empty());
|
||||
|
||||
// Move original content to the appropriate space
|
||||
let size = self.area.area() as usize;
|
||||
@@ -479,9 +499,8 @@ impl Buffer {
|
||||
updates.push((x, y, &next_buffer[i]));
|
||||
}
|
||||
|
||||
to_skip = current.symbol().width().saturating_sub(1);
|
||||
|
||||
let affected_width = std::cmp::max(current.symbol().width(), previous.symbol().width());
|
||||
to_skip = current.width().saturating_sub(1);
|
||||
let affected_width = std::cmp::max(current.width(), previous.width());
|
||||
invalidated = std::cmp::max(affected_width, invalidated).saturating_sub(1);
|
||||
}
|
||||
updates
|
||||
@@ -504,7 +523,11 @@ impl<P: Into<Position>> Index<P> for Buffer {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, buffer::Cell, layout::Position};
|
||||
/// use ratatui::{
|
||||
/// buffer::{Buffer, Cell},
|
||||
/// layout::{Position, Rect},
|
||||
/// };
|
||||
///
|
||||
/// let buf = Buffer::empty(Rect::new(0, 0, 10, 10));
|
||||
/// let cell = &buf[(0, 0)];
|
||||
/// let cell = &buf[Position::new(0, 0)];
|
||||
@@ -530,7 +553,11 @@ impl<P: Into<Position>> IndexMut<P> for Buffer {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, buffer::Cell, layout::Position};
|
||||
/// use ratatui::{
|
||||
/// buffer::{Buffer, Cell},
|
||||
/// layout::{Position, Rect},
|
||||
/// };
|
||||
///
|
||||
/// 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");
|
||||
@@ -570,7 +597,7 @@ impl fmt::Debug for Buffer {
|
||||
} else {
|
||||
overwritten.push((x, c.symbol()));
|
||||
}
|
||||
skip = std::cmp::max(skip, c.symbol().width()).saturating_sub(1);
|
||||
skip = std::cmp::max(skip, c.width()).saturating_sub(1);
|
||||
#[cfg(feature = "underline-color")]
|
||||
{
|
||||
let style = (c.fg, c.bg, c.underline_color, c.modifier);
|
||||
@@ -622,6 +649,7 @@ mod tests {
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
use super::*;
|
||||
use crate::style::{Color, Modifier, Stylize};
|
||||
|
||||
#[test]
|
||||
fn debug_empty_buffer() {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use compact_str::CompactString;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use crate::prelude::*;
|
||||
use compact_str::CompactString;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::style::{Color, Modifier, Style};
|
||||
|
||||
/// A buffer cell
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Cell {
|
||||
/// The string to be drawn in the cell.
|
||||
@@ -31,11 +34,57 @@ pub struct Cell {
|
||||
|
||||
/// Whether the cell should be skipped when copying (diffing) the buffer to the screen.
|
||||
pub skip: bool,
|
||||
|
||||
/// Cache the width of the cell.
|
||||
width: std::cell::Cell<Option<usize>>,
|
||||
}
|
||||
|
||||
impl PartialEq for Cell {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let eq = self.symbol == other.symbol
|
||||
&& self.fg == other.fg
|
||||
&& self.bg == other.bg
|
||||
&& self.modifier == other.modifier
|
||||
&& self.skip == other.skip;
|
||||
// explicitly not comparing width, as it is a cache and may be not set
|
||||
// && self.width == other.width
|
||||
|
||||
#[cfg(feature = "underline-color")]
|
||||
return eq && self.underline_color == other.underline_color;
|
||||
#[cfg(not(feature = "underline-color"))]
|
||||
return eq;
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Cell {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.symbol.hash(state);
|
||||
self.fg.hash(state);
|
||||
self.bg.hash(state);
|
||||
#[cfg(feature = "underline-color")]
|
||||
self.underline_color.hash(state);
|
||||
self.modifier.hash(state);
|
||||
self.skip.hash(state);
|
||||
|
||||
// explicitly not hashing width, as it is a cache and not part of the cell's identity
|
||||
// self.width.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Cell {
|
||||
/// An empty `Cell`
|
||||
pub const EMPTY: Self = Self::new(" ");
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
symbol: CompactString::const_new(" "),
|
||||
fg: Color::Reset,
|
||||
bg: Color::Reset,
|
||||
#[cfg(feature = "underline-color")]
|
||||
underline_color: Color::Reset,
|
||||
modifier: Modifier::empty(),
|
||||
skip: false,
|
||||
width: std::cell::Cell::new(Some(1)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `Cell` with the given symbol.
|
||||
///
|
||||
@@ -52,6 +101,7 @@ impl Cell {
|
||||
underline_color: Color::Reset,
|
||||
modifier: Modifier::empty(),
|
||||
skip: false,
|
||||
width: std::cell::Cell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +114,7 @@ impl Cell {
|
||||
/// Sets the symbol of the cell.
|
||||
pub fn set_symbol(&mut self, symbol: &str) -> &mut Self {
|
||||
self.symbol = CompactString::new(symbol);
|
||||
self.width.set(None);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -72,6 +123,7 @@ impl Cell {
|
||||
/// This is particularly useful for adding zero-width characters to the cell.
|
||||
pub(crate) fn append_symbol(&mut self, symbol: &str) -> &mut Self {
|
||||
self.symbol.push_str(symbol);
|
||||
self.width.set(None);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -79,6 +131,7 @@ impl Cell {
|
||||
pub fn set_char(&mut self, ch: char) -> &mut Self {
|
||||
let mut buf = [0; 4];
|
||||
self.symbol = CompactString::new(ch.encode_utf8(&mut buf));
|
||||
self.width.set(None);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -148,18 +201,33 @@ impl Cell {
|
||||
}
|
||||
self.modifier = Modifier::empty();
|
||||
self.skip = false;
|
||||
self.width.set(Some(1));
|
||||
}
|
||||
|
||||
/// Returns the width of the cell.
|
||||
///
|
||||
/// This value is cached and will only be recomputed when the cell is modified.
|
||||
#[must_use]
|
||||
pub fn width(&self) -> usize {
|
||||
if let Some(width) = self.width.get() {
|
||||
width
|
||||
} else {
|
||||
let width = self.symbol().width();
|
||||
self.width.set(Some(width));
|
||||
width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Cell {
|
||||
fn default() -> Self {
|
||||
Self::EMPTY
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<char> for Cell {
|
||||
fn from(ch: char) -> Self {
|
||||
let mut cell = Self::EMPTY;
|
||||
let mut cell = Self::empty();
|
||||
cell.set_char(ch);
|
||||
cell
|
||||
}
|
||||
@@ -182,19 +250,20 @@ mod tests {
|
||||
underline_color: Color::Reset,
|
||||
modifier: Modifier::empty(),
|
||||
skip: false,
|
||||
width: std::cell::Cell::new(None),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
let cell = Cell::EMPTY;
|
||||
let cell = Cell::empty();
|
||||
assert_eq!(cell.symbol(), " ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_symbol() {
|
||||
let mut cell = Cell::EMPTY;
|
||||
let mut cell = Cell::empty();
|
||||
cell.set_symbol("あ"); // Multi-byte character
|
||||
assert_eq!(cell.symbol(), "あ");
|
||||
cell.set_symbol("👨👩👧👦"); // Multiple code units combined with ZWJ
|
||||
@@ -203,7 +272,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn append_symbol() {
|
||||
let mut cell = Cell::EMPTY;
|
||||
let mut cell = Cell::empty();
|
||||
cell.set_symbol("あ"); // Multi-byte character
|
||||
cell.append_symbol("\u{200B}"); // zero-width space
|
||||
assert_eq!(cell.symbol(), "あ\u{200B}");
|
||||
@@ -211,28 +280,28 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn set_char() {
|
||||
let mut cell = Cell::EMPTY;
|
||||
let mut cell = Cell::empty();
|
||||
cell.set_char('あ'); // Multi-byte character
|
||||
assert_eq!(cell.symbol(), "あ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_fg() {
|
||||
let mut cell = Cell::EMPTY;
|
||||
let mut cell = Cell::empty();
|
||||
cell.set_fg(Color::Red);
|
||||
assert_eq!(cell.fg, Color::Red);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_bg() {
|
||||
let mut cell = Cell::EMPTY;
|
||||
let mut cell = Cell::empty();
|
||||
cell.set_bg(Color::Red);
|
||||
assert_eq!(cell.bg, Color::Red);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_style() {
|
||||
let mut cell = Cell::EMPTY;
|
||||
let mut cell = Cell::empty();
|
||||
cell.set_style(Style::new().fg(Color::Red).bg(Color::Blue));
|
||||
assert_eq!(cell.fg, Color::Red);
|
||||
assert_eq!(cell.bg, Color::Blue);
|
||||
@@ -240,14 +309,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn set_skip() {
|
||||
let mut cell = Cell::EMPTY;
|
||||
let mut cell = Cell::empty();
|
||||
cell.set_skip(true);
|
||||
assert!(cell.skip);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset() {
|
||||
let mut cell = Cell::EMPTY;
|
||||
let mut cell = Cell::empty();
|
||||
cell.set_symbol("あ");
|
||||
cell.set_fg(Color::Red);
|
||||
cell.set_bg(Color::Blue);
|
||||
@@ -261,7 +330,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn style() {
|
||||
let cell = Cell::EMPTY;
|
||||
let cell = Cell::empty();
|
||||
assert_eq!(
|
||||
cell.style(),
|
||||
Style {
|
||||
@@ -294,4 +363,12 @@ mod tests {
|
||||
let cell2 = Cell::new("い");
|
||||
assert_ne!(cell1, cell2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn width() {
|
||||
let cell = Cell::new("あ");
|
||||
assert_eq!(cell.width, std::cell::Cell::new(None)); // not yet cached
|
||||
assert_eq!(cell.width(), 2);
|
||||
assert_eq!(cell.width, std::cell::Cell::new(Some(2))); // cached
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,5 +17,5 @@ pub use flex::Flex;
|
||||
pub use layout::Layout;
|
||||
pub use margin::Margin;
|
||||
pub use position::Position;
|
||||
pub use rect::*;
|
||||
pub use rect::{Columns, Offset, Positions, Rect, Rows};
|
||||
pub use size::Size;
|
||||
|
||||
@@ -26,7 +26,8 @@ use strum::EnumIs;
|
||||
/// `Constraint` provides helper methods to create lists of constraints from various input formats.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::Constraint;
|
||||
///
|
||||
/// // Create a layout with specified lengths for each element
|
||||
/// let constraints = Constraint::from_lengths([10, 20, 10]);
|
||||
///
|
||||
@@ -223,7 +224,8 @@ impl Constraint {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// # let area = Rect::default();
|
||||
/// let constraints = Constraint::from_lengths([1, 2, 3]);
|
||||
/// let layout = Layout::default().constraints(constraints).split(area);
|
||||
@@ -240,7 +242,8 @@ impl Constraint {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// # let area = Rect::default();
|
||||
/// let constraints = Constraint::from_ratios([(1, 4), (1, 2), (1, 4)]);
|
||||
/// let layout = Layout::default().constraints(constraints).split(area);
|
||||
@@ -257,7 +260,8 @@ impl Constraint {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// # let area = Rect::default();
|
||||
/// let constraints = Constraint::from_percentages([25, 50, 25]);
|
||||
/// let layout = Layout::default().constraints(constraints).split(area);
|
||||
@@ -274,7 +278,8 @@ impl Constraint {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// # let area = Rect::default();
|
||||
/// let constraints = Constraint::from_maxes([1, 2, 3]);
|
||||
/// let layout = Layout::default().constraints(constraints).split(area);
|
||||
@@ -291,7 +296,8 @@ impl Constraint {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// # let area = Rect::default();
|
||||
/// let constraints = Constraint::from_mins([1, 2, 3]);
|
||||
/// let layout = Layout::default().constraints(constraints).split(area);
|
||||
@@ -308,7 +314,8 @@ impl Constraint {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// # let area = Rect::default();
|
||||
/// let constraints = Constraint::from_fills([1, 2, 3]);
|
||||
/// let layout = Layout::default().constraints(constraints).split(area);
|
||||
@@ -330,7 +337,8 @@ impl From<u16> for Constraint {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||
///
|
||||
/// # let area = Rect::default();
|
||||
/// let layout = Layout::new(Direction::Vertical, [1, 2, 3]).split(area);
|
||||
/// let layout = Layout::horizontal([1, 2, 3]).split(area);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use strum::{Display, EnumIs, EnumString};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use super::constraint::Constraint;
|
||||
use crate::layout::Constraint;
|
||||
|
||||
/// Defines the options for layout flex justify content in a container.
|
||||
///
|
||||
|
||||
@@ -12,8 +12,7 @@ use self::strengths::{
|
||||
ALL_SEGMENT_GROW, FILL_GROW, GROW, LENGTH_SIZE_EQ, MAX_SIZE_EQ, MAX_SIZE_LE, MIN_SIZE_EQ,
|
||||
MIN_SIZE_GE, PERCENTAGE_SIZE_EQ, RATIO_SIZE_EQ, SPACER_SIZE_EQ, SPACE_GROW,
|
||||
};
|
||||
use super::Flex;
|
||||
use crate::prelude::*;
|
||||
use crate::layout::{Constraint, Direction, Flex, Margin, Rect};
|
||||
|
||||
type Rects = Rc<[Rect]>;
|
||||
type Segments = Rects;
|
||||
@@ -87,7 +86,11 @@ thread_local! {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// layout::{Constraint, Direction, Layout, Rect},
|
||||
/// widgets::Paragraph,
|
||||
/// Frame,
|
||||
/// };
|
||||
///
|
||||
/// fn render(frame: &mut Frame, area: Rect) {
|
||||
/// let layout = Layout::new(
|
||||
@@ -141,7 +144,8 @@ impl Layout {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Direction, Layout};
|
||||
///
|
||||
/// Layout::new(
|
||||
/// Direction::Horizontal,
|
||||
/// [Constraint::Length(5), Constraint::Min(0)],
|
||||
@@ -174,7 +178,8 @@ impl Layout {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Layout};
|
||||
///
|
||||
/// let layout = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
|
||||
/// ```
|
||||
pub fn vertical<I>(constraints: I) -> Self
|
||||
@@ -193,7 +198,8 @@ impl Layout {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Layout};
|
||||
///
|
||||
/// let layout = Layout::horizontal([Constraint::Length(5), Constraint::Min(0)]);
|
||||
/// ```
|
||||
pub fn horizontal<I>(constraints: I) -> Self
|
||||
@@ -221,7 +227,8 @@ impl Layout {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||
///
|
||||
/// let layout = Layout::default()
|
||||
/// .direction(Direction::Horizontal)
|
||||
/// .constraints([Constraint::Length(5), Constraint::Min(0)])
|
||||
@@ -255,7 +262,8 @@ impl Layout {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// let layout = Layout::default()
|
||||
/// .constraints([
|
||||
/// Constraint::Percentage(20),
|
||||
@@ -299,7 +307,8 @@ impl Layout {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// let layout = Layout::default()
|
||||
/// .constraints([Constraint::Min(0)])
|
||||
/// .margin(2)
|
||||
@@ -320,7 +329,8 @@ impl Layout {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// let layout = Layout::default()
|
||||
/// .constraints([Constraint::Min(0)])
|
||||
/// .horizontal_margin(2)
|
||||
@@ -338,7 +348,8 @@ impl Layout {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// let layout = Layout::default()
|
||||
/// .constraints([Constraint::Min(0)])
|
||||
/// .vertical_margin(2)
|
||||
@@ -369,7 +380,8 @@ impl Layout {
|
||||
/// In this example, the items in the layout will be aligned to the start.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::layout::{Flex, Layout, Constraint::*};
|
||||
/// use ratatui::layout::{Constraint::*, Flex, Layout};
|
||||
///
|
||||
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).flex(Flex::Start);
|
||||
/// ```
|
||||
///
|
||||
@@ -377,7 +389,8 @@ impl Layout {
|
||||
/// space.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::layout::{Flex, Layout, Constraint::*};
|
||||
/// use ratatui::layout::{Constraint::*, Flex, Layout};
|
||||
///
|
||||
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).flex(Flex::Legacy);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -397,7 +410,8 @@ impl Layout {
|
||||
/// In this example, the spacing between each item in the layout is set to 2 cells.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::layout::{Layout, Constraint::*};
|
||||
/// use ratatui::layout::{Constraint::*, Layout};
|
||||
///
|
||||
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).spacing(2);
|
||||
/// ```
|
||||
///
|
||||
@@ -426,7 +440,8 @@ impl Layout {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{layout::{Layout, Constraint}, Frame};
|
||||
///
|
||||
/// # fn render(frame: &mut Frame) {
|
||||
/// let area = frame.area();
|
||||
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
|
||||
@@ -458,7 +473,8 @@ impl Layout {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{layout::{Layout, Constraint}, Frame};
|
||||
///
|
||||
/// # fn render(frame: &mut Frame) {
|
||||
/// let area = frame.area();
|
||||
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
|
||||
@@ -497,7 +513,7 @@ impl Layout {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||
/// let layout = Layout::default()
|
||||
/// .direction(Direction::Vertical)
|
||||
/// .constraints([Constraint::Length(5), Constraint::Min(0)])
|
||||
@@ -529,7 +545,8 @@ impl Layout {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||
///
|
||||
/// let (areas, spacers) = Layout::default()
|
||||
/// .direction(Direction::Vertical)
|
||||
/// .constraints([Constraint::Length(5), Constraint::Min(0)])
|
||||
@@ -702,35 +719,35 @@ fn configure_constraints(
|
||||
constraints: &[Constraint],
|
||||
flex: Flex,
|
||||
) -> Result<(), AddConstraintError> {
|
||||
for (&constraint, &element) in constraints.iter().zip(segments.iter()) {
|
||||
for (&constraint, &segment) in constraints.iter().zip(segments.iter()) {
|
||||
match constraint {
|
||||
Constraint::Max(max) => {
|
||||
solver.add_constraint(element.has_max_size(max, MAX_SIZE_LE))?;
|
||||
solver.add_constraint(element.has_int_size(max, MAX_SIZE_EQ))?;
|
||||
solver.add_constraint(segment.has_max_size(max, MAX_SIZE_LE))?;
|
||||
solver.add_constraint(segment.has_int_size(max, MAX_SIZE_EQ))?;
|
||||
}
|
||||
Constraint::Min(min) => {
|
||||
solver.add_constraint(element.has_min_size(min, MIN_SIZE_GE))?;
|
||||
solver.add_constraint(segment.has_min_size(min, MIN_SIZE_GE))?;
|
||||
if flex.is_legacy() {
|
||||
solver.add_constraint(element.has_int_size(min, MIN_SIZE_EQ))?;
|
||||
solver.add_constraint(segment.has_int_size(min, MIN_SIZE_EQ))?;
|
||||
} else {
|
||||
solver.add_constraint(element.has_size(area, FILL_GROW))?;
|
||||
solver.add_constraint(segment.has_size(area, FILL_GROW))?;
|
||||
}
|
||||
}
|
||||
Constraint::Length(length) => {
|
||||
solver.add_constraint(element.has_int_size(length, LENGTH_SIZE_EQ))?;
|
||||
solver.add_constraint(segment.has_int_size(length, LENGTH_SIZE_EQ))?;
|
||||
}
|
||||
Constraint::Percentage(p) => {
|
||||
let size = area.size() * f64::from(p) / 100.00;
|
||||
solver.add_constraint(element.has_size(size, PERCENTAGE_SIZE_EQ))?;
|
||||
solver.add_constraint(segment.has_size(size, PERCENTAGE_SIZE_EQ))?;
|
||||
}
|
||||
Constraint::Ratio(num, den) => {
|
||||
// avoid division by zero by using 1 when denominator is 0
|
||||
let size = area.size() * f64::from(num) / f64::from(den.max(1));
|
||||
solver.add_constraint(element.has_size(size, RATIO_SIZE_EQ))?;
|
||||
solver.add_constraint(segment.has_size(size, RATIO_SIZE_EQ))?;
|
||||
}
|
||||
Constraint::Fill(_) => {
|
||||
// given no other constraints, this segment will grow as much as possible.
|
||||
solver.add_constraint(element.has_size(area, FILL_GROW))?;
|
||||
solver.add_constraint(segment.has_size(area, FILL_GROW))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -837,7 +854,7 @@ fn configure_fill_constraints(
|
||||
constraints: &[Constraint],
|
||||
flex: Flex,
|
||||
) -> Result<(), AddConstraintError> {
|
||||
for ((&left_constraint, &left_element), (&right_constraint, &right_element)) in constraints
|
||||
for ((&left_constraint, &left_segment), (&right_constraint, &right_segment)) in constraints
|
||||
.iter()
|
||||
.zip(segments.iter())
|
||||
.filter(|(c, _)| c.is_fill() || (!flex.is_legacy() && c.is_min()))
|
||||
@@ -854,9 +871,9 @@ fn configure_fill_constraints(
|
||||
_ => unreachable!(),
|
||||
};
|
||||
solver.add_constraint(
|
||||
(right_scaling_factor * left_element.size())
|
||||
(right_scaling_factor * left_segment.size())
|
||||
| EQ(GROW)
|
||||
| (left_scaling_factor * right_element.size()),
|
||||
| (left_scaling_factor * right_segment.size()),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -1293,9 +1310,9 @@ mod tests {
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::{
|
||||
layout::flex::Flex,
|
||||
prelude::{Constraint::*, *},
|
||||
widgets::Paragraph,
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Constraint::*, Direction, Flex, Layout, Rect},
|
||||
widgets::{Paragraph, Widget},
|
||||
};
|
||||
|
||||
/// Test that the given constraints applied to the given area result in the expected layout.
|
||||
@@ -1847,7 +1864,7 @@ mod tests {
|
||||
])
|
||||
.split(target);
|
||||
|
||||
assert_eq!(target.height, chunks.iter().map(|r| r.height).sum::<u16>());
|
||||
assert_eq!(chunks.iter().map(|r| r.height).sum::<u16>(), target.height);
|
||||
chunks.windows(2).for_each(|w| assert!(w[0].y <= w[1].y));
|
||||
}
|
||||
|
||||
@@ -1940,7 +1957,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
assert_eq!(r, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -1959,7 +1976,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, r);
|
||||
assert_eq!(r, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -1990,7 +2007,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
assert_eq!(r, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2020,7 +2037,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
assert_eq!(r, expected);
|
||||
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(&constraints)
|
||||
@@ -2029,7 +2046,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
assert_eq!(r, expected);
|
||||
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(&constraints)
|
||||
@@ -2038,7 +2055,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
assert_eq!(r, expected);
|
||||
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(&constraints)
|
||||
@@ -2047,7 +2064,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
assert_eq!(r, expected);
|
||||
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(&constraints)
|
||||
@@ -2056,7 +2073,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
assert_eq!(r, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2070,7 +2087,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
assert_eq!(r, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2117,7 +2134,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
assert_eq!(r, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2134,7 +2151,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
assert_eq!(r, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2152,7 +2169,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
assert_eq!(r, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2216,7 +2233,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, r);
|
||||
assert_eq!(r, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2242,7 +2259,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, result);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2274,7 +2291,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, r);
|
||||
assert_eq!(r, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2298,7 +2315,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, r);
|
||||
assert_eq!(r, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2323,7 +2340,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, result);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2366,7 +2383,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, result);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2386,7 +2403,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, result);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2410,7 +2427,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, result);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2436,7 +2453,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, result);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2462,7 +2479,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, result);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -2483,7 +2500,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, result);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@ use std::{
|
||||
fmt,
|
||||
};
|
||||
|
||||
use super::{Position, Size};
|
||||
use crate::prelude::*;
|
||||
use crate::layout::{Margin, Position, Size};
|
||||
|
||||
mod iter;
|
||||
pub use iter::*;
|
||||
@@ -27,7 +26,7 @@ pub struct Rect {
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
/// Amounts by which to move a [`Rect`](super::Rect).
|
||||
/// Amounts by which to move a [`Rect`](crate::layout::Rect).
|
||||
///
|
||||
/// Positive numbers move to the right/bottom and negative to the left/top.
|
||||
///
|
||||
@@ -56,32 +55,41 @@ impl Rect {
|
||||
height: 0,
|
||||
};
|
||||
|
||||
/// Creates a new `Rect`, with width and height limited to keep the area under max `u16`. If
|
||||
/// clipped, aspect ratio will be preserved.
|
||||
pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
|
||||
let max_area = u16::MAX;
|
||||
let (clipped_width, clipped_height) =
|
||||
if u32::from(width) * u32::from(height) > u32::from(max_area) {
|
||||
let aspect_ratio = f64::from(width) / f64::from(height);
|
||||
let max_area_f = f64::from(max_area);
|
||||
let height_f = (max_area_f / aspect_ratio).sqrt();
|
||||
let width_f = height_f * aspect_ratio;
|
||||
(width_f as u16, height_f as u16)
|
||||
} else {
|
||||
(width, height)
|
||||
};
|
||||
/// Creates a new `Rect`, with width and height limited to keep both bounds within `u16`.
|
||||
///
|
||||
/// If the width or height would cause the right or bottom coordinate to be larger than the
|
||||
/// maximum value of `u16`, the width or height will be clamped to keep the right or bottom
|
||||
/// coordinate within `u16`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::layout::Rect;
|
||||
///
|
||||
/// let rect = Rect::new(1, 2, 3, 4);
|
||||
/// ```
|
||||
pub const fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
|
||||
// these calculations avoid using min so that this function can be const
|
||||
let max_width = u16::MAX - x;
|
||||
let max_height = u16::MAX - y;
|
||||
let width = if width > max_width { max_width } else { width };
|
||||
let height = if height > max_height {
|
||||
max_height
|
||||
} else {
|
||||
height
|
||||
};
|
||||
Self {
|
||||
x,
|
||||
y,
|
||||
width: clipped_width,
|
||||
height: clipped_height,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
/// The area of the `Rect`. If the area is larger than the maximum value of `u16`, it will be
|
||||
/// clamped to `u16::MAX`.
|
||||
pub const fn area(self) -> u16 {
|
||||
self.width.saturating_mul(self.height)
|
||||
pub const fn area(self) -> u32 {
|
||||
(self.width as u32) * (self.height as u32)
|
||||
}
|
||||
|
||||
/// Returns true if the `Rect` has no area.
|
||||
@@ -205,7 +213,8 @@ impl Rect {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, layout::Position};
|
||||
/// use ratatui::layout::{Position, Rect};
|
||||
///
|
||||
/// let rect = Rect::new(1, 2, 3, 4);
|
||||
/// assert!(rect.contains(Position { x: 1, y: 2 }));
|
||||
/// ````
|
||||
@@ -234,7 +243,8 @@ impl Rect {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{layout::Rect, Frame};
|
||||
///
|
||||
/// # fn render(frame: &mut Frame) {
|
||||
/// let area = frame.area();
|
||||
/// let rect = Rect::new(0, 0, 100, 100).clamp(area);
|
||||
@@ -254,7 +264,8 @@ impl Rect {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
|
||||
///
|
||||
/// fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// for row in area.rows() {
|
||||
/// Line::raw("Hello, world!").render(row, buf);
|
||||
@@ -270,7 +281,11 @@ impl Rect {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// widgets::{Block, Borders, Widget},
|
||||
/// };
|
||||
/// fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// if let Some(left) = area.columns().next() {
|
||||
/// Block::new().borders(Borders::LEFT).render(left, buf);
|
||||
@@ -288,7 +303,8 @@ impl Rect {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{buffer::Buffer, layout::Rect};
|
||||
///
|
||||
/// fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// for position in area.positions() {
|
||||
/// buf[(position.x, position.y)].set_symbol("x");
|
||||
@@ -304,7 +320,8 @@ impl Rect {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::layout::Rect;
|
||||
///
|
||||
/// let rect = Rect::new(1, 2, 3, 4);
|
||||
/// let position = rect.as_position();
|
||||
/// ````
|
||||
@@ -352,6 +369,7 @@ mod tests {
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
use crate::layout::{Constraint, Layout};
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
@@ -496,46 +514,28 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn size_truncation() {
|
||||
for width in 256u16..300u16 {
|
||||
for height in 256u16..300u16 {
|
||||
let rect = Rect::new(0, 0, width, height);
|
||||
rect.area(); // Should not panic.
|
||||
assert!(rect.width < width || rect.height < height);
|
||||
// The target dimensions are rounded down so the math will not be too precise
|
||||
// but let's make sure the ratios don't diverge crazily.
|
||||
assert!(
|
||||
(f64::from(rect.width) / f64::from(rect.height)
|
||||
- f64::from(width) / f64::from(height))
|
||||
.abs()
|
||||
< 1.0
|
||||
);
|
||||
assert_eq!(
|
||||
Rect::new(u16::MAX - 100, u16::MAX - 1000, 200, 2000),
|
||||
Rect {
|
||||
x: u16::MAX - 100,
|
||||
y: u16::MAX - 1000,
|
||||
width: 100,
|
||||
height: 1000
|
||||
}
|
||||
}
|
||||
|
||||
// One dimension below 255, one above. Area above max u16.
|
||||
let width = 900;
|
||||
let height = 100;
|
||||
let rect = Rect::new(0, 0, width, height);
|
||||
assert_ne!(rect.width, 900);
|
||||
assert_ne!(rect.height, 100);
|
||||
assert!(rect.width < width || rect.height < height);
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn size_preservation() {
|
||||
for width in 0..256u16 {
|
||||
for height in 0..256u16 {
|
||||
let rect = Rect::new(0, 0, width, height);
|
||||
rect.area(); // Should not panic.
|
||||
assert_eq!(rect.width, width);
|
||||
assert_eq!(rect.height, height);
|
||||
assert_eq!(
|
||||
Rect::new(u16::MAX - 100, u16::MAX - 1000, 100, 1000),
|
||||
Rect {
|
||||
x: u16::MAX - 100,
|
||||
y: u16::MAX - 1000,
|
||||
width: 100,
|
||||
height: 1000
|
||||
}
|
||||
}
|
||||
|
||||
// One dimension below 255, one above. Area below max u16.
|
||||
let rect = Rect::new(0, 0, 300, 100);
|
||||
assert_eq!(rect.width, 300);
|
||||
assert_eq!(rect.height, 100);
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -546,7 +546,7 @@ mod tests {
|
||||
width: 10,
|
||||
height: 10,
|
||||
};
|
||||
const _AREA: u16 = RECT.area();
|
||||
const _AREA: u32 = RECT.area();
|
||||
const _LEFT: u16 = RECT.left();
|
||||
const _RIGHT: u16 = RECT.right();
|
||||
const _TOP: u16 = RECT.top();
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use crate::prelude::*;
|
||||
use crate::layout::{Position, Rect};
|
||||
|
||||
/// An iterator over rows within a `Rect`.
|
||||
pub struct Rows {
|
||||
/// The `Rect` associated with the rows.
|
||||
pub rect: Rect,
|
||||
/// The y coordinate of the row within the `Rect`.
|
||||
pub current_row: u16,
|
||||
rect: Rect,
|
||||
/// The y coordinate of the row within the `Rect` when iterating forwards.
|
||||
current_row_fwd: u16,
|
||||
/// The y coordinate of the row within the `Rect` when iterating backwards.
|
||||
current_row_back: u16,
|
||||
}
|
||||
|
||||
impl Rows {
|
||||
@@ -13,7 +15,8 @@ impl Rows {
|
||||
pub const fn new(rect: Rect) -> Self {
|
||||
Self {
|
||||
rect,
|
||||
current_row: rect.y,
|
||||
current_row_fwd: rect.y,
|
||||
current_row_back: rect.bottom(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,11 +28,25 @@ impl Iterator for Rows {
|
||||
///
|
||||
/// Returns `None` when there are no more rows to iterate through.
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.current_row >= self.rect.bottom() {
|
||||
if self.current_row_fwd >= self.current_row_back {
|
||||
return None;
|
||||
}
|
||||
let row = Rect::new(self.rect.x, self.current_row, self.rect.width, 1);
|
||||
self.current_row += 1;
|
||||
let row = Rect::new(self.rect.x, self.current_row_fwd, self.rect.width, 1);
|
||||
self.current_row_fwd += 1;
|
||||
Some(row)
|
||||
}
|
||||
}
|
||||
|
||||
impl DoubleEndedIterator for Rows {
|
||||
/// Retrieves the previous row within the `Rect`.
|
||||
///
|
||||
/// Returns `None` when there are no more rows to iterate through.
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
if self.current_row_back <= self.current_row_fwd {
|
||||
return None;
|
||||
}
|
||||
self.current_row_back -= 1;
|
||||
let row = Rect::new(self.rect.x, self.current_row_back, self.rect.width, 1);
|
||||
Some(row)
|
||||
}
|
||||
}
|
||||
@@ -37,9 +54,11 @@ impl Iterator for Rows {
|
||||
/// An iterator over columns within a `Rect`.
|
||||
pub struct Columns {
|
||||
/// The `Rect` associated with the columns.
|
||||
pub rect: Rect,
|
||||
/// The x coordinate of the column within the `Rect`.
|
||||
pub current_column: u16,
|
||||
rect: Rect,
|
||||
/// The x coordinate of the column within the `Rect` when iterating forwards.
|
||||
current_column_fwd: u16,
|
||||
/// The x coordinate of the column within the `Rect` when iterating backwards.
|
||||
current_column_back: u16,
|
||||
}
|
||||
|
||||
impl Columns {
|
||||
@@ -47,7 +66,8 @@ impl Columns {
|
||||
pub const fn new(rect: Rect) -> Self {
|
||||
Self {
|
||||
rect,
|
||||
current_column: rect.x,
|
||||
current_column_fwd: rect.x,
|
||||
current_column_back: rect.right(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,11 +79,25 @@ impl Iterator for Columns {
|
||||
///
|
||||
/// Returns `None` when there are no more columns to iterate through.
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.current_column >= self.rect.right() {
|
||||
if self.current_column_fwd >= self.current_column_back {
|
||||
return None;
|
||||
}
|
||||
let column = Rect::new(self.current_column, self.rect.y, 1, self.rect.height);
|
||||
self.current_column += 1;
|
||||
let column = Rect::new(self.current_column_fwd, self.rect.y, 1, self.rect.height);
|
||||
self.current_column_fwd += 1;
|
||||
Some(column)
|
||||
}
|
||||
}
|
||||
|
||||
impl DoubleEndedIterator for Columns {
|
||||
/// Retrieves the previous column within the `Rect`.
|
||||
///
|
||||
/// Returns `None` when there are no more columns to iterate through.
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
if self.current_column_back <= self.current_column_fwd {
|
||||
return None;
|
||||
}
|
||||
self.current_column_back -= 1;
|
||||
let column = Rect::new(self.current_column_back, self.rect.y, 1, self.rect.height);
|
||||
Some(column)
|
||||
}
|
||||
}
|
||||
@@ -114,19 +148,92 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn rows() {
|
||||
let rect = Rect::new(0, 0, 2, 2);
|
||||
let rect = Rect::new(0, 0, 2, 3);
|
||||
let mut rows = Rows::new(rect);
|
||||
assert_eq!(rows.next(), Some(Rect::new(0, 0, 2, 1)));
|
||||
assert_eq!(rows.next(), Some(Rect::new(0, 1, 2, 1)));
|
||||
assert_eq!(rows.next(), Some(Rect::new(0, 2, 2, 1)));
|
||||
assert_eq!(rows.next(), None);
|
||||
assert_eq!(rows.next_back(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rows_back() {
|
||||
let rect = Rect::new(0, 0, 2, 3);
|
||||
let mut rows = Rows::new(rect);
|
||||
assert_eq!(rows.next_back(), Some(Rect::new(0, 2, 2, 1)));
|
||||
assert_eq!(rows.next_back(), Some(Rect::new(0, 1, 2, 1)));
|
||||
assert_eq!(rows.next_back(), Some(Rect::new(0, 0, 2, 1)));
|
||||
assert_eq!(rows.next_back(), None);
|
||||
assert_eq!(rows.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rows_meet_in_the_middle() {
|
||||
let rect = Rect::new(0, 0, 2, 4);
|
||||
let mut rows = Rows::new(rect);
|
||||
assert_eq!(rows.next(), Some(Rect::new(0, 0, 2, 1)));
|
||||
assert_eq!(rows.next_back(), Some(Rect::new(0, 3, 2, 1)));
|
||||
assert_eq!(rows.next(), Some(Rect::new(0, 1, 2, 1)));
|
||||
assert_eq!(rows.next_back(), Some(Rect::new(0, 2, 2, 1)));
|
||||
assert_eq!(rows.next(), None);
|
||||
assert_eq!(rows.next_back(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn columns() {
|
||||
let rect = Rect::new(0, 0, 2, 2);
|
||||
let rect = Rect::new(0, 0, 3, 2);
|
||||
let mut columns = Columns::new(rect);
|
||||
assert_eq!(columns.next(), Some(Rect::new(0, 0, 1, 2)));
|
||||
assert_eq!(columns.next(), Some(Rect::new(1, 0, 1, 2)));
|
||||
assert_eq!(columns.next(), Some(Rect::new(2, 0, 1, 2)));
|
||||
assert_eq!(columns.next(), None);
|
||||
assert_eq!(columns.next_back(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn columns_back() {
|
||||
let rect = Rect::new(0, 0, 3, 2);
|
||||
let mut columns = Columns::new(rect);
|
||||
assert_eq!(columns.next_back(), Some(Rect::new(2, 0, 1, 2)));
|
||||
assert_eq!(columns.next_back(), Some(Rect::new(1, 0, 1, 2)));
|
||||
assert_eq!(columns.next_back(), Some(Rect::new(0, 0, 1, 2)));
|
||||
assert_eq!(columns.next_back(), None);
|
||||
assert_eq!(columns.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn columns_meet_in_the_middle() {
|
||||
let rect = Rect::new(0, 0, 4, 2);
|
||||
let mut columns = Columns::new(rect);
|
||||
assert_eq!(columns.next(), Some(Rect::new(0, 0, 1, 2)));
|
||||
assert_eq!(columns.next_back(), Some(Rect::new(3, 0, 1, 2)));
|
||||
assert_eq!(columns.next(), Some(Rect::new(1, 0, 1, 2)));
|
||||
assert_eq!(columns.next_back(), Some(Rect::new(2, 0, 1, 2)));
|
||||
assert_eq!(columns.next(), None);
|
||||
assert_eq!(columns.next_back(), None);
|
||||
}
|
||||
|
||||
/// We allow a total of `65536` columns in the range `(0..=65535)`. In this test we iterate
|
||||
/// forward and skip the first `65534` columns, and expect the next column to be `65535` and
|
||||
/// the subsequent columns to be `None`.
|
||||
#[test]
|
||||
fn columns_max() {
|
||||
let rect = Rect::new(0, 0, u16::MAX, 1);
|
||||
let mut columns = Columns::new(rect).skip(usize::from(u16::MAX - 1));
|
||||
assert_eq!(columns.next(), Some(Rect::new(u16::MAX - 1, 0, 1, 1)));
|
||||
assert_eq!(columns.next(), None);
|
||||
}
|
||||
|
||||
/// We allow a total of `65536` columns in the range `(0..=65535)`. In this test we iterate
|
||||
/// backward and skip the last `65534` columns, and expect the next column to be `0` and the
|
||||
/// subsequent columns to be `None`.
|
||||
#[test]
|
||||
fn columns_min() {
|
||||
let rect = Rect::new(0, 0, u16::MAX, 1);
|
||||
let mut columns = Columns::new(rect).rev().skip(usize::from(u16::MAX - 1));
|
||||
assert_eq!(columns.next(), Some(Rect::new(0, 0, 1, 1)));
|
||||
assert_eq!(columns.next(), None);
|
||||
assert_eq!(columns.next(), None);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![warn(missing_docs)]
|
||||
use std::fmt;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::layout::Rect;
|
||||
|
||||
/// A simple size struct
|
||||
///
|
||||
|
||||
@@ -23,7 +23,6 @@ pub use crate::backend::CrosstermBackend;
|
||||
pub use crate::backend::TermionBackend;
|
||||
#[cfg(feature = "termwiz")]
|
||||
pub use crate::backend::TermwizBackend;
|
||||
pub(crate) use crate::widgets::{StatefulWidgetRef, WidgetRef};
|
||||
pub use crate::{
|
||||
backend::{self, Backend},
|
||||
buffer::{self, Buffer},
|
||||
|
||||
168
src/style.rs
168
src/style.rs
@@ -13,7 +13,10 @@
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```
|
||||
//! use ratatui::prelude::*;
|
||||
//! use ratatui::{
|
||||
//! style::{Color, Modifier, Style},
|
||||
//! text::Span,
|
||||
//! };
|
||||
//!
|
||||
//! let heading_style = Style::new()
|
||||
//! .fg(Color::Black)
|
||||
@@ -41,7 +44,11 @@
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```
|
||||
//! use ratatui::{prelude::*, widgets::*};
|
||||
//! use ratatui::{
|
||||
//! style::{Color, Modifier, Style, Stylize},
|
||||
//! text::Span,
|
||||
//! widgets::Paragraph,
|
||||
//! };
|
||||
//!
|
||||
//! assert_eq!(
|
||||
//! "hello".red().on_blue().bold(),
|
||||
@@ -92,7 +99,7 @@ bitflags! {
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{prelude::*};
|
||||
/// use ratatui::style::Modifier;
|
||||
///
|
||||
/// let m = Modifier::BOLD | Modifier::ITALIC;
|
||||
/// ```
|
||||
@@ -128,7 +135,7 @@ impl fmt::Debug for Modifier {
|
||||
/// Style lets you control the main characteristics of the displayed elements.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::prelude::*;
|
||||
/// use ratatui::style::{Color, Modifier, Style};
|
||||
///
|
||||
/// Style::default()
|
||||
/// .fg(Color::Black)
|
||||
@@ -139,7 +146,8 @@ impl fmt::Debug for Modifier {
|
||||
/// Styles can also be created with a [shorthand notation](crate::style#using-style-shorthands).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::{Style, Stylize};
|
||||
///
|
||||
/// Style::new().black().on_green().italic().bold();
|
||||
/// ```
|
||||
///
|
||||
@@ -149,7 +157,11 @@ impl fmt::Debug for Modifier {
|
||||
/// anywhere that accepts `Into<Style>`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Color, Modifier, Style},
|
||||
/// text::Line,
|
||||
/// };
|
||||
///
|
||||
/// Line::styled("hello", Style::new().fg(Color::Red));
|
||||
/// // simplifies to
|
||||
/// Line::styled("hello", Color::Red);
|
||||
@@ -164,7 +176,11 @@ impl fmt::Debug for Modifier {
|
||||
/// just S3.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// style::{Color, Modifier, Style},
|
||||
/// };
|
||||
///
|
||||
/// let styles = [
|
||||
/// Style::default()
|
||||
@@ -200,7 +216,11 @@ impl fmt::Debug for Modifier {
|
||||
/// reset all properties until that point use [`Style::reset`].
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// style::{Color, Modifier, Style},
|
||||
/// };
|
||||
///
|
||||
/// let styles = [
|
||||
/// Style::default()
|
||||
@@ -240,46 +260,7 @@ pub struct Style {
|
||||
impl fmt::Debug for Style {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("Style::new()")?;
|
||||
if let Some(fg) = self.fg {
|
||||
fg.stylize_debug(ColorDebugKind::Foreground).fmt(f)?;
|
||||
}
|
||||
if let Some(bg) = self.bg {
|
||||
bg.stylize_debug(ColorDebugKind::Background).fmt(f)?;
|
||||
}
|
||||
#[cfg(feature = "underline-color")]
|
||||
if let Some(underline_color) = self.underline_color {
|
||||
underline_color
|
||||
.stylize_debug(ColorDebugKind::Underline)
|
||||
.fmt(f)?;
|
||||
}
|
||||
for modifier in self.add_modifier.iter() {
|
||||
match modifier {
|
||||
Modifier::BOLD => f.write_str(".bold()")?,
|
||||
Modifier::DIM => f.write_str(".dim()")?,
|
||||
Modifier::ITALIC => f.write_str(".italic()")?,
|
||||
Modifier::UNDERLINED => f.write_str(".underlined()")?,
|
||||
Modifier::SLOW_BLINK => f.write_str(".slow_blink()")?,
|
||||
Modifier::RAPID_BLINK => f.write_str(".rapid_blink()")?,
|
||||
Modifier::REVERSED => f.write_str(".reversed()")?,
|
||||
Modifier::HIDDEN => f.write_str(".hidden()")?,
|
||||
Modifier::CROSSED_OUT => f.write_str(".crossed_out()")?,
|
||||
_ => f.write_fmt(format_args!(".add_modifier(Modifier::{modifier:?})"))?,
|
||||
}
|
||||
}
|
||||
for modifier in self.sub_modifier.iter() {
|
||||
match modifier {
|
||||
Modifier::BOLD => f.write_str(".not_bold()")?,
|
||||
Modifier::DIM => f.write_str(".not_dim()")?,
|
||||
Modifier::ITALIC => f.write_str(".not_italic()")?,
|
||||
Modifier::UNDERLINED => f.write_str(".not_underlined()")?,
|
||||
Modifier::SLOW_BLINK => f.write_str(".not_slow_blink()")?,
|
||||
Modifier::RAPID_BLINK => f.write_str(".not_rapid_blink()")?,
|
||||
Modifier::REVERSED => f.write_str(".not_reversed()")?,
|
||||
Modifier::HIDDEN => f.write_str(".not_hidden()")?,
|
||||
Modifier::CROSSED_OUT => f.write_str(".not_crossed_out()")?,
|
||||
_ => f.write_fmt(format_args!(".remove_modifier(Modifier::{modifier:?})"))?,
|
||||
}
|
||||
}
|
||||
self.fmt_stylize(f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -325,7 +306,8 @@ impl Style {
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::{Color, Style};
|
||||
///
|
||||
/// let style = Style::default().fg(Color::Blue);
|
||||
/// let diff = Style::default().fg(Color::Red);
|
||||
/// assert_eq!(style.patch(diff), Style::default().fg(Color::Red));
|
||||
@@ -341,7 +323,8 @@ impl Style {
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::{Color, Style};
|
||||
///
|
||||
/// let style = Style::default().bg(Color::Blue);
|
||||
/// let diff = Style::default().bg(Color::Red);
|
||||
/// assert_eq!(style.patch(diff), Style::default().bg(Color::Red));
|
||||
@@ -365,7 +348,8 @@ impl Style {
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::{Color, Modifier, Style};
|
||||
///
|
||||
/// let style = Style::default()
|
||||
/// .underline_color(Color::Blue)
|
||||
/// .add_modifier(Modifier::UNDERLINED);
|
||||
@@ -393,7 +377,8 @@ impl Style {
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::{Modifier, Style};
|
||||
///
|
||||
/// let style = Style::default().add_modifier(Modifier::BOLD);
|
||||
/// let diff = Style::default().add_modifier(Modifier::ITALIC);
|
||||
/// let patched = style.patch(diff);
|
||||
@@ -414,7 +399,8 @@ impl Style {
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::{Modifier, Style};
|
||||
///
|
||||
/// let style = Style::default().add_modifier(Modifier::BOLD | Modifier::ITALIC);
|
||||
/// let diff = Style::default().remove_modifier(Modifier::ITALIC);
|
||||
/// let patched = style.patch(diff);
|
||||
@@ -436,7 +422,8 @@ impl Style {
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::{Color, Modifier, Style};
|
||||
///
|
||||
/// let style_1 = Style::default().fg(Color::Yellow);
|
||||
/// let style_2 = Style::default().bg(Color::Red);
|
||||
/// let combined = style_1.patch(style_2);
|
||||
@@ -463,6 +450,54 @@ impl Style {
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Formats the style in a way that can be copy-pasted into code using the style shorthands.
|
||||
///
|
||||
/// This is useful for debugging and for generating code snippets.
|
||||
pub(crate) fn fmt_stylize(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use fmt::Debug;
|
||||
if let Some(fg) = self.fg {
|
||||
fg.stylize_debug(ColorDebugKind::Foreground).fmt(f)?;
|
||||
}
|
||||
if let Some(bg) = self.bg {
|
||||
bg.stylize_debug(ColorDebugKind::Background).fmt(f)?;
|
||||
}
|
||||
#[cfg(feature = "underline-color")]
|
||||
if let Some(underline_color) = self.underline_color {
|
||||
underline_color
|
||||
.stylize_debug(ColorDebugKind::Underline)
|
||||
.fmt(f)?;
|
||||
}
|
||||
for modifier in self.add_modifier.iter() {
|
||||
match modifier {
|
||||
Modifier::BOLD => f.write_str(".bold()")?,
|
||||
Modifier::DIM => f.write_str(".dim()")?,
|
||||
Modifier::ITALIC => f.write_str(".italic()")?,
|
||||
Modifier::UNDERLINED => f.write_str(".underlined()")?,
|
||||
Modifier::SLOW_BLINK => f.write_str(".slow_blink()")?,
|
||||
Modifier::RAPID_BLINK => f.write_str(".rapid_blink()")?,
|
||||
Modifier::REVERSED => f.write_str(".reversed()")?,
|
||||
Modifier::HIDDEN => f.write_str(".hidden()")?,
|
||||
Modifier::CROSSED_OUT => f.write_str(".crossed_out()")?,
|
||||
_ => f.write_fmt(format_args!(".add_modifier(Modifier::{modifier:?})"))?,
|
||||
}
|
||||
}
|
||||
for modifier in self.sub_modifier.iter() {
|
||||
match modifier {
|
||||
Modifier::BOLD => f.write_str(".not_bold()")?,
|
||||
Modifier::DIM => f.write_str(".not_dim()")?,
|
||||
Modifier::ITALIC => f.write_str(".not_italic()")?,
|
||||
Modifier::UNDERLINED => f.write_str(".not_underlined()")?,
|
||||
Modifier::SLOW_BLINK => f.write_str(".not_slow_blink()")?,
|
||||
Modifier::RAPID_BLINK => f.write_str(".not_rapid_blink()")?,
|
||||
Modifier::REVERSED => f.write_str(".not_reversed()")?,
|
||||
Modifier::HIDDEN => f.write_str(".not_hidden()")?,
|
||||
Modifier::CROSSED_OUT => f.write_str(".not_crossed_out()")?,
|
||||
_ => f.write_fmt(format_args!(".remove_modifier(Modifier::{modifier:?})"))?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for Style {
|
||||
@@ -473,7 +508,8 @@ impl From<Color> for Style {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::{Color, Style};
|
||||
///
|
||||
/// let style = Style::from(Color::Red);
|
||||
/// ```
|
||||
fn from(color: Color) -> Self {
|
||||
@@ -487,7 +523,8 @@ impl From<(Color, Color)> for Style {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::{Color, Style};
|
||||
///
|
||||
/// // red foreground, blue background
|
||||
/// let style = Style::from((Color::Red, Color::Blue));
|
||||
/// // default foreground, blue background
|
||||
@@ -509,7 +546,8 @@ impl From<Modifier> for Style {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::{Style, Modifier};
|
||||
///
|
||||
/// // add bold and italic
|
||||
/// let style = Style::from(Modifier::BOLD|Modifier::ITALIC);
|
||||
fn from(modifier: Modifier) -> Self {
|
||||
@@ -523,7 +561,8 @@ impl From<(Modifier, Modifier)> for Style {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::{Modifier, Style};
|
||||
///
|
||||
/// // add bold and italic, remove dim
|
||||
/// let style = Style::from((Modifier::BOLD | Modifier::ITALIC, Modifier::DIM));
|
||||
/// ```
|
||||
@@ -542,7 +581,8 @@ impl From<(Color, Modifier)> for Style {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::{Color, Modifier, Style};
|
||||
///
|
||||
/// // red foreground, add bold and italic
|
||||
/// let style = Style::from((Color::Red, Modifier::BOLD | Modifier::ITALIC));
|
||||
/// ```
|
||||
@@ -559,7 +599,8 @@ impl From<(Color, Color, Modifier)> for Style {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::{Color, Modifier, Style};
|
||||
///
|
||||
/// // red foreground, blue background, add bold and italic
|
||||
/// let style = Style::from((Color::Red, Color::Blue, Modifier::BOLD | Modifier::ITALIC));
|
||||
/// ```
|
||||
@@ -575,7 +616,8 @@ impl From<(Color, Color, Modifier, Modifier)> for Style {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::{Color, Modifier, Style};
|
||||
///
|
||||
/// // red foreground, blue background, add bold and italic, remove dim
|
||||
/// let style = Style::from((
|
||||
/// Color::Red,
|
||||
@@ -605,6 +647,10 @@ mod tests {
|
||||
#[case(Style::new().on_blue(), "Style::new().on_blue()")]
|
||||
#[case(Style::new().bold(), "Style::new().bold()")]
|
||||
#[case(Style::new().not_italic(), "Style::new().not_italic()")]
|
||||
#[case(
|
||||
Style::new().red().on_blue().bold().italic().not_dim().not_hidden(),
|
||||
"Style::new().red().on_blue().bold().italic().not_dim().not_hidden()"
|
||||
)]
|
||||
fn debug(#[case] style: Style, #[case] expected: &'static str) {
|
||||
assert_eq!(format!("{style:?}"), expected);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ use crate::style::stylize::{ColorDebug, ColorDebugKind};
|
||||
/// ```
|
||||
/// use std::str::FromStr;
|
||||
///
|
||||
/// use ratatui::prelude::*;
|
||||
/// use ratatui::style::Color;
|
||||
///
|
||||
/// assert_eq!(Color::from_str("red"), Ok(Color::Red));
|
||||
/// assert_eq!("red".parse(), Ok(Color::Red));
|
||||
@@ -168,7 +168,9 @@ impl<'de> serde::Deserialize<'de> for Color {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::prelude::*;
|
||||
/// use std::str::FromStr;
|
||||
///
|
||||
/// use ratatui::style::Color;
|
||||
///
|
||||
/// #[derive(Debug, serde::Deserialize)]
|
||||
/// struct Theme {
|
||||
@@ -263,7 +265,7 @@ impl std::error::Error for ParseColorError {}
|
||||
/// ```
|
||||
/// use std::str::FromStr;
|
||||
///
|
||||
/// use ratatui::prelude::*;
|
||||
/// use ratatui::style::Color;
|
||||
///
|
||||
/// let color: Color = Color::from_str("blue").unwrap();
|
||||
/// assert_eq!(color, Color::Blue);
|
||||
@@ -379,7 +381,7 @@ impl Color {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::prelude::*;
|
||||
/// use ratatui::style::Color;
|
||||
///
|
||||
/// let color: Color = Color::from_hsl(360.0, 100.0, 100.0);
|
||||
/// assert_eq!(color, Color::Rgb(255, 255, 255));
|
||||
@@ -396,6 +398,41 @@ impl Color {
|
||||
// Delegate to the function for normalized HSL to RGB conversion
|
||||
normalized_hsl_to_rgb(h / 360.0, s / 100.0, l / 100.0)
|
||||
}
|
||||
|
||||
/// Converts a `HSLuv` representation to a `Color::Rgb` instance.
|
||||
///
|
||||
/// The `from_hsluv` function converts the Hue, Saturation and Lightness values to a
|
||||
/// corresponding `Color` RGB equivalent.
|
||||
///
|
||||
/// Hue values should be in the range [0, 360].
|
||||
/// Saturation and L values should be in the range [0, 100].
|
||||
/// Values that are not in the range are clamped to be within the range.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::prelude::*;
|
||||
///
|
||||
/// let color = Color::from_hsluv(360.0, 50.0, 75.0);
|
||||
/// assert_eq!(color, Color::Rgb(223, 171, 181));
|
||||
///
|
||||
/// let color: Color = Color::from_hsluv(0.0, 0.0, 0.0);
|
||||
/// assert_eq!(color, Color::Rgb(0, 0, 0));
|
||||
/// ```
|
||||
#[cfg(feature = "palette")]
|
||||
pub fn from_hsluv(h: f64, s: f64, l: f64) -> Self {
|
||||
use palette::{Clamp, FromColor, Hsluv, Srgb};
|
||||
|
||||
let hsluv = Hsluv::new(h, s, l).clamp();
|
||||
let Srgb {
|
||||
red,
|
||||
green,
|
||||
blue,
|
||||
standard: _,
|
||||
}: Srgb<u8> = Srgb::from_color(hsluv).into();
|
||||
|
||||
Self::Rgb(red, green, blue)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts normalized HSL (Hue, Saturation, Lightness) values to RGB (Red, Green, Blue) color
|
||||
@@ -511,6 +548,34 @@ mod tests {
|
||||
assert_eq!(color, Color::Rgb(0, 0, 0));
|
||||
}
|
||||
|
||||
#[cfg(feature = "palette")]
|
||||
#[test]
|
||||
fn test_hsluv_to_rgb() {
|
||||
// Test with valid HSLuv values
|
||||
let color = Color::from_hsluv(120.0, 50.0, 75.0);
|
||||
assert_eq!(color, Color::Rgb(147, 198, 129));
|
||||
|
||||
// Test with H value at upper bound
|
||||
let color = Color::from_hsluv(360.0, 50.0, 75.0);
|
||||
assert_eq!(color, Color::Rgb(223, 171, 181));
|
||||
|
||||
// Test with H value exceeding the upper bound
|
||||
let color = Color::from_hsluv(400.0, 50.0, 75.0);
|
||||
assert_eq!(color, Color::Rgb(226, 174, 140));
|
||||
|
||||
// Test with S and L values exceeding the upper bound
|
||||
let color = Color::from_hsluv(240.0, 120.0, 150.0);
|
||||
assert_eq!(color, Color::Rgb(255, 255, 255));
|
||||
|
||||
// Test with H, S, and L values below the lower bound
|
||||
let color = Color::from_hsluv(0.0, 0.0, 0.0);
|
||||
assert_eq!(color, Color::Rgb(0, 0, 0));
|
||||
|
||||
// Test with S and L values below the lower bound
|
||||
let color = Color::from_hsluv(60.0, 0.0, 0.0);
|
||||
assert_eq!(color, Color::Rgb(0, 0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_u32() {
|
||||
assert_eq!(Color::from_u32(0x000000), Color::Rgb(0, 0, 0));
|
||||
|
||||
@@ -403,8 +403,10 @@
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use ratatui::prelude::*;
|
||||
//! use ratatui::style::palette::material::{BLUE, RED};
|
||||
//! use ratatui::style::{
|
||||
//! palette::material::{BLUE, RED},
|
||||
//! Color,
|
||||
//! };
|
||||
//!
|
||||
//! assert_eq!(RED.c500, Color::Rgb(244, 67, 54));
|
||||
//! assert_eq!(BLUE.c500, Color::Rgb(33, 150, 243));
|
||||
@@ -412,7 +414,7 @@
|
||||
//!
|
||||
//! [`matdesign-color` crate]: https://crates.io/crates/matdesign-color
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::style::Color;
|
||||
|
||||
/// A palette of colors for use in Material design with accent colors
|
||||
///
|
||||
|
||||
@@ -268,14 +268,16 @@
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use ratatui::prelude::*;
|
||||
//! use ratatui::style::palette::tailwind::{BLUE, RED};
|
||||
//! use ratatui::style::{
|
||||
//! palette::tailwind::{BLUE, RED},
|
||||
//! Color,
|
||||
//! };
|
||||
//!
|
||||
//! assert_eq!(RED.c500, Color::Rgb(239, 68, 68));
|
||||
//! assert_eq!(BLUE.c500, Color::Rgb(59, 130, 246));
|
||||
//! ```
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::style::Color;
|
||||
|
||||
pub struct Palette {
|
||||
pub c50: Color,
|
||||
|
||||
@@ -7,7 +7,7 @@ use ::palette::{
|
||||
};
|
||||
use palette::{stimulus::IntoStimulus, Srgb};
|
||||
|
||||
use super::Color;
|
||||
use crate::style::Color;
|
||||
|
||||
/// Convert an [`palette::Srgb`] color to a [`Color`].
|
||||
///
|
||||
|
||||
@@ -196,7 +196,11 @@ macro_rules! modifier {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Color, Modifier, Style, Stylize},
|
||||
/// text::Line,
|
||||
/// widgets::{Block, Paragraph},
|
||||
/// };
|
||||
///
|
||||
/// let span = "hello".red().on_blue().bold();
|
||||
/// let line = Line::from(vec![
|
||||
|
||||
@@ -155,7 +155,7 @@ pub enum Marker {
|
||||
}
|
||||
|
||||
pub mod scrollbar {
|
||||
use super::{block, line};
|
||||
use crate::symbols::{block, line};
|
||||
|
||||
/// Scrollbar Set
|
||||
/// ```text
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{block, line};
|
||||
use crate::symbols::{block, line};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub struct Set {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
//! ```rust,no_run
|
||||
//! use std::io::stdout;
|
||||
//!
|
||||
//! use ratatui::{prelude::*, widgets::Paragraph};
|
||||
//! use ratatui::{backend::CrosstermBackend, widgets::Paragraph, Terminal};
|
||||
//!
|
||||
//! let backend = CrosstermBackend::new(stdout());
|
||||
//! let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::prelude::*;
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::{Position, Rect},
|
||||
widgets::{StatefulWidget, StatefulWidgetRef, Widget, WidgetRef},
|
||||
};
|
||||
|
||||
/// A consistent view into the terminal state for rendering a single frame.
|
||||
///
|
||||
@@ -10,6 +14,7 @@ use crate::prelude::*;
|
||||
/// to the terminal. This avoids drawing redundant cells.
|
||||
///
|
||||
/// [`Buffer`]: crate::buffer::Buffer
|
||||
/// [`Terminal::draw`]: crate::Terminal::draw
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct Frame<'a> {
|
||||
/// Where should the cursor be after drawing this frame?
|
||||
@@ -31,6 +36,8 @@ pub struct Frame<'a> {
|
||||
/// `CompletedFrame` represents the state of the terminal after all changes performed in the last
|
||||
/// [`Terminal::draw`] call have been applied. Therefore, it is only valid until the next call to
|
||||
/// [`Terminal::draw`].
|
||||
///
|
||||
/// [`Terminal::draw`]: crate::Terminal::draw
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct CompletedFrame<'a> {
|
||||
/// The buffer that was used to draw the last frame.
|
||||
@@ -73,10 +80,12 @@ impl Frame<'_> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::Block};
|
||||
/// # use ratatui::{backend::TestBackend, Terminal};
|
||||
/// # let backend = TestBackend::new(5, 5);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
/// # let mut frame = terminal.get_frame();
|
||||
/// use ratatui::{layout::Rect, widgets::Block};
|
||||
///
|
||||
/// let block = Block::new();
|
||||
/// let area = Rect::new(0, 0, 5, 5);
|
||||
/// frame.render_widget(block, area);
|
||||
@@ -96,10 +105,12 @@ impl Frame<'_> {
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "unstable-widget-ref")] {
|
||||
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::Block};
|
||||
/// # use ratatui::{backend::TestBackend, Terminal};
|
||||
/// # let backend = TestBackend::new(5, 5);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
/// # let mut frame = terminal.get_frame();
|
||||
/// use ratatui::{layout::Rect, widgets::Block};
|
||||
///
|
||||
/// let block = Block::new();
|
||||
/// let area = Rect::new(0, 0, 5, 5);
|
||||
/// frame.render_widget_ref(block, area);
|
||||
@@ -122,10 +133,15 @@ impl Frame<'_> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::*};
|
||||
/// # use ratatui::{backend::TestBackend, Terminal};
|
||||
/// # let backend = TestBackend::new(5, 5);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
/// # let mut frame = terminal.get_frame();
|
||||
/// use ratatui::{
|
||||
/// layout::Rect,
|
||||
/// widgets::{List, ListItem, ListState},
|
||||
/// };
|
||||
///
|
||||
/// let mut state = ListState::default().with_selected(Some(1));
|
||||
/// let list = List::new(vec![ListItem::new("Item 1"), ListItem::new("Item 2")]);
|
||||
/// let area = Rect::new(0, 0, 5, 5);
|
||||
@@ -153,10 +169,15 @@ impl Frame<'_> {
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "unstable-widget-ref")] {
|
||||
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::*};
|
||||
/// # use ratatui::{backend::TestBackend, Terminal};
|
||||
/// # let backend = TestBackend::new(5, 5);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
/// # let mut frame = terminal.get_frame();
|
||||
/// use ratatui::{
|
||||
/// layout::Rect,
|
||||
/// widgets::{List, ListItem, ListState},
|
||||
/// };
|
||||
///
|
||||
/// let mut state = ListState::default().with_selected(Some(1));
|
||||
/// let list = List::new(vec![ListItem::new("Item 1"), ListItem::new("Item 2")]);
|
||||
/// let area = Rect::new(0, 0, 5, 5);
|
||||
@@ -178,6 +199,10 @@ impl Frame<'_> {
|
||||
/// Note that this will interfere with calls to [`Terminal::hide_cursor`],
|
||||
/// [`Terminal::show_cursor`], and [`Terminal::set_cursor_position`]. Pick one of the APIs and
|
||||
/// stick with it.
|
||||
///
|
||||
/// [`Terminal::hide_cursor`]: crate::Terminal::hide_cursor
|
||||
/// [`Terminal::show_cursor`]: crate::Terminal::show_cursor
|
||||
/// [`Terminal::set_cursor_position`]: crate::Terminal::set_cursor_position
|
||||
pub fn set_cursor_position<P: Into<Position>>(&mut self, position: P) {
|
||||
self.cursor_position = Some(position.into());
|
||||
}
|
||||
@@ -188,6 +213,10 @@ impl Frame<'_> {
|
||||
/// Note that this will interfere with calls to [`Terminal::hide_cursor`],
|
||||
/// [`Terminal::show_cursor`], and [`Terminal::set_cursor_position`]. Pick one of the APIs and
|
||||
/// stick with it.
|
||||
///
|
||||
/// [`Terminal::hide_cursor`]: crate::Terminal::hide_cursor
|
||||
/// [`Terminal::show_cursor`]: crate::Terminal::show_cursor
|
||||
/// [`Terminal::set_cursor_position`]: crate::Terminal::set_cursor_position
|
||||
#[deprecated = "the method set_cursor_position indicates more clearly what about the cursor to set"]
|
||||
pub fn set_cursor(&mut self, x: u16, y: u16) {
|
||||
self.set_cursor_position(Position { x, y });
|
||||
@@ -215,7 +244,7 @@ impl Frame<'_> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::*};
|
||||
/// # use ratatui::{backend::TestBackend, Terminal};
|
||||
/// # let backend = TestBackend::new(5, 5);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
/// # let mut frame = terminal.get_frame();
|
||||
|
||||
@@ -5,8 +5,7 @@ use crossterm::{
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
|
||||
use super::TerminalOptions;
|
||||
use crate::{backend::CrosstermBackend, Terminal};
|
||||
use crate::{backend::CrosstermBackend, terminal::TerminalOptions, Terminal};
|
||||
|
||||
/// A type alias for the default terminal type.
|
||||
///
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::io;
|
||||
|
||||
use crate::{
|
||||
backend::ClearType, buffer::Cell, prelude::*, CompletedFrame, TerminalOptions, Viewport,
|
||||
backend::{Backend, ClearType},
|
||||
buffer::{Buffer, Cell},
|
||||
layout::{Position, Rect, Size},
|
||||
CompletedFrame, Frame, TerminalOptions, Viewport,
|
||||
};
|
||||
|
||||
/// An interface to interact and draw [`Frame`]s on the user's terminal.
|
||||
@@ -30,10 +33,9 @@ use crate::{
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use std::io::stdout;
|
||||
///
|
||||
/// use ratatui::widgets::Paragraph;
|
||||
/// use ratatui::{backend::CrosstermBackend, widgets::Paragraph, Terminal};
|
||||
///
|
||||
/// let backend = CrosstermBackend::new(stdout());
|
||||
/// let mut terminal = Terminal::new(backend)?;
|
||||
@@ -107,8 +109,10 @@ where
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use std::io::stdout;
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use std::io::stdout;
|
||||
///
|
||||
/// use ratatui::{backend::CrosstermBackend, Terminal};
|
||||
///
|
||||
/// let backend = CrosstermBackend::new(stdout());
|
||||
/// let terminal = Terminal::new(backend)?;
|
||||
/// # std::io::Result::Ok(())
|
||||
@@ -127,8 +131,10 @@ where
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::io::stdout;
|
||||
/// # use ratatui::{prelude::*, backend::TestBackend, Viewport, TerminalOptions};
|
||||
/// use std::io::stdout;
|
||||
///
|
||||
/// use ratatui::{backend::CrosstermBackend, layout::Rect, Terminal, TerminalOptions, Viewport};
|
||||
///
|
||||
/// let backend = CrosstermBackend::new(stdout());
|
||||
/// let viewport = Viewport::Fixed(Rect::new(0, 0, 10, 10));
|
||||
/// let terminal = Terminal::with_options(backend, TerminalOptions { viewport })?;
|
||||
@@ -276,10 +282,9 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::layout::Position;
|
||||
/// # let backend = ratatui::backend::TestBackend::new(10, 10);
|
||||
/// # let mut terminal = ratatui::Terminal::new(backend)?;
|
||||
/// use ratatui::widgets::Paragraph;
|
||||
/// use ratatui::{layout::Position, widgets::Paragraph};
|
||||
///
|
||||
/// // with a closure
|
||||
/// terminal.draw(|frame| {
|
||||
@@ -552,7 +557,13 @@ where
|
||||
/// ## Insert a single line before the current viewport
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// backend::TestBackend,
|
||||
/// style::{Color, Style},
|
||||
/// text::{Line, Span},
|
||||
/// widgets::{Paragraph, Widget},
|
||||
/// Terminal,
|
||||
/// };
|
||||
/// # let backend = TestBackend::new(10, 10);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
/// terminal.insert_before(1, |buf| {
|
||||
@@ -568,10 +579,22 @@ where
|
||||
where
|
||||
F: FnOnce(&mut Buffer),
|
||||
{
|
||||
if !matches!(self.viewport, Viewport::Inline(_)) {
|
||||
return Ok(());
|
||||
match self.viewport {
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
Viewport::Inline(_) => self.insert_before_scrolling_regions(height, draw_fn),
|
||||
#[cfg(not(feature = "scrolling-regions"))]
|
||||
Viewport::Inline(_) => self.insert_before_no_scrolling_regions(height, draw_fn),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement `Self::insert_before` using standard backend capabilities.
|
||||
#[cfg(not(feature = "scrolling-regions"))]
|
||||
fn insert_before_no_scrolling_regions(
|
||||
&mut self,
|
||||
height: u16,
|
||||
draw_fn: impl FnOnce(&mut Buffer),
|
||||
) -> io::Result<()> {
|
||||
// The approach of this function is to first render all of the lines to insert into a
|
||||
// temporary buffer, and then to loop drawing chunks from the buffer to the screen. drawing
|
||||
// this buffer onto the screen.
|
||||
@@ -657,6 +680,86 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Implement `Self::insert_before` using scrolling regions.
|
||||
///
|
||||
/// If a terminal supports scrolling regions, it means that we can define a subset of rows of
|
||||
/// the screen, and then tell the terminal to scroll up or down just within that region. The
|
||||
/// rows outside of the region are not affected.
|
||||
///
|
||||
/// This function utilizes this feature to avoid having to redraw the viewport. This is done
|
||||
/// either by splitting the screen at the top of the viewport, and then creating a gap by
|
||||
/// either scrolling the viewport down, or scrolling the area above it up. The lines to insert
|
||||
/// are then drawn into the gap created.
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn insert_before_scrolling_regions(
|
||||
&mut self,
|
||||
mut height: u16,
|
||||
draw_fn: impl FnOnce(&mut Buffer),
|
||||
) -> io::Result<()> {
|
||||
// The approach of this function is to first render all of the lines to insert into a
|
||||
// temporary buffer, and then to loop drawing chunks from the buffer to the screen. drawing
|
||||
// this buffer onto the screen.
|
||||
let area = Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: self.viewport_area.width,
|
||||
height,
|
||||
};
|
||||
let mut buffer = Buffer::empty(area);
|
||||
draw_fn(&mut buffer);
|
||||
let mut buffer = buffer.content.as_slice();
|
||||
|
||||
// Handle the special case where the viewport takes up the whole screen.
|
||||
if self.viewport_area.height == self.last_known_area.height {
|
||||
// "Borrow" the top line of the viewport. Draw over it, then immediately scroll it into
|
||||
// scrollback. Do this repeatedly until the whole buffer has been put into scrollback.
|
||||
let mut first = true;
|
||||
while !buffer.is_empty() {
|
||||
buffer = if first {
|
||||
self.draw_lines(0, 1, buffer)?
|
||||
} else {
|
||||
self.draw_lines_over_cleared(0, 1, buffer)?
|
||||
};
|
||||
first = false;
|
||||
self.backend.scroll_region_up(0..1, 1)?;
|
||||
}
|
||||
|
||||
// Redraw the top line of the viewport.
|
||||
let width = self.viewport_area.width as usize;
|
||||
let top_line = self.buffers[1 - self.current].content[0..width].to_vec();
|
||||
self.draw_lines_over_cleared(0, 1, &top_line)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Handle the case where the viewport isn't yet at the bottom of the screen.
|
||||
{
|
||||
let viewport_top = self.viewport_area.top();
|
||||
let viewport_bottom = self.viewport_area.bottom();
|
||||
let screen_bottom = self.last_known_area.bottom();
|
||||
if viewport_bottom < screen_bottom {
|
||||
let to_draw = height.min(screen_bottom - viewport_bottom);
|
||||
self.backend
|
||||
.scroll_region_down(viewport_top..viewport_bottom + to_draw, to_draw)?;
|
||||
buffer = self.draw_lines_over_cleared(viewport_top, to_draw, buffer)?;
|
||||
self.set_viewport_area(Rect {
|
||||
y: viewport_top + to_draw,
|
||||
..self.viewport_area
|
||||
});
|
||||
height -= to_draw;
|
||||
}
|
||||
}
|
||||
|
||||
let viewport_top = self.viewport_area.top();
|
||||
while height > 0 {
|
||||
let to_draw = height.min(viewport_top);
|
||||
self.backend.scroll_region_up(0..viewport_top, to_draw)?;
|
||||
buffer = self.draw_lines_over_cleared(viewport_top - to_draw, to_draw, buffer)?;
|
||||
height -= to_draw;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Draw lines at the given vertical offset. The slice of cells must contain enough cells
|
||||
/// for the requested lines. A slice of the unused cells are returned.
|
||||
fn draw_lines<'a>(
|
||||
@@ -678,7 +781,33 @@ where
|
||||
Ok(remainder)
|
||||
}
|
||||
|
||||
/// Draw lines at the given vertical offset, assuming that the lines they are replacing on the
|
||||
/// screen are cleared. The slice of cells must contain enough cells for the requested lines. A
|
||||
/// slice of the unused cells are returned.
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn draw_lines_over_cleared<'a>(
|
||||
&mut self,
|
||||
y_offset: u16,
|
||||
lines_to_draw: u16,
|
||||
cells: &'a [Cell],
|
||||
) -> io::Result<&'a [Cell]> {
|
||||
let width: usize = self.last_known_area.width.into();
|
||||
let (to_draw, remainder) = cells.split_at(width * lines_to_draw as usize);
|
||||
if lines_to_draw > 0 {
|
||||
let area = Rect::new(0, y_offset, width as u16, y_offset + lines_to_draw);
|
||||
let old = Buffer::empty(area);
|
||||
let new = Buffer {
|
||||
area,
|
||||
content: to_draw.to_vec(),
|
||||
};
|
||||
self.backend.draw(old.diff(&new).into_iter())?;
|
||||
self.backend.flush()?;
|
||||
}
|
||||
Ok(remainder)
|
||||
}
|
||||
|
||||
/// Scroll the whole screen up by the given number of lines.
|
||||
#[cfg(not(feature = "scrolling-regions"))]
|
||||
fn scroll_up(&mut self, lines_to_scroll: u16) -> io::Result<()> {
|
||||
if lines_to_scroll > 0 {
|
||||
self.set_cursor_position(Position::new(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::layout::Rect;
|
||||
|
||||
/// Represents the viewport of the terminal. The viewport is the area of the terminal that is
|
||||
/// currently visible to the user. It can be either fullscreen, inline or fixed.
|
||||
@@ -14,6 +14,8 @@ use crate::prelude::*;
|
||||
/// by a [`Rect`].
|
||||
///
|
||||
/// See [`Terminal::with_options`] for more information.
|
||||
///
|
||||
/// [`Terminal::with_options`]: crate::Terminal::with_options
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum Viewport {
|
||||
/// The viewport is fullscreen
|
||||
|
||||
@@ -19,7 +19,11 @@
|
||||
//! its `title` property (which is a [`Line`] under the hood):
|
||||
//!
|
||||
//! ```rust
|
||||
//! use ratatui::{prelude::*, widgets::*};
|
||||
//! use ratatui::{
|
||||
//! style::{Color, Style},
|
||||
//! text::{Line, Span},
|
||||
//! widgets::Block,
|
||||
//! };
|
||||
//!
|
||||
//! // A simple string with no styling.
|
||||
//! // Converted to Line(vec![
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{prelude::*, style::Styled};
|
||||
use crate::style::{Style, Styled};
|
||||
|
||||
const NBSP: &str = "\u{00a0}";
|
||||
const ZWSP: &str = "\u{200b}";
|
||||
@@ -19,6 +19,8 @@ impl<'a> StyledGrapheme<'a> {
|
||||
///
|
||||
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
pub fn new<S: Into<Style>>(symbol: &'a str, style: S) -> Self {
|
||||
Self {
|
||||
symbol,
|
||||
@@ -48,6 +50,7 @@ impl<'a> Styled for StyledGrapheme<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::style::Stylize;
|
||||
|
||||
#[test]
|
||||
fn new() {
|
||||
|
||||
191
src/text/line.rs
191
src/text/line.rs
@@ -4,7 +4,13 @@ use std::{borrow::Cow, fmt};
|
||||
|
||||
use unicode_truncate::UnicodeTruncateStr;
|
||||
|
||||
use crate::{prelude::*, style::Styled, text::StyledGrapheme};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Rect},
|
||||
style::{Style, Styled},
|
||||
text::{Span, StyledGrapheme, Text},
|
||||
widgets::{Widget, WidgetRef},
|
||||
};
|
||||
|
||||
/// A line of text, consisting of one or more [`Span`]s.
|
||||
///
|
||||
@@ -69,7 +75,10 @@ use crate::{prelude::*, style::Styled, text::StyledGrapheme};
|
||||
/// [`Style`].
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Color, Modifier, Style, Stylize},
|
||||
/// text::{Line, Span},
|
||||
/// };
|
||||
///
|
||||
/// let style = Style::new().yellow();
|
||||
/// let line = Line::raw("Hello, world!").style(style);
|
||||
@@ -93,7 +102,11 @@ use crate::{prelude::*, style::Styled, text::StyledGrapheme};
|
||||
/// methods of the [`Stylize`] trait.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Color, Modifier, Style, Stylize},
|
||||
/// text::Line,
|
||||
/// };
|
||||
///
|
||||
/// let line = Line::from("Hello world!").style(Style::new().yellow().italic());
|
||||
/// let line = Line::from("Hello world!").style(Color::Yellow);
|
||||
/// let line = Line::from("Hello world!").style((Color::Yellow, Color::Black));
|
||||
@@ -108,7 +121,8 @@ use crate::{prelude::*, style::Styled, text::StyledGrapheme};
|
||||
/// ignored and the line is truncated.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{layout::Alignment, text::Line};
|
||||
///
|
||||
/// let line = Line::from("Hello world!").alignment(Alignment::Right);
|
||||
/// let line = Line::from("Hello world!").centered();
|
||||
/// let line = Line::from("Hello world!").left_aligned();
|
||||
@@ -120,7 +134,15 @@ use crate::{prelude::*, style::Styled, text::StyledGrapheme};
|
||||
/// `Line` implements the [`Widget`] trait, which means it can be rendered to a [`Buffer`].
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Line,
|
||||
/// widgets::Widget,
|
||||
/// Frame,
|
||||
/// };
|
||||
///
|
||||
/// # fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// // in another widget's render method
|
||||
/// let line = Line::from("Hello world!").style(Style::new().yellow().italic());
|
||||
@@ -139,7 +161,14 @@ use crate::{prelude::*, style::Styled, text::StyledGrapheme};
|
||||
/// provides more functionality.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// style::Stylize,
|
||||
/// text::Line,
|
||||
/// widgets::{Paragraph, Widget, Wrap},
|
||||
/// };
|
||||
///
|
||||
/// # fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// let line = Line::from("Hello world!").yellow().italic();
|
||||
/// Paragraph::new(line)
|
||||
@@ -149,6 +178,7 @@ use crate::{prelude::*, style::Styled, text::StyledGrapheme};
|
||||
/// ```
|
||||
///
|
||||
/// [`Paragraph`]: crate::widgets::Paragraph
|
||||
/// [`Stylize`]: crate::style::Stylize
|
||||
#[derive(Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Line<'a> {
|
||||
/// The style of this line of text.
|
||||
@@ -163,18 +193,28 @@ pub struct Line<'a> {
|
||||
|
||||
impl fmt::Debug for Line<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.style == Style::default() && self.alignment.is_none() {
|
||||
f.write_str("Line ")?;
|
||||
return f.debug_list().entries(&self.spans).finish();
|
||||
if self.spans.is_empty() {
|
||||
f.write_str("Line::default()")?;
|
||||
} else if self.spans.len() == 1 && self.spans[0].style == Style::default() {
|
||||
f.write_str(r#"Line::from(""#)?;
|
||||
f.write_str(&self.spans[0].content)?;
|
||||
f.write_str(r#"")"#)?;
|
||||
} else if self.spans.len() == 1 {
|
||||
f.write_str("Line::from(")?;
|
||||
self.spans[0].fmt(f)?;
|
||||
f.write_str(")")?;
|
||||
} else {
|
||||
f.write_str("Line::from_iter(")?;
|
||||
f.debug_list().entries(&self.spans).finish()?;
|
||||
f.write_str(")")?;
|
||||
}
|
||||
let mut debug = f.debug_struct("Line");
|
||||
if self.style != Style::default() {
|
||||
debug.field("style", &self.style);
|
||||
self.style.fmt_stylize(f)?;
|
||||
match self.alignment {
|
||||
Some(Alignment::Left) => write!(f, ".left_aligned()"),
|
||||
Some(Alignment::Center) => write!(f, ".centered()"),
|
||||
Some(Alignment::Right) => write!(f, ".right_aligned()"),
|
||||
None => Ok(()),
|
||||
}
|
||||
if let Some(alignment) = self.alignment {
|
||||
debug.field("alignment", &format!("Alignment::{alignment}"));
|
||||
}
|
||||
debug.field("spans", &self.spans).finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,8 +239,10 @@ impl<'a> Line<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// # use std::borrow::Cow;
|
||||
/// use std::borrow::Cow;
|
||||
///
|
||||
/// use ratatui::text::Line;
|
||||
///
|
||||
/// Line::raw("test content");
|
||||
/// Line::raw(String::from("test content"));
|
||||
/// Line::raw(Cow::from("test content"));
|
||||
@@ -228,13 +270,20 @@ impl<'a> Line<'a> {
|
||||
/// Any newlines in the content are removed.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// # use std::borrow::Cow;
|
||||
/// use std::borrow::Cow;
|
||||
///
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Line,
|
||||
/// };
|
||||
///
|
||||
/// let style = Style::new().yellow().italic();
|
||||
/// Line::styled("My text", style);
|
||||
/// Line::styled(String::from("My text"), style);
|
||||
/// Line::styled(Cow::from("test content"), style);
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
pub fn styled<T, S>(content: T, style: S) -> Self
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
@@ -255,7 +304,8 @@ impl<'a> Line<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{style::Stylize, text::Line};
|
||||
///
|
||||
/// let line = Line::default().spans(vec!["Hello".blue(), " world!".green()]);
|
||||
/// let line = Line::default().spans([1, 2, 3].iter().map(|i| format!("Item {}", i)));
|
||||
/// ```
|
||||
@@ -282,9 +332,15 @@ impl<'a> Line<'a> {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Line,
|
||||
/// };
|
||||
///
|
||||
/// let mut line = Line::from("foo").style(Style::new().red());
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
@@ -300,7 +356,8 @@ impl<'a> Line<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{layout::Alignment, text::Line};
|
||||
///
|
||||
/// let mut line = Line::from("Hi, what's up?");
|
||||
/// assert_eq!(None, line.alignment);
|
||||
/// assert_eq!(
|
||||
@@ -325,7 +382,8 @@ impl<'a> Line<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::text::Line;
|
||||
///
|
||||
/// let line = Line::from("Hi, what's up?").left_aligned();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -342,7 +400,8 @@ impl<'a> Line<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::text::Line;
|
||||
///
|
||||
/// let line = Line::from("Hi, what's up?").centered();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -359,7 +418,8 @@ impl<'a> Line<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::text::Line;
|
||||
///
|
||||
/// let line = Line::from("Hi, what's up?").right_aligned();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -372,7 +432,8 @@ impl<'a> Line<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{style::Stylize, text::Line};
|
||||
///
|
||||
/// let line = Line::from(vec!["Hello".blue(), " world!".green()]);
|
||||
/// assert_eq!(12, line.width());
|
||||
/// ```
|
||||
@@ -393,7 +454,10 @@ impl<'a> Line<'a> {
|
||||
/// ```rust
|
||||
/// use std::iter::Iterator;
|
||||
///
|
||||
/// use ratatui::{prelude::*, text::StyledGrapheme};
|
||||
/// use ratatui::{
|
||||
/// style::{Color, Style},
|
||||
/// text::{Line, StyledGrapheme},
|
||||
/// };
|
||||
///
|
||||
/// let line = Line::styled("Text", Style::default().fg(Color::Yellow));
|
||||
/// let style = Style::default().fg(Color::Green).bg(Color::Black);
|
||||
@@ -408,6 +472,8 @@ impl<'a> Line<'a> {
|
||||
/// ]
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
pub fn styled_graphemes<S: Into<Style>>(
|
||||
&'a self,
|
||||
base_style: S,
|
||||
@@ -432,13 +498,19 @@ impl<'a> Line<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Color, Modifier},
|
||||
/// text::Line,
|
||||
/// };
|
||||
///
|
||||
/// let line = Line::styled("My text", Modifier::ITALIC);
|
||||
///
|
||||
/// let styled_line = Line::styled("My text", (Color::Yellow, Modifier::ITALIC));
|
||||
///
|
||||
/// assert_eq!(styled_line, line.patch_style(Color::Yellow));
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn patch_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = self.style.patch(style);
|
||||
@@ -454,8 +526,12 @@ impl<'a> Line<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// # let style = Style::default().yellow();
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Line,
|
||||
/// };
|
||||
///
|
||||
/// let line = Line::styled("My text", style);
|
||||
///
|
||||
/// assert_eq!(Style::reset(), line.reset_style().style);
|
||||
@@ -483,7 +559,8 @@ impl<'a> Line<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::text::{Line, Span};
|
||||
///
|
||||
/// let mut line = Line::from("Hello, ");
|
||||
/// line.push_span(Span::raw("world!"));
|
||||
/// line.push_span(" How are you?");
|
||||
@@ -532,6 +609,12 @@ impl<'a> From<&'a str> for Line<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Cow<'a, str>> for Line<'a> {
|
||||
fn from(s: Cow<'a, str>) -> Self {
|
||||
Self::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Vec<Span<'a>>> for Line<'a> {
|
||||
fn from(spans: Vec<Span<'a>>) -> Self {
|
||||
Self {
|
||||
@@ -749,6 +832,7 @@ mod tests {
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
use super::*;
|
||||
use crate::style::{Color, Modifier, Stylize};
|
||||
|
||||
#[fixture]
|
||||
fn small_buf() -> Buffer {
|
||||
@@ -1522,4 +1606,49 @@ mod tests {
|
||||
assert_eq!(result, "Hello world!");
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::empty(Line::default(), "Line::default()")]
|
||||
#[case::raw(Line::raw("Hello, world!"), r#"Line::from("Hello, world!")"#)]
|
||||
#[case::styled(
|
||||
Line::styled("Hello, world!", Color::Yellow),
|
||||
r#"Line::from("Hello, world!").yellow()"#
|
||||
)]
|
||||
#[case::styled_complex(
|
||||
Line::from(String::from("Hello, world!")).green().on_blue().bold().italic().not_dim(),
|
||||
r#"Line::from("Hello, world!").green().on_blue().bold().italic().not_dim()"#
|
||||
)]
|
||||
#[case::styled_span(
|
||||
Line::from(Span::styled("Hello, world!", Color::Yellow)),
|
||||
r#"Line::from(Span::from("Hello, world!").yellow())"#
|
||||
)]
|
||||
#[case::styled_line_and_span(
|
||||
Line::from(vec![
|
||||
Span::styled("Hello", Color::Yellow),
|
||||
Span::styled(" world!", Color::Green),
|
||||
]).italic(),
|
||||
r#"Line::from_iter([Span::from("Hello").yellow(), Span::from(" world!").green()]).italic()"#
|
||||
)]
|
||||
#[case::spans_vec(
|
||||
Line::from(vec![
|
||||
Span::styled("Hello", Color::Blue),
|
||||
Span::styled(" world!", Color::Green),
|
||||
]),
|
||||
r#"Line::from_iter([Span::from("Hello").blue(), Span::from(" world!").green()])"#,
|
||||
)]
|
||||
#[case::left_aligned(
|
||||
Line::from("Hello, world!").left_aligned(),
|
||||
r#"Line::from("Hello, world!").left_aligned()"#
|
||||
)]
|
||||
#[case::centered(
|
||||
Line::from("Hello, world!").centered(),
|
||||
r#"Line::from("Hello, world!").centered()"#
|
||||
)]
|
||||
#[case::right_aligned(
|
||||
Line::from("Hello, world!").right_aligned(),
|
||||
r#"Line::from("Hello, world!").right_aligned()"#
|
||||
)]
|
||||
fn debug(#[case] line: Line, #[case] expected: &str) {
|
||||
assert_eq!(format!("{line:?}"), expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{borrow::Cow, fmt};
|
||||
|
||||
use super::Text;
|
||||
use crate::text::Text;
|
||||
|
||||
/// A wrapper around a string that is masked when displayed.
|
||||
///
|
||||
@@ -10,7 +10,12 @@ use super::Text;
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// text::Masked,
|
||||
/// widgets::{Paragraph, Widget},
|
||||
/// };
|
||||
///
|
||||
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 1));
|
||||
/// let password = Masked::new("12345", 'x');
|
||||
|
||||
101
src/text/span.rs
101
src/text/span.rs
@@ -3,7 +3,13 @@ use std::{borrow::Cow, fmt};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{prelude::*, style::Styled, text::StyledGrapheme};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Style, Styled},
|
||||
text::{Line, StyledGrapheme},
|
||||
widgets::{Widget, WidgetRef},
|
||||
};
|
||||
|
||||
/// Represents a part of a line that is contiguous and where all characters share the same style.
|
||||
///
|
||||
@@ -36,7 +42,7 @@ use crate::{prelude::*, style::Styled, text::StyledGrapheme};
|
||||
/// any type convertible to [`Cow<str>`].
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::prelude::*;
|
||||
/// use ratatui::text::Span;
|
||||
///
|
||||
/// let span = Span::raw("test content");
|
||||
/// let span = Span::raw(String::from("test content"));
|
||||
@@ -50,7 +56,10 @@ use crate::{prelude::*, style::Styled, text::StyledGrapheme};
|
||||
/// the [`Stylize`] trait.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Span,
|
||||
/// };
|
||||
///
|
||||
/// let span = Span::styled("test content", Style::new().green());
|
||||
/// let span = Span::styled(String::from("test content"), Style::new().green());
|
||||
@@ -64,7 +73,7 @@ use crate::{prelude::*, style::Styled, text::StyledGrapheme};
|
||||
/// defined in the [`Stylize`] trait.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::prelude::*;
|
||||
/// use ratatui::{style::Stylize, text::Span};
|
||||
///
|
||||
/// let span = Span::raw("test content").green().on_yellow().italic();
|
||||
/// let span = Span::raw(String::from("test content"))
|
||||
@@ -78,7 +87,7 @@ use crate::{prelude::*, style::Styled, text::StyledGrapheme};
|
||||
/// wrapping and alignment for you.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::prelude::*;
|
||||
/// use ratatui::{style::Stylize, Frame};
|
||||
///
|
||||
/// # fn render_frame(frame: &mut Frame) {
|
||||
/// frame.render_widget("test content".green().on_yellow().italic(), frame.area());
|
||||
@@ -98,13 +107,15 @@ pub struct Span<'a> {
|
||||
|
||||
impl fmt::Debug for Span<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.style == Style::default() {
|
||||
return write!(f, "Span({:?})", self.content);
|
||||
if self.content.is_empty() {
|
||||
write!(f, "Span::default()")?;
|
||||
} else {
|
||||
write!(f, "Span::from({:?})", self.content)?;
|
||||
}
|
||||
f.debug_struct("Span")
|
||||
.field("style", &self.style)
|
||||
.field("content", &self.content)
|
||||
.finish()
|
||||
if self.style != Style::default() {
|
||||
self.style.fmt_stylize(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +125,8 @@ impl<'a> Span<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::text::Span;
|
||||
///
|
||||
/// Span::raw("test content");
|
||||
/// Span::raw(String::from("test content"));
|
||||
/// ```
|
||||
@@ -139,11 +151,17 @@ impl<'a> Span<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Span,
|
||||
/// };
|
||||
///
|
||||
/// let style = Style::new().yellow().on_green().italic();
|
||||
/// Span::styled("test content", style);
|
||||
/// Span::styled(String::from("test content"), style);
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
pub fn styled<T, S>(content: T, style: S) -> Self
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
@@ -165,7 +183,8 @@ impl<'a> Span<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::text::Span;
|
||||
///
|
||||
/// let mut span = Span::default().content("content");
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -190,9 +209,15 @@ impl<'a> Span<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Span,
|
||||
/// };
|
||||
///
|
||||
/// let mut span = Span::default().style(Style::new().green());
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
@@ -209,11 +234,17 @@ impl<'a> Span<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Span,
|
||||
/// };
|
||||
///
|
||||
/// let span = Span::styled("test content", Style::new().green().italic())
|
||||
/// .patch_style(Style::new().red().on_yellow().bold());
|
||||
/// assert_eq!(span.style, Style::new().red().on_yellow().italic().bold());
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn patch_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = self.style.patch(style);
|
||||
@@ -229,7 +260,11 @@ impl<'a> Span<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Span,
|
||||
/// };
|
||||
///
|
||||
/// let span = Span::styled(
|
||||
/// "Test Content",
|
||||
/// Style::new().dark_gray().on_yellow().italic(),
|
||||
@@ -260,7 +295,10 @@ impl<'a> Span<'a> {
|
||||
/// ```rust
|
||||
/// use std::iter::Iterator;
|
||||
///
|
||||
/// use ratatui::{prelude::*, text::StyledGrapheme};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::{Span, StyledGrapheme},
|
||||
/// };
|
||||
///
|
||||
/// let span = Span::styled("Test", Style::new().green().italic());
|
||||
/// let style = Style::new().red().on_yellow();
|
||||
@@ -275,6 +313,8 @@ impl<'a> Span<'a> {
|
||||
/// ],
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
pub fn styled_graphemes<S: Into<Style>>(
|
||||
&'a self,
|
||||
base_style: S,
|
||||
@@ -292,7 +332,8 @@ impl<'a> Span<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::Stylize;
|
||||
///
|
||||
/// let line = "Test Content".green().italic().into_left_aligned_line();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -311,7 +352,8 @@ impl<'a> Span<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::Stylize;
|
||||
///
|
||||
/// let line = "Test Content".green().italic().into_centered_line();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -330,7 +372,8 @@ impl<'a> Span<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::style::Stylize;
|
||||
///
|
||||
/// let line = "Test Content".green().italic().into_right_aligned_line();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -464,10 +507,10 @@ impl fmt::Display for Span<'_> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use buffer::Cell;
|
||||
use rstest::fixture;
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
use super::*;
|
||||
use crate::{buffer::Cell, layout::Alignment, style::Stylize};
|
||||
|
||||
#[fixture]
|
||||
fn small_buf() -> Buffer {
|
||||
@@ -843,4 +886,16 @@ mod tests {
|
||||
Line::from(vec![Span::raw("test"), Span::raw("content")])
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::default(Span::default(), "Span::default()")]
|
||||
#[case::raw(Span::raw("test"), r#"Span::from("test")"#)]
|
||||
#[case::styled(Span::styled("test", Style::new().green()), r#"Span::from("test").green()"#)]
|
||||
#[case::styled_italic(
|
||||
Span::styled("test", Style::new().green().italic()),
|
||||
r#"Span::from("test").green().italic()"#
|
||||
)]
|
||||
fn debug(#[case] span: Span, #[case] expected: &str) {
|
||||
assert_eq!(format!("{span:?}"), expected);
|
||||
}
|
||||
}
|
||||
|
||||
231
src/text/text.rs
231
src/text/text.rs
@@ -1,7 +1,13 @@
|
||||
#![warn(missing_docs)]
|
||||
use std::{borrow::Cow, fmt};
|
||||
|
||||
use crate::{prelude::*, style::Styled};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Rect},
|
||||
style::{Style, Styled},
|
||||
text::{Line, Span},
|
||||
widgets::{Widget, WidgetRef},
|
||||
};
|
||||
|
||||
/// A string split over one or more lines.
|
||||
///
|
||||
@@ -62,7 +68,10 @@ use crate::{prelude::*, style::Styled};
|
||||
/// ```rust
|
||||
/// use std::{borrow::Cow, iter};
|
||||
///
|
||||
/// use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Color, Modifier, Style, Stylize},
|
||||
/// text::{Line, Span, Text},
|
||||
/// };
|
||||
///
|
||||
/// let style = Style::new().yellow().italic();
|
||||
/// let text = Text::raw("The first line\nThe second line").style(style);
|
||||
@@ -99,7 +108,11 @@ use crate::{prelude::*, style::Styled};
|
||||
/// [`Stylize`] trait.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Color, Modifier, Style, Stylize},
|
||||
/// text::{Line, Text},
|
||||
/// };
|
||||
///
|
||||
/// let text = Text::from("The first line\nThe second line").style(Style::new().yellow().italic());
|
||||
/// let text = Text::from("The first line\nThe second line")
|
||||
/// .yellow()
|
||||
@@ -116,7 +129,11 @@ use crate::{prelude::*, style::Styled};
|
||||
/// Lines composing the text can also be individually aligned with [`Line::alignment`].
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// layout::Alignment,
|
||||
/// text::{Line, Text},
|
||||
/// };
|
||||
///
|
||||
/// let text = Text::from("The first line\nThe second line").alignment(Alignment::Right);
|
||||
/// let text = Text::from("The first line\nThe second line").right_aligned();
|
||||
/// let text = Text::from(vec![
|
||||
@@ -132,7 +149,9 @@ use crate::{prelude::*, style::Styled};
|
||||
/// [`Frame`].
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// # use ratatui::{buffer::Buffer, layout::Rect};
|
||||
/// use ratatui::{text::Text, widgets::Widget, Frame};
|
||||
///
|
||||
/// // within another widget's `render` method:
|
||||
/// # fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// let text = Text::from("The first line\nThe second line");
|
||||
@@ -152,7 +171,13 @@ use crate::{prelude::*, style::Styled};
|
||||
/// provides more functionality.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// text::Text,
|
||||
/// widgets::{Paragraph, Widget, Wrap},
|
||||
/// };
|
||||
///
|
||||
/// # fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// let text = Text::from("The first line\nThe second line");
|
||||
/// let paragraph = Paragraph::new(text)
|
||||
@@ -163,6 +188,8 @@ use crate::{prelude::*, style::Styled};
|
||||
/// ```
|
||||
///
|
||||
/// [`Paragraph`]: crate::widgets::Paragraph
|
||||
/// [`Stylize`]: crate::style::Stylize
|
||||
/// [`Frame`]: crate::Frame
|
||||
#[derive(Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Text<'a> {
|
||||
/// The alignment of this text.
|
||||
@@ -175,19 +202,23 @@ pub struct Text<'a> {
|
||||
|
||||
impl fmt::Debug for Text<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.style == Style::default() && self.alignment.is_none() {
|
||||
f.write_str("Text ")?;
|
||||
f.debug_list().entries(&self.lines).finish()
|
||||
if self.lines.is_empty() {
|
||||
f.write_str("Text::default()")?;
|
||||
} else if self.lines.len() == 1 {
|
||||
write!(f, "Text::from({:?})", self.lines[0])?;
|
||||
} else {
|
||||
let mut debug = f.debug_struct("Text");
|
||||
if self.style != Style::default() {
|
||||
debug.field("style", &self.style);
|
||||
}
|
||||
if let Some(alignment) = self.alignment {
|
||||
debug.field("alignment", &format!("Alignment::{alignment}"));
|
||||
}
|
||||
debug.field("lines", &self.lines).finish()
|
||||
f.write_str("Text::from_iter(")?;
|
||||
f.debug_list().entries(self.lines.iter()).finish()?;
|
||||
f.write_str(")")?;
|
||||
}
|
||||
self.style.fmt_stylize(f)?;
|
||||
match self.alignment {
|
||||
Some(Alignment::Left) => f.write_str(".left_aligned()")?,
|
||||
Some(Alignment::Center) => f.write_str(".centered()")?,
|
||||
Some(Alignment::Right) => f.write_str(".right_aligned()")?,
|
||||
_ => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +228,8 @@ impl<'a> Text<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::text::Text;
|
||||
///
|
||||
/// Text::raw("The first line\nThe second line");
|
||||
/// Text::raw(String::from("The first line\nThe second line"));
|
||||
/// ```
|
||||
@@ -222,13 +254,19 @@ impl<'a> Text<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Color, Modifier, Style},
|
||||
/// text::Text,
|
||||
/// };
|
||||
///
|
||||
/// let style = Style::default()
|
||||
/// .fg(Color::Yellow)
|
||||
/// .add_modifier(Modifier::ITALIC);
|
||||
/// Text::styled("The first line\nThe second line", style);
|
||||
/// Text::styled(String::from("The first line\nThe second line"), style);
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
pub fn styled<T, S>(content: T, style: S) -> Self
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
@@ -242,7 +280,8 @@ impl<'a> Text<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::text::Text;
|
||||
///
|
||||
/// let text = Text::from("The first line\nThe second line");
|
||||
/// assert_eq!(15, text.width());
|
||||
/// ```
|
||||
@@ -255,7 +294,8 @@ impl<'a> Text<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::text::Text;
|
||||
///
|
||||
/// let text = Text::from("The first line\nThe second line");
|
||||
/// assert_eq!(2, text.height());
|
||||
/// ```
|
||||
@@ -276,9 +316,15 @@ impl<'a> Text<'a> {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Text,
|
||||
/// };
|
||||
///
|
||||
/// let mut line = Text::from("foo").style(Style::new().red());
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
@@ -302,7 +348,11 @@ impl<'a> Text<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Color, Modifier},
|
||||
/// text::Text,
|
||||
/// };
|
||||
///
|
||||
/// let raw_text = Text::styled("The first line\nThe second line", Modifier::ITALIC);
|
||||
/// let styled_text = Text::styled(
|
||||
/// String::from("The first line\nThe second line"),
|
||||
@@ -313,6 +363,9 @@ impl<'a> Text<'a> {
|
||||
/// let raw_text = raw_text.patch_style(Color::Yellow);
|
||||
/// assert_eq!(raw_text, styled_text);
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
/// [`Stylize`]: crate::style::Stylize
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn patch_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = self.style.patch(style);
|
||||
@@ -328,7 +381,11 @@ impl<'a> Text<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// style::{Color, Modifier, Style},
|
||||
/// text::Text,
|
||||
/// };
|
||||
///
|
||||
/// let text = Text::styled(
|
||||
/// "The first line\nThe second line",
|
||||
/// (Color::Yellow, Modifier::ITALIC),
|
||||
@@ -355,7 +412,8 @@ impl<'a> Text<'a> {
|
||||
/// Set alignment to the whole text.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{layout::Alignment, text::Text};
|
||||
///
|
||||
/// let mut text = Text::from("Hi, what's up?");
|
||||
/// assert_eq!(None, text.alignment);
|
||||
/// assert_eq!(
|
||||
@@ -367,7 +425,11 @@ impl<'a> Text<'a> {
|
||||
/// Set a default alignment and override it on a per line basis.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// layout::Alignment,
|
||||
/// text::{Line, Text},
|
||||
/// };
|
||||
///
|
||||
/// let text = Text::from(vec![
|
||||
/// Line::from("left").alignment(Alignment::Left),
|
||||
/// Line::from("default"),
|
||||
@@ -404,7 +466,8 @@ impl<'a> Text<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::text::Text;
|
||||
///
|
||||
/// let text = Text::from("Hi, what's up?").left_aligned();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -423,7 +486,8 @@ impl<'a> Text<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::text::Text;
|
||||
///
|
||||
/// let text = Text::from("Hi, what's up?").centered();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -442,7 +506,8 @@ impl<'a> Text<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::text::Text;
|
||||
///
|
||||
/// let text = Text::from("Hi, what's up?").right_aligned();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -468,7 +533,8 @@ impl<'a> Text<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::text::{Line, Span, Text};
|
||||
///
|
||||
/// let mut text = Text::from("Hello, world!");
|
||||
/// text.push_line(Line::from("How are you?"));
|
||||
/// text.push_line(Span::from("How are you?"));
|
||||
@@ -486,7 +552,8 @@ impl<'a> Text<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// use ratatui::text::{Span, Text};
|
||||
///
|
||||
/// let mut text = Text::from("Hello, world!");
|
||||
/// text.push_span(Span::from("How are you?"));
|
||||
/// text.push_span("How are you?");
|
||||
@@ -708,6 +775,7 @@ mod tests {
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
use super::*;
|
||||
use crate::style::{Color, Modifier, Stylize};
|
||||
|
||||
#[fixture]
|
||||
fn small_buf() -> Buffer {
|
||||
@@ -1251,45 +1319,68 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
mod debug {
|
||||
use super::*;
|
||||
#[rstest]
|
||||
#[case::default(Text::default(), "Text::default()")]
|
||||
// TODO jm: these could be improved to inspect the line / span if there's only one. e.g.
|
||||
// Text::from("Hello, world!") and Text::from("Hello, world!".blue()) but the current
|
||||
// implementation is good enough for now.
|
||||
#[case::raw(
|
||||
Text::raw("Hello, world!"),
|
||||
r#"Text::from(Line::from("Hello, world!"))"#
|
||||
)]
|
||||
#[case::styled(
|
||||
Text::styled("Hello, world!", Color::Yellow),
|
||||
r#"Text::from(Line::from("Hello, world!")).yellow()"#
|
||||
)]
|
||||
#[case::complex_styled(
|
||||
Text::from("Hello, world!").yellow().on_blue().bold().italic().not_dim().not_hidden(),
|
||||
r#"Text::from(Line::from("Hello, world!")).yellow().on_blue().bold().italic().not_dim().not_hidden()"#
|
||||
)]
|
||||
#[case::alignment(
|
||||
Text::from("Hello, world!").centered(),
|
||||
r#"Text::from(Line::from("Hello, world!")).centered()"#
|
||||
)]
|
||||
#[case::styled_alignment(
|
||||
Text::styled("Hello, world!", Color::Yellow).centered(),
|
||||
r#"Text::from(Line::from("Hello, world!")).yellow().centered()"#
|
||||
)]
|
||||
#[case::multiple_lines(
|
||||
Text::from(vec![
|
||||
Line::from("Hello, world!"),
|
||||
Line::from("How are you?")
|
||||
]),
|
||||
r#"Text::from_iter([Line::from("Hello, world!"), Line::from("How are you?")])"#
|
||||
)]
|
||||
fn debug(#[case] text: Text, #[case] expected: &str) {
|
||||
assert_eq!(format!("{text:?}"), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "This is just showing the debug output of the assertions"]
|
||||
fn no_style() {
|
||||
let text = Text::from("single unstyled line");
|
||||
assert_eq!(text, Text::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "This is just showing the debug output of the assertions"]
|
||||
fn text_style() {
|
||||
let text = Text::from("single styled line")
|
||||
.red()
|
||||
.on_black()
|
||||
.bold()
|
||||
.not_italic();
|
||||
assert_eq!(text, Text::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "This is just showing the debug output of the assertions"]
|
||||
fn line_style() {
|
||||
let text = Text::from(vec![
|
||||
Line::from("first line").red().alignment(Alignment::Right),
|
||||
Line::from("second line").on_black(),
|
||||
]);
|
||||
assert_eq!(text, Text::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "This is just showing the debug output of the assertions"]
|
||||
fn span_style() {
|
||||
let text = Text::from(Line::from(vec![
|
||||
Span::from("first span").red(),
|
||||
Span::from("second span").on_black(),
|
||||
]));
|
||||
assert_eq!(text, Text::default());
|
||||
}
|
||||
#[test]
|
||||
fn debug_alternate() {
|
||||
let text = Text::from_iter([
|
||||
Line::from("Hello, world!"),
|
||||
Line::from("How are you?").bold().left_aligned(),
|
||||
Line::from_iter([
|
||||
Span::from("I'm "),
|
||||
Span::from("doing ").italic(),
|
||||
Span::from("great!").bold(),
|
||||
]),
|
||||
])
|
||||
.on_blue()
|
||||
.italic()
|
||||
.centered();
|
||||
assert_eq!(
|
||||
format!("{text:#?}"),
|
||||
indoc::indoc! {r#"
|
||||
Text::from_iter([
|
||||
Line::from("Hello, world!"),
|
||||
Line::from("How are you?").bold().left_aligned(),
|
||||
Line::from_iter([
|
||||
Span::from("I'm "),
|
||||
Span::from("doing ").italic(),
|
||||
Span::from("great!").bold(),
|
||||
]),
|
||||
]).on_blue().italic().centered()"#}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ mod chart;
|
||||
mod clear;
|
||||
mod gauge;
|
||||
mod list;
|
||||
mod logo;
|
||||
mod paragraph;
|
||||
mod reflow;
|
||||
mod scrollbar;
|
||||
@@ -46,6 +47,7 @@ pub use self::{
|
||||
clear::Clear,
|
||||
gauge::{Gauge, LineGauge},
|
||||
list::{List, ListDirection, ListItem, ListState},
|
||||
logo::{RatatuiLogo, Size as RatatuiLogoSize},
|
||||
paragraph::{Paragraph, Wrap},
|
||||
scrollbar::{ScrollDirection, Scrollbar, ScrollbarOrientation, ScrollbarState},
|
||||
sparkline::{RenderDirection, Sparkline},
|
||||
@@ -83,7 +85,11 @@ use crate::{buffer::Buffer, layout::Rect, style::Style};
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use ratatui::{backend::TestBackend, prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// backend::TestBackend,
|
||||
/// widgets::{Clear, Widget},
|
||||
/// Terminal,
|
||||
/// };
|
||||
/// # let backend = TestBackend::new(5, 5);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
///
|
||||
@@ -95,7 +101,7 @@ use crate::{buffer::Buffer, layout::Rect, style::Style};
|
||||
/// It's common to render widgets inside other widgets:
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
|
||||
///
|
||||
/// struct MyWidget;
|
||||
///
|
||||
@@ -133,7 +139,11 @@ pub trait Widget {
|
||||
/// ```rust,no_run
|
||||
/// use std::io;
|
||||
///
|
||||
/// use ratatui::{backend::TestBackend, prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// backend::TestBackend,
|
||||
/// widgets::{List, ListItem, ListState, StatefulWidget, Widget},
|
||||
/// Terminal,
|
||||
/// };
|
||||
///
|
||||
/// // Let's say we have some events to display.
|
||||
/// struct Events {
|
||||
@@ -257,7 +267,12 @@ pub trait StatefulWidget {
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "unstable-widget-ref")] {
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// text::Line,
|
||||
/// widgets::{Widget, WidgetRef},
|
||||
/// };
|
||||
///
|
||||
/// struct Greeting;
|
||||
///
|
||||
@@ -332,7 +347,12 @@ impl<W: WidgetRef> Widget for &W {
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "unstable-widget-ref")] {
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// text::Line,
|
||||
/// widgets::{Widget, WidgetRef},
|
||||
/// };
|
||||
///
|
||||
/// struct Parent {
|
||||
/// child: Option<Child>,
|
||||
@@ -381,7 +401,13 @@ impl<W: WidgetRef> WidgetRef for Option<W> {
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "unstable-widget-ref")] {
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// style::Stylize,
|
||||
/// text::Line,
|
||||
/// widgets::{StatefulWidget, StatefulWidgetRef, Widget},
|
||||
/// };
|
||||
///
|
||||
/// struct PersonalGreeting;
|
||||
///
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
use crate::{prelude::*, style::Styled, widgets::Block};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::{Direction, Rect},
|
||||
style::{Style, Styled},
|
||||
symbols::{self},
|
||||
text::Line,
|
||||
widgets::{block::BlockExt, Block, Widget, WidgetRef},
|
||||
};
|
||||
|
||||
mod bar;
|
||||
mod bar_group;
|
||||
@@ -42,7 +49,10 @@ pub use bar_group::BarGroup;
|
||||
/// The first group is added by an array slice (`&[(&str, u64)]`).
|
||||
/// The second group is added by a [`BarGroup`] instance.
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// widgets::{Bar, BarChart, BarGroup, Block},
|
||||
/// };
|
||||
///
|
||||
/// BarChart::default()
|
||||
/// .block(Block::bordered().title("BarChart"))
|
||||
@@ -113,7 +123,8 @@ impl<'a> BarChart<'a> {
|
||||
/// The first group is added by an array slice (`&[(&str, u64)]`).
|
||||
/// The second group is added by a [`BarGroup`] instance.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::{Bar, BarChart, BarGroup};
|
||||
///
|
||||
/// BarChart::default()
|
||||
/// .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
|
||||
/// .data(BarGroup::default().bars(&[Bar::default().value(10), Bar::default().value(20)]));
|
||||
@@ -143,7 +154,7 @@ impl<'a> BarChart<'a> {
|
||||
/// This example shows the default behavior when `max` is not set.
|
||||
/// The maximum value in the dataset is taken (here, `100`).
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::BarChart;
|
||||
/// BarChart::default().data(&[("foo", 1), ("bar", 2), ("baz", 100)]);
|
||||
/// // Renders
|
||||
/// // █
|
||||
@@ -154,7 +165,8 @@ impl<'a> BarChart<'a> {
|
||||
/// This example shows a custom max value.
|
||||
/// The maximum height being `2`, `bar` & `baz` render as the max.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::BarChart;
|
||||
///
|
||||
/// BarChart::default()
|
||||
/// .data(&[("foo", 1), ("bar", 2), ("baz", 100)])
|
||||
/// .max(2);
|
||||
@@ -176,6 +188,8 @@ impl<'a> BarChart<'a> {
|
||||
///
|
||||
/// It is also possible to set individually the style of each [`Bar`].
|
||||
/// In this case the default style will be patched by the individual style
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn bar_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.bar_style = style.into();
|
||||
@@ -204,7 +218,8 @@ impl<'a> BarChart<'a> {
|
||||
///
|
||||
/// This shows two bars with a gap of `3`. Notice the labels will always stay under the bar.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::BarChart;
|
||||
///
|
||||
/// BarChart::default()
|
||||
/// .data(&[("foo", 1), ("bar", 2)])
|
||||
/// .bar_gap(3);
|
||||
@@ -239,6 +254,8 @@ impl<'a> BarChart<'a> {
|
||||
/// # See also
|
||||
///
|
||||
/// [`Bar::value_style`] to set the value style individually.
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn value_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.value_style = style.into();
|
||||
@@ -256,6 +273,8 @@ impl<'a> BarChart<'a> {
|
||||
/// # See also
|
||||
///
|
||||
/// [`Bar::label`] to set the label style individually.
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn label_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.label_style = style.into();
|
||||
@@ -275,6 +294,8 @@ impl<'a> BarChart<'a> {
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// The style will be applied to everything that isn't styled (borders, bars, labels, ...).
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
@@ -615,7 +636,12 @@ mod tests {
|
||||
use itertools::iproduct;
|
||||
|
||||
use super::*;
|
||||
use crate::widgets::BorderType;
|
||||
use crate::{
|
||||
layout::Alignment,
|
||||
style::{Color, Modifier, Stylize},
|
||||
text::Span,
|
||||
widgets::BorderType,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::{buffer::Buffer, layout::Rect, style::Style, text::Line, widgets::Widget};
|
||||
/// A bar to be shown by the [`BarChart`](crate::widgets::BarChart) widget.
|
||||
///
|
||||
/// Here is an explanation of a `Bar`'s components.
|
||||
@@ -17,13 +16,16 @@ use crate::prelude::*;
|
||||
/// The following example creates a bar with the label "Bar 1", a value "10",
|
||||
/// red background and a white value foreground.
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// widgets::Bar,
|
||||
/// };
|
||||
///
|
||||
/// Bar::default()
|
||||
/// .label("Bar 1".into())
|
||||
/// .value(10)
|
||||
/// .style(Style::default().fg(Color::Red))
|
||||
/// .value_style(Style::default().bg(Color::Red).fg(Color::White))
|
||||
/// .style(Style::new().red())
|
||||
/// .value_style(Style::new().red().on_white())
|
||||
/// .text_value("10°C".to_string());
|
||||
/// ```
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
@@ -74,6 +76,8 @@ impl<'a> Bar<'a> {
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// This will apply to every non-styled element. It can be seen and used as a default value.
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
@@ -88,6 +92,8 @@ impl<'a> Bar<'a> {
|
||||
/// # See also
|
||||
///
|
||||
/// [`Bar::value`] to set the value.
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn value_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.value_style = style.into();
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
use super::Bar;
|
||||
use crate::prelude::*;
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Rect},
|
||||
style::Style,
|
||||
text::Line,
|
||||
widgets::{barchart::Bar, Widget},
|
||||
};
|
||||
|
||||
/// A group of bars to be shown by the Barchart.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::{Bar, BarGroup};
|
||||
///
|
||||
/// BarGroup::default()
|
||||
/// .label("Group 1".into())
|
||||
|
||||
@@ -8,7 +8,14 @@
|
||||
use itertools::Itertools;
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
use crate::{prelude::*, style::Styled, symbols::border, widgets::Borders};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Rect},
|
||||
style::{Style, Styled},
|
||||
symbols::border,
|
||||
text::Line,
|
||||
widgets::{Borders, Widget, WidgetRef},
|
||||
};
|
||||
|
||||
mod padding;
|
||||
pub mod title;
|
||||
@@ -67,7 +74,10 @@ pub use title::{Position, Title};
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Color, Style},
|
||||
/// widgets::{Block, BorderType, Borders},
|
||||
/// };
|
||||
///
|
||||
/// Block::new()
|
||||
/// .border_type(BorderType::Rounded)
|
||||
@@ -79,12 +89,9 @@ pub use title::{Position, Title};
|
||||
///
|
||||
/// You may also use multiple titles like in the following:
|
||||
/// ```
|
||||
/// use ratatui::{
|
||||
/// prelude::*,
|
||||
/// widgets::{
|
||||
/// block::{Position, Title},
|
||||
/// Block,
|
||||
/// },
|
||||
/// use ratatui::widgets::{
|
||||
/// block::{Position, Title},
|
||||
/// Block,
|
||||
/// };
|
||||
///
|
||||
/// Block::new()
|
||||
@@ -94,10 +101,7 @@ pub use title::{Position, Title};
|
||||
///
|
||||
/// You can also pass it as parameters of another widget so that the block surrounds them:
|
||||
/// ```
|
||||
/// use ratatui::{
|
||||
/// prelude::*,
|
||||
/// widgets::{Block, Borders, List},
|
||||
/// };
|
||||
/// use ratatui::widgets::{Block, Borders, List};
|
||||
///
|
||||
/// let surrounding_block = Block::default()
|
||||
/// .borders(Borders::ALL)
|
||||
@@ -220,7 +224,8 @@ impl<'a> Block<'a> {
|
||||
/// Create a new block with [all borders](Borders::ALL) shown
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::widgets::{Block, Borders};
|
||||
/// use ratatui::widgets::{Block, Borders};
|
||||
///
|
||||
/// assert_eq!(Block::bordered(), Block::new().borders(Borders::ALL));
|
||||
/// ```
|
||||
pub const fn bordered() -> Self {
|
||||
@@ -268,8 +273,8 @@ impl<'a> Block<'a> {
|
||||
/// - Two titles with the same alignment (notice the left titles are separated)
|
||||
/// ```
|
||||
/// use ratatui::{
|
||||
/// prelude::*,
|
||||
/// widgets::{block::*, *},
|
||||
/// text::Line,
|
||||
/// widgets::{Block, Borders},
|
||||
/// };
|
||||
///
|
||||
/// Block::new()
|
||||
@@ -320,7 +325,8 @@ impl<'a> Block<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{ prelude::*, widgets::* };
|
||||
/// use ratatui::{ widgets::Block, text::Line };
|
||||
///
|
||||
/// Block::bordered()
|
||||
/// .title_top("Left1") // By default in the top left corner
|
||||
/// .title_top(Line::from("Left2").left_aligned())
|
||||
@@ -348,7 +354,8 @@ impl<'a> Block<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{ prelude::*, widgets::* };
|
||||
/// use ratatui::{ widgets::Block, text::Line };
|
||||
///
|
||||
/// Block::bordered()
|
||||
/// .title_bottom("Left1") // By default in the top left corner
|
||||
/// .title_bottom(Line::from("Left2").left_aligned())
|
||||
@@ -377,6 +384,8 @@ impl<'a> Block<'a> {
|
||||
///
|
||||
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn title_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.titles_style = style.into();
|
||||
@@ -392,10 +401,7 @@ impl<'a> Block<'a> {
|
||||
/// This example aligns all titles in the center except the "right" title which explicitly sets
|
||||
/// [`Alignment::Right`].
|
||||
/// ```
|
||||
/// use ratatui::{
|
||||
/// prelude::*,
|
||||
/// widgets::{block::*, *},
|
||||
/// };
|
||||
/// use ratatui::{layout::Alignment, text::Line, widgets::Block};
|
||||
///
|
||||
/// Block::new()
|
||||
/// .title_alignment(Alignment::Center)
|
||||
@@ -419,13 +425,7 @@ impl<'a> Block<'a> {
|
||||
/// This example positions all titles on the bottom except the "top" title which explicitly sets
|
||||
/// [`Position::Top`].
|
||||
/// ```
|
||||
/// use ratatui::{
|
||||
/// prelude::*,
|
||||
/// widgets::{
|
||||
/// block::{Position, Title},
|
||||
/// Block,
|
||||
/// },
|
||||
/// };
|
||||
/// use ratatui::widgets::{block::Position, Block};
|
||||
///
|
||||
/// Block::new()
|
||||
/// .title_position(Position::Bottom)
|
||||
@@ -454,9 +454,14 @@ impl<'a> Block<'a> {
|
||||
///
|
||||
/// This example shows a `Block` with blue borders.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// widgets::Block,
|
||||
/// };
|
||||
/// Block::bordered().border_style(Style::new().blue());
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn border_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.border_style = style.into();
|
||||
@@ -479,7 +484,11 @@ impl<'a> Block<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Color, Style, Stylize},
|
||||
/// widgets::{Block, Paragraph},
|
||||
/// };
|
||||
///
|
||||
/// let block = Block::new().style(Style::new().red().on_black());
|
||||
///
|
||||
/// // For border and title you can additionally apply styles on top of the block level style.
|
||||
@@ -496,6 +505,7 @@ impl<'a> Block<'a> {
|
||||
/// ```
|
||||
///
|
||||
/// [`Paragraph`]: crate::widgets::Paragraph
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
@@ -510,7 +520,7 @@ impl<'a> Block<'a> {
|
||||
///
|
||||
/// Display left and right borders.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::{Block, Borders};
|
||||
/// Block::new().borders(Borders::LEFT | Borders::RIGHT);
|
||||
/// ```
|
||||
///
|
||||
@@ -531,7 +541,7 @@ impl<'a> Block<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::{Block, BorderType};
|
||||
/// Block::bordered()
|
||||
/// .border_type(BorderType::Rounded)
|
||||
/// .title("Block");
|
||||
@@ -553,7 +563,8 @@ impl<'a> Block<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{widgets::Block, symbols};
|
||||
///
|
||||
/// Block::bordered().border_set(symbols::border::DOUBLE).title("Block");
|
||||
/// // Renders
|
||||
/// // ╔Block╗
|
||||
@@ -573,7 +584,8 @@ impl<'a> Block<'a> {
|
||||
///
|
||||
/// This renders a `Block` with no padding (the default).
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::{Block, Padding};
|
||||
///
|
||||
/// Block::bordered().padding(Padding::ZERO);
|
||||
/// // Renders
|
||||
/// // ┌───────┐
|
||||
@@ -584,7 +596,8 @@ impl<'a> Block<'a> {
|
||||
/// This example shows a `Block` with padding left and right ([`Padding::horizontal`]).
|
||||
/// Notice the two spaces before and after the content.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::{Block, Padding};
|
||||
///
|
||||
/// Block::bordered().padding(Padding::horizontal(2));
|
||||
/// // Renders
|
||||
/// // ┌───────────┐
|
||||
@@ -603,7 +616,8 @@ impl<'a> Block<'a> {
|
||||
///
|
||||
/// Draw a block nested within another block
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{widgets::Block, Frame};
|
||||
///
|
||||
/// # fn render_nested_block(frame: &mut Frame) {
|
||||
/// let outer_block = Block::bordered().title("Outer");
|
||||
/// let inner_block = Block::bordered().title("Inner");
|
||||
@@ -990,6 +1004,7 @@ mod tests {
|
||||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
use crate::style::{Color, Modifier, Stylize};
|
||||
|
||||
#[test]
|
||||
fn create_with_all_borders() {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::Padding;
|
||||
///
|
||||
/// Padding::uniform(1);
|
||||
/// Padding::horizontal(2);
|
||||
|
||||
@@ -31,14 +31,14 @@ use crate::{layout::Alignment, text::Line};
|
||||
///
|
||||
/// Blue title on a white background (via [`Stylize`](crate::style::Stylize) trait).
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::block::*};
|
||||
/// use ratatui::{style::Stylize, widgets::block::Title};
|
||||
///
|
||||
/// Title::from("Title".blue().on_white());
|
||||
/// ```
|
||||
///
|
||||
/// Title with multiple styles (see [`Line`] and [`Stylize`](crate::style::Stylize)).
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::block::*};
|
||||
/// use ratatui::{style::Stylize, text::Line, widgets::block::Title};
|
||||
///
|
||||
/// Title::from(Line::from(vec!["Q".white().underlined(), "uit".gray()]));
|
||||
/// ```
|
||||
@@ -46,7 +46,7 @@ use crate::{layout::Alignment, text::Line};
|
||||
/// Complete example
|
||||
/// ```
|
||||
/// use ratatui::{
|
||||
/// prelude::*,
|
||||
/// layout::Alignment,
|
||||
/// widgets::{
|
||||
/// block::{Position, Title},
|
||||
/// Block,
|
||||
@@ -84,7 +84,10 @@ pub struct Title<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::widgets::{block::*, *};
|
||||
/// use ratatui::widgets::{
|
||||
/// block::{Position, Title},
|
||||
/// Block,
|
||||
/// };
|
||||
///
|
||||
/// Block::new().title(Title::from("title").position(Position::Bottom));
|
||||
/// ```
|
||||
|
||||
@@ -60,7 +60,11 @@ impl fmt::Debug for Borders {
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{border, prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// border,
|
||||
/// widgets::{Block, Borders},
|
||||
/// };
|
||||
///
|
||||
/// Block::new()
|
||||
/// .title("Construct Borders and use them in place")
|
||||
/// .borders(border!(TOP, BOTTOM));
|
||||
@@ -69,7 +73,7 @@ impl fmt::Debug for Borders {
|
||||
/// `border!` can be called with any number of individual sides:
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{border, prelude::*, widgets::*};
|
||||
/// use ratatui::{border, widgets::Borders};
|
||||
/// let right_open = border!(TOP, LEFT, BOTTOM);
|
||||
/// assert_eq!(right_open, Borders::TOP | Borders::LEFT | Borders::BOTTOM);
|
||||
/// ```
|
||||
@@ -77,7 +81,8 @@ impl fmt::Debug for Borders {
|
||||
/// Single borders work but using `Borders::` directly would be simpler.
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{border, prelude::*, widgets::*};
|
||||
/// use ratatui::{border, widgets::Borders};
|
||||
///
|
||||
/// assert_eq!(border!(TOP), Borders::TOP);
|
||||
/// assert_eq!(border!(ALL), Borders::ALL);
|
||||
/// assert_eq!(border!(), Borders::NONE);
|
||||
|
||||
@@ -12,7 +12,13 @@ use std::collections::HashMap;
|
||||
|
||||
use time::{Date, Duration, OffsetDateTime};
|
||||
|
||||
use crate::{prelude::*, widgets::Block};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Constraint, Layout, Rect},
|
||||
style::Style,
|
||||
text::{Line, Span},
|
||||
widgets::{block::BlockExt, Block, Widget, WidgetRef},
|
||||
};
|
||||
|
||||
/// Display a month calendar for the month containing `display_date`
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
@@ -46,6 +52,8 @@ impl<'a, DS: DateStyler> Monthly<'a, DS> {
|
||||
///
|
||||
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn show_surrounding<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.show_surrounding = Some(style.into());
|
||||
@@ -56,6 +64,8 @@ impl<'a, DS: DateStyler> Monthly<'a, DS> {
|
||||
///
|
||||
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn show_weekdays_header<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.show_weekday = Some(style.into());
|
||||
@@ -66,6 +76,8 @@ impl<'a, DS: DateStyler> Monthly<'a, DS> {
|
||||
///
|
||||
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn show_month_header<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.show_month = Some(style.into());
|
||||
@@ -76,6 +88,8 @@ impl<'a, DS: DateStyler> Monthly<'a, DS> {
|
||||
///
|
||||
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn default_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.default_style = style.into();
|
||||
@@ -201,6 +215,8 @@ impl CalendarEventStore {
|
||||
///
|
||||
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
pub fn today<S: Into<Style>>(style: S) -> Self {
|
||||
let mut res = Self::default();
|
||||
res.add(
|
||||
@@ -216,6 +232,8 @@ impl CalendarEventStore {
|
||||
///
|
||||
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
pub fn add<S: Into<Style>>(&mut self, date: Date, style: S) {
|
||||
// to simplify style nonsense, last write wins
|
||||
let _ = self.0.insert(date, style.into());
|
||||
@@ -250,6 +268,7 @@ mod tests {
|
||||
use time::Month;
|
||||
|
||||
use super::*;
|
||||
use crate::style::Color;
|
||||
|
||||
#[test]
|
||||
fn event_store() {
|
||||
|
||||
@@ -30,7 +30,14 @@ pub use self::{
|
||||
points::Points,
|
||||
rectangle::Rectangle,
|
||||
};
|
||||
use crate::{prelude::*, symbols::Marker, text::Line as TextLine, widgets::Block};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Color, Style},
|
||||
symbols::{self, Marker},
|
||||
text::Line as TextLine,
|
||||
widgets::{block::BlockExt, Block, Widget, WidgetRef},
|
||||
};
|
||||
|
||||
/// Something that can be drawn on a [`Canvas`].
|
||||
///
|
||||
@@ -356,7 +363,10 @@ impl<'a, 'b> Painter<'a, 'b> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::canvas::*};
|
||||
/// use ratatui::{
|
||||
/// symbols,
|
||||
/// widgets::canvas::{Context, Painter},
|
||||
/// };
|
||||
///
|
||||
/// let mut ctx = Context::new(2, 2, [1.0, 2.0], [0.0, 2.0], symbols::Marker::Braille);
|
||||
/// let mut painter = Painter::from(&mut ctx);
|
||||
@@ -399,7 +409,11 @@ impl<'a, 'b> Painter<'a, 'b> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::canvas::*};
|
||||
/// use ratatui::{
|
||||
/// style::Color,
|
||||
/// symbols,
|
||||
/// widgets::canvas::{Context, Painter},
|
||||
/// };
|
||||
///
|
||||
/// let mut ctx = Context::new(1, 1, [0.0, 2.0], [0.0, 2.0], symbols::Marker::Braille);
|
||||
/// let mut painter = Painter::from(&mut ctx);
|
||||
@@ -425,7 +439,7 @@ impl<'a, 'b> From<&'a mut Context<'b>> for Painter<'a, 'b> {
|
||||
/// This is used by the [`Canvas`] widget to draw shapes on the grid. It can be useful to think of
|
||||
/// this as similar to the [`Frame`] struct that is used to draw widgets on the terminal.
|
||||
///
|
||||
/// [`Frame`]: crate::prelude::Frame
|
||||
/// [`Frame`]: crate::Frame
|
||||
#[derive(Debug)]
|
||||
pub struct Context<'a> {
|
||||
x_bounds: [f64; 2],
|
||||
@@ -449,7 +463,7 @@ impl<'a> Context<'a> {
|
||||
/// example, if you want to draw a map of the world, you might want to use the following bounds:
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::canvas::*};
|
||||
/// use ratatui::{symbols, widgets::canvas::Context};
|
||||
///
|
||||
/// let ctx = Context::new(
|
||||
/// 100,
|
||||
@@ -513,6 +527,8 @@ impl<'a> Context<'a> {
|
||||
///
|
||||
/// Note that the text is always printed on top of the canvas and is **not** affected by the
|
||||
/// layers.
|
||||
///
|
||||
/// [`Text`]: crate::text::Text
|
||||
pub fn print<T>(&mut self, x: f64, y: f64, line: T)
|
||||
where
|
||||
T: Into<TextLine<'a>>,
|
||||
@@ -563,7 +579,10 @@ impl<'a> Context<'a> {
|
||||
/// ```
|
||||
/// use ratatui::{
|
||||
/// style::Color,
|
||||
/// widgets::{canvas::*, *},
|
||||
/// widgets::{
|
||||
/// canvas::{Canvas, Line, Map, MapResolution, Rectangle},
|
||||
/// Block,
|
||||
/// },
|
||||
/// };
|
||||
///
|
||||
/// Canvas::default()
|
||||
@@ -698,7 +717,7 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::canvas::*};
|
||||
/// use ratatui::{symbols, widgets::canvas::Canvas};
|
||||
///
|
||||
/// Canvas::default()
|
||||
/// .marker(symbols::Marker::Braille)
|
||||
|
||||
@@ -65,7 +65,12 @@ mod tests {
|
||||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
use crate::{prelude::*, symbols::Marker, widgets::canvas::Canvas};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
symbols::Marker,
|
||||
widgets::{canvas::Canvas, Widget},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn map_resolution_to_string() {
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
widgets::canvas::{Line, Painter, Shape},
|
||||
};
|
||||
|
||||
/// A rectangle to draw on a [`Canvas`](super::Canvas)
|
||||
/// A rectangle to draw on a [`Canvas`](crate::widgets::canvas::Canvas)
|
||||
///
|
||||
/// Sizes used here are **not** in terminal cell. This is much more similar to the
|
||||
/// mathematic coordinate system.
|
||||
@@ -66,7 +66,13 @@ impl Shape for Rectangle {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{prelude::*, symbols::Marker, widgets::canvas::Canvas};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::{Margin, Rect},
|
||||
style::{Style, Stylize},
|
||||
symbols::Marker,
|
||||
widgets::{canvas::Canvas, Widget},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn draw_block_lines() {
|
||||
|
||||
@@ -3,12 +3,15 @@ use std::{cmp::max, ops::Not};
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
use crate::{
|
||||
layout::Flex,
|
||||
prelude::*,
|
||||
style::Styled,
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Constraint, Flex, Layout, Position, Rect},
|
||||
style::{Color, Style, Styled},
|
||||
symbols::{self},
|
||||
text::Line,
|
||||
widgets::{
|
||||
block::BlockExt,
|
||||
canvas::{Canvas, Line as CanvasLine, Points},
|
||||
Block,
|
||||
Block, Widget, WidgetRef,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -25,7 +28,11 @@ use crate::{
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// widgets::Axis,
|
||||
/// };
|
||||
///
|
||||
/// let axis = Axis::default()
|
||||
/// .title("X Axis")
|
||||
/// .style(Style::default().gray())
|
||||
@@ -94,7 +101,8 @@ impl<'a> Axis<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{style::Stylize, widgets::Axis};
|
||||
///
|
||||
/// let axis = Axis::default()
|
||||
/// .bounds([0.0, 50.0])
|
||||
/// .labels(["0".bold(), "25".into(), "50".bold()]);
|
||||
@@ -122,7 +130,8 @@ impl<'a> Axis<'a> {
|
||||
/// like so
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{style::Stylize, widgets::Axis};
|
||||
///
|
||||
/// let axis = Axis::default().red();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -297,7 +306,11 @@ impl LegendPosition {
|
||||
/// This example draws a red line between two points.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{prelude::*, symbols::Marker, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::Stylize,
|
||||
/// symbols::Marker,
|
||||
/// widgets::{Dataset, GraphType},
|
||||
/// };
|
||||
///
|
||||
/// let dataset = Dataset::default()
|
||||
/// .name("dataset 1")
|
||||
@@ -401,7 +414,8 @@ impl<'a> Dataset<'a> {
|
||||
/// like so
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{style::Stylize, widgets::Dataset};
|
||||
///
|
||||
/// let dataset = Dataset::default().red();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -452,7 +466,11 @@ struct ChartLayout {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// symbols,
|
||||
/// widgets::{Axis, Block, Chart, Dataset, GraphType},
|
||||
/// };
|
||||
///
|
||||
/// // Create the datasets to fill the chart with
|
||||
/// let datasets = vec![
|
||||
@@ -521,17 +539,19 @@ impl<'a> Chart<'a> {
|
||||
/// This creates a simple chart with one [`Dataset`]
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let data_points = vec![];
|
||||
/// use ratatui::widgets::{Chart, Dataset};
|
||||
///
|
||||
/// let data_points = vec![];
|
||||
/// let chart = Chart::new(vec![Dataset::default().data(&data_points)]);
|
||||
/// ```
|
||||
///
|
||||
/// This creates a chart with multiple [`Dataset`]s
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let data_points = vec![];
|
||||
/// # let data_points2 = vec![];
|
||||
/// use ratatui::widgets::{Chart, Dataset};
|
||||
///
|
||||
/// let data_points = vec![];
|
||||
/// let data_points2 = vec![];
|
||||
/// let chart = Chart::new(vec![
|
||||
/// Dataset::default().data(&data_points),
|
||||
/// Dataset::default().data(&data_points2),
|
||||
@@ -581,7 +601,8 @@ impl<'a> Chart<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::{Axis, Chart};
|
||||
///
|
||||
/// let chart = Chart::new(vec![]).x_axis(
|
||||
/// Axis::default()
|
||||
/// .title("X Axis")
|
||||
@@ -604,7 +625,8 @@ impl<'a> Chart<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::{Axis, Chart};
|
||||
///
|
||||
/// let chart = Chart::new(vec![]).y_axis(
|
||||
/// Axis::default()
|
||||
/// .title("Y Axis")
|
||||
@@ -635,7 +657,8 @@ impl<'a> Chart<'a> {
|
||||
/// its height is greater than 25% of the total widget height.
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{layout::Constraint, widgets::Chart};
|
||||
///
|
||||
/// let constraints = (Constraint::Ratio(1, 3), Constraint::Ratio(1, 4));
|
||||
/// let chart = Chart::new(vec![]).hidden_legend_constraints(constraints);
|
||||
/// ```
|
||||
@@ -644,7 +667,8 @@ impl<'a> Chart<'a> {
|
||||
/// first one is always true.
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{layout::Constraint, widgets::Chart};
|
||||
///
|
||||
/// let constraints = (Constraint::Min(0), Constraint::Ratio(1, 4));
|
||||
/// let chart = Chart::new(vec![]).hidden_legend_constraints(constraints);
|
||||
/// ```
|
||||
@@ -653,7 +677,8 @@ impl<'a> Chart<'a> {
|
||||
/// [`Chart::legend_position`].
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{layout::Constraint, widgets::Chart};
|
||||
///
|
||||
/// let constraints = (Constraint::Length(0), Constraint::Ratio(1, 4));
|
||||
/// let chart = Chart::new(vec![]).hidden_legend_constraints(constraints);
|
||||
/// ```
|
||||
@@ -685,14 +710,16 @@ impl<'a> Chart<'a> {
|
||||
/// Show the legend on the top left corner.
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::widgets::{Chart, LegendPosition};
|
||||
/// use ratatui::widgets::{Chart, LegendPosition};
|
||||
///
|
||||
/// let chart: Chart = Chart::new(vec![]).legend_position(Some(LegendPosition::TopLeft));
|
||||
/// ```
|
||||
///
|
||||
/// Hide the legend altogether
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::widgets::{Chart, LegendPosition};
|
||||
/// use ratatui::widgets::{Chart, LegendPosition};
|
||||
///
|
||||
/// let chart = Chart::new(vec![]).legend_position(None);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -1134,6 +1161,7 @@ mod tests {
|
||||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
use crate::style::{Modifier, Stylize};
|
||||
|
||||
struct LegendTestCase {
|
||||
chart_area: Rect,
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::prelude::*;
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
widgets::{Widget, WidgetRef},
|
||||
};
|
||||
|
||||
/// A widget to clear/reset a certain area to allow overdrawing (e.g. for popups).
|
||||
///
|
||||
@@ -8,7 +12,11 @@ use crate::prelude::*;
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// layout::Rect,
|
||||
/// widgets::{Block, Clear},
|
||||
/// Frame,
|
||||
/// };
|
||||
///
|
||||
/// fn draw_on_clear(f: &mut Frame, area: Rect) {
|
||||
/// let block = Block::bordered().title("Block");
|
||||
@@ -43,7 +51,7 @@ impl WidgetRef for Clear {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::{buffer::Buffer, layout::Rect, widgets::Widget};
|
||||
#[test]
|
||||
fn render() {
|
||||
let mut buffer = Buffer::with_lines(["xxxxxxxxxxxxxxx"; 7]);
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
use crate::{prelude::*, style::Styled, widgets::Block};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Color, Style, Styled},
|
||||
symbols::{self},
|
||||
text::{Line, Span},
|
||||
widgets::{block::BlockExt, Block, Widget, WidgetRef},
|
||||
};
|
||||
|
||||
/// A widget to display a progress bar.
|
||||
///
|
||||
@@ -16,16 +23,14 @@ use crate::{prelude::*, style::Styled, widgets::Block};
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// widgets::{Block, Gauge},
|
||||
/// };
|
||||
///
|
||||
/// Gauge::default()
|
||||
/// .block(Block::bordered().title("Progress"))
|
||||
/// .gauge_style(
|
||||
/// Style::default()
|
||||
/// .fg(Color::White)
|
||||
/// .bg(Color::Black)
|
||||
/// .add_modifier(Modifier::ITALIC),
|
||||
/// )
|
||||
/// .gauge_style(Style::new().white().on_black().italic())
|
||||
/// .percent(20);
|
||||
/// ```
|
||||
///
|
||||
@@ -242,16 +247,15 @@ fn get_unicode_block<'a>(frac: f64) -> &'a str {
|
||||
/// # Examples:
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// symbols,
|
||||
/// widgets::{Block, LineGauge},
|
||||
/// };
|
||||
///
|
||||
/// LineGauge::default()
|
||||
/// .block(Block::bordered().title("Progress"))
|
||||
/// .filled_style(
|
||||
/// Style::default()
|
||||
/// .fg(Color::White)
|
||||
/// .bg(Color::Black)
|
||||
/// .add_modifier(Modifier::BOLD),
|
||||
/// )
|
||||
/// .filled_style(Style::new().white().on_black().bold())
|
||||
/// .line_set(symbols::line::THICK)
|
||||
/// .ratio(0.4);
|
||||
/// ```
|
||||
@@ -443,7 +447,10 @@ impl<'a> Styled for LineGauge<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::{
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
symbols,
|
||||
};
|
||||
#[test]
|
||||
#[should_panic = "Percentage should be between 0 and 100 inclusively"]
|
||||
fn gauge_invalid_percentage() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::prelude::*;
|
||||
use crate::{style::Style, text::Text};
|
||||
|
||||
/// A single item in a [`List`]
|
||||
///
|
||||
@@ -19,14 +19,15 @@ use crate::prelude::*;
|
||||
/// You can create [`ListItem`]s from simple `&str`
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListItem;
|
||||
/// let item = ListItem::new("Item 1");
|
||||
/// ```
|
||||
///
|
||||
/// Anything that can be converted to [`Text`] can be a [`ListItem`].
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{text::Line, widgets::ListItem};
|
||||
///
|
||||
/// let item1: ListItem = "Item 1".into();
|
||||
/// let item2: ListItem = Line::raw("Item 2").into();
|
||||
/// ```
|
||||
@@ -34,7 +35,8 @@ use crate::prelude::*;
|
||||
/// A [`ListItem`] styled with [`Stylize`]
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{style::Stylize, widgets::ListItem};
|
||||
///
|
||||
/// let item = ListItem::new("Item 1").red().on_white();
|
||||
/// ```
|
||||
///
|
||||
@@ -42,7 +44,12 @@ use crate::prelude::*;
|
||||
/// [`Text`]
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::Stylize,
|
||||
/// text::{Span, Text},
|
||||
/// widgets::ListItem,
|
||||
/// };
|
||||
///
|
||||
/// let mut text = Text::default();
|
||||
/// text.extend(["Item".blue(), Span::raw(" "), "1".bold().red()]);
|
||||
/// let item = ListItem::new(text);
|
||||
@@ -51,12 +58,15 @@ use crate::prelude::*;
|
||||
/// A right-aligned `ListItem`
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// ListItem::new(Text::from("foo").alignment(Alignment::Right));
|
||||
/// use ratatui::{text::Text, widgets::ListItem};
|
||||
///
|
||||
/// ListItem::new(Text::from("foo").right_aligned());
|
||||
/// ```
|
||||
///
|
||||
/// [`List`]: crate::widgets::List
|
||||
/// [`Stylize`]: crate::style::Stylize
|
||||
/// [`Line`]: crate::text::Line
|
||||
/// [`Line::alignment`]: crate::text::Line::alignment
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct ListItem<'a> {
|
||||
pub(crate) content: Text<'a>,
|
||||
@@ -73,22 +83,25 @@ impl<'a> ListItem<'a> {
|
||||
/// You can create [`ListItem`]s from simple `&str`
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListItem;
|
||||
///
|
||||
/// let item = ListItem::new("Item 1");
|
||||
/// ```
|
||||
///
|
||||
/// Anything that can be converted to [`Text`] can be a [`ListItem`].
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{text::Line, widgets::ListItem};
|
||||
///
|
||||
/// let item1: ListItem = "Item 1".into();
|
||||
/// let item2: ListItem = Line::raw("Item 2").into();
|
||||
/// ```
|
||||
///
|
||||
/// You can also create multilines item
|
||||
/// You can also create multiline items
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListItem;
|
||||
///
|
||||
/// let item = ListItem::new("Multi-line\nitem");
|
||||
/// ```
|
||||
///
|
||||
@@ -118,7 +131,11 @@ impl<'a> ListItem<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// widgets::ListItem,
|
||||
/// };
|
||||
///
|
||||
/// let item = ListItem::new("Item 1").style(Style::new().red().italic());
|
||||
/// ```
|
||||
///
|
||||
@@ -127,12 +144,14 @@ impl<'a> ListItem<'a> {
|
||||
/// concisely.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{style::Stylize, widgets::ListItem};
|
||||
///
|
||||
/// let item = ListItem::new("Item 1").red().italic();
|
||||
/// ```
|
||||
///
|
||||
/// [`Styled`]: crate::style::Styled
|
||||
/// [`ListState`]: crate::widgets::list::ListState
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
@@ -146,7 +165,8 @@ impl<'a> ListItem<'a> {
|
||||
/// One line item
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListItem;
|
||||
///
|
||||
/// let item = ListItem::new("Item 1");
|
||||
/// assert_eq!(item.height(), 1);
|
||||
/// ```
|
||||
@@ -154,7 +174,8 @@ impl<'a> ListItem<'a> {
|
||||
/// Two lines item (note the `\n`)
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListItem;
|
||||
///
|
||||
/// let item = ListItem::new("Multi-line\nitem");
|
||||
/// assert_eq!(item.height(), 2);
|
||||
/// ```
|
||||
@@ -167,13 +188,15 @@ impl<'a> ListItem<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListItem;
|
||||
///
|
||||
/// let item = ListItem::new("12345");
|
||||
/// assert_eq!(item.width(), 5);
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListItem;
|
||||
///
|
||||
/// let item = ListItem::new("12345\n1234567");
|
||||
/// assert_eq!(item.width(), 7);
|
||||
/// ```
|
||||
@@ -198,6 +221,10 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
style::{Color, Modifier, Stylize},
|
||||
text::{Line, Span},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn new_from_str() {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
use super::ListItem;
|
||||
use crate::{
|
||||
prelude::*,
|
||||
style::Styled,
|
||||
widgets::{Block, HighlightSpacing},
|
||||
style::{Style, Styled},
|
||||
widgets::{Block, HighlightSpacing, ListItem},
|
||||
};
|
||||
|
||||
/// A widget to display several items among which one can be selected (optional)
|
||||
@@ -41,14 +39,20 @@ use crate::{
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// layout::Rect,
|
||||
/// style::{Style, Stylize},
|
||||
/// widgets::{Block, List, ListDirection, ListItem},
|
||||
/// Frame,
|
||||
/// };
|
||||
///
|
||||
/// # fn ui(frame: &mut Frame) {
|
||||
/// # let area = Rect::default();
|
||||
/// let items = ["Item 1", "Item 2", "Item 3"];
|
||||
/// let list = List::new(items)
|
||||
/// .block(Block::bordered().title("List"))
|
||||
/// .style(Style::default().fg(Color::White))
|
||||
/// .highlight_style(Style::default().add_modifier(Modifier::ITALIC))
|
||||
/// .style(Style::new().white())
|
||||
/// .highlight_style(Style::new().italic())
|
||||
/// .highlight_symbol(">>")
|
||||
/// .repeat_highlight_symbol(true)
|
||||
/// .direction(ListDirection::BottomToTop);
|
||||
@@ -60,7 +64,13 @@ use crate::{
|
||||
/// # Stateful example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// layout::Rect,
|
||||
/// style::{Style, Stylize},
|
||||
/// widgets::{Block, List, ListState},
|
||||
/// Frame,
|
||||
/// };
|
||||
///
|
||||
/// # fn ui(frame: &mut Frame) {
|
||||
/// # let area = Rect::default();
|
||||
/// // This should be stored outside of the function in your application state.
|
||||
@@ -68,7 +78,7 @@ use crate::{
|
||||
/// let items = ["Item 1", "Item 2", "Item 3"];
|
||||
/// let list = List::new(items)
|
||||
/// .block(Block::bordered().title("List"))
|
||||
/// .highlight_style(Style::new().add_modifier(Modifier::REVERSED))
|
||||
/// .highlight_style(Style::new().reversed())
|
||||
/// .highlight_symbol(">>")
|
||||
/// .repeat_highlight_symbol(true);
|
||||
///
|
||||
@@ -88,6 +98,9 @@ use crate::{
|
||||
/// [`ListState`]: crate::widgets::list::ListState
|
||||
/// [scroll]: crate::widgets::list::ListState::offset
|
||||
/// [select]: crate::widgets::list::ListState::select
|
||||
/// [`Text::alignment`]: crate::text::Text::alignment
|
||||
/// [`StatefulWidget`]: crate::widgets::StatefulWidget
|
||||
/// [`Widget`]: crate::widgets::Widget
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
|
||||
pub struct List<'a> {
|
||||
/// An optional block to wrap the widget in
|
||||
@@ -135,17 +148,23 @@ impl<'a> List<'a> {
|
||||
/// From a slice of [`&str`]
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::List;
|
||||
///
|
||||
/// let list = List::new(["Item 1", "Item 2"]);
|
||||
/// ```
|
||||
///
|
||||
/// From [`Text`]
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Text,
|
||||
/// widgets::List,
|
||||
/// };
|
||||
///
|
||||
/// let list = List::new([
|
||||
/// Text::styled("Item 1", Style::default().red()),
|
||||
/// Text::styled("Item 2", Style::default().red()),
|
||||
/// Text::styled("Item 1", Style::new().red()),
|
||||
/// Text::styled("Item 2", Style::new().red()),
|
||||
/// ]);
|
||||
/// ```
|
||||
///
|
||||
@@ -153,10 +172,13 @@ impl<'a> List<'a> {
|
||||
/// [`List::items`] fluent setter.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::List;
|
||||
///
|
||||
/// let empty_list = List::default();
|
||||
/// let filled_list = empty_list.items(["Item 1"]);
|
||||
/// ```
|
||||
///
|
||||
/// [`Text`]: crate::text::Text
|
||||
pub fn new<T>(items: T) -> Self
|
||||
where
|
||||
T: IntoIterator,
|
||||
@@ -181,9 +203,12 @@ impl<'a> List<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::List;
|
||||
///
|
||||
/// let list = List::default().items(["Item 1", "Item 2"]);
|
||||
/// ```
|
||||
///
|
||||
/// [`Text`]: crate::text::Text
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn items<T>(mut self, items: T) -> Self
|
||||
where
|
||||
@@ -203,8 +228,9 @@ impl<'a> List<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = ["Item 1"];
|
||||
/// use ratatui::widgets::{Block, List};
|
||||
///
|
||||
/// let items = ["Item 1"];
|
||||
/// let block = Block::bordered().title("List");
|
||||
/// let list = List::new(items).block(block);
|
||||
/// ```
|
||||
@@ -227,8 +253,12 @@ impl<'a> List<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = ["Item 1"];
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// widgets::List,
|
||||
/// };
|
||||
///
|
||||
/// let items = ["Item 1"];
|
||||
/// let list = List::new(items).style(Style::new().red().italic());
|
||||
/// ```
|
||||
///
|
||||
@@ -238,10 +268,13 @@ impl<'a> List<'a> {
|
||||
/// [`Stylize`]: crate::style::Stylize
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = ["Item 1"];
|
||||
/// use ratatui::{style::Stylize, widgets::List};
|
||||
///
|
||||
/// let items = ["Item 1"];
|
||||
/// let list = List::new(items).red().italic();
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
@@ -257,8 +290,9 @@ impl<'a> List<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = ["Item 1", "Item 2"];
|
||||
/// use ratatui::widgets::List;
|
||||
///
|
||||
/// let items = ["Item 1", "Item 2"];
|
||||
/// let list = List::new(items).highlight_symbol(">>");
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -281,10 +315,16 @@ impl<'a> List<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = ["Item 1", "Item 2"];
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// widgets::List,
|
||||
/// };
|
||||
///
|
||||
/// let items = ["Item 1", "Item 2"];
|
||||
/// let list = List::new(items).highlight_style(Style::new().red().italic());
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn highlight_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.highlight_style = style.into();
|
||||
@@ -323,8 +363,9 @@ impl<'a> List<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = ["Item 1"];
|
||||
/// use ratatui::widgets::{HighlightSpacing, List};
|
||||
///
|
||||
/// let items = ["Item 1"];
|
||||
/// let list = List::new(items).highlight_spacing(HighlightSpacing::Always);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -345,8 +386,9 @@ impl<'a> List<'a> {
|
||||
/// Bottom to top
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = ["Item 1"];
|
||||
/// use ratatui::widgets::{List, ListDirection};
|
||||
///
|
||||
/// let items = ["Item 1"];
|
||||
/// let list = List::new(items).direction(ListDirection::BottomToTop);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -364,8 +406,9 @@ impl<'a> List<'a> {
|
||||
/// A padding value of 1 will keep 1 item above and 1 item bellow visible if possible
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = ["Item 1"];
|
||||
/// use ratatui::widgets::List;
|
||||
///
|
||||
/// let items = ["Item 1"];
|
||||
/// let list = List::new(items).scroll_padding(1);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -423,6 +466,7 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
use crate::style::{Color, Modifier, Stylize};
|
||||
|
||||
#[test]
|
||||
fn collect_list_from_iterator() {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{
|
||||
prelude::{Buffer, Rect, StatefulWidget, StatefulWidgetRef, Widget, WidgetRef},
|
||||
widgets::{block::BlockExt, List, ListDirection, ListState},
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
widgets::{
|
||||
block::BlockExt, List, ListDirection, ListState, StatefulWidget, StatefulWidgetRef, Widget,
|
||||
WidgetRef,
|
||||
},
|
||||
};
|
||||
|
||||
impl Widget for List<'_> {
|
||||
@@ -273,8 +277,12 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
prelude::*,
|
||||
widgets::{Block, HighlightSpacing, ListItem},
|
||||
backend,
|
||||
layout::{Alignment, Rect},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
text::Line,
|
||||
widgets::{Block, HighlightSpacing, ListItem, StatefulWidget, Widget},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
#[fixture]
|
||||
|
||||
@@ -20,10 +20,15 @@
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// layout::Rect,
|
||||
/// widgets::{List, ListState},
|
||||
/// Frame,
|
||||
/// };
|
||||
///
|
||||
/// # fn ui(frame: &mut Frame) {
|
||||
/// # let area = Rect::default();
|
||||
/// # let items = ["Item 1"];
|
||||
/// let items = ["Item 1"];
|
||||
/// let list = List::new(items);
|
||||
///
|
||||
/// // This should be stored outside of the function in your application state.
|
||||
@@ -52,7 +57,8 @@ impl ListState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListState;
|
||||
///
|
||||
/// let state = ListState::default().with_offset(1);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -68,7 +74,8 @@ impl ListState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListState;
|
||||
///
|
||||
/// let state = ListState::default().with_selected(Some(1));
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -82,7 +89,8 @@ impl ListState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListState;
|
||||
///
|
||||
/// let state = ListState::default();
|
||||
/// assert_eq!(state.offset(), 0);
|
||||
/// ```
|
||||
@@ -95,7 +103,8 @@ impl ListState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListState;
|
||||
///
|
||||
/// let mut state = ListState::default();
|
||||
/// *state.offset_mut() = 1;
|
||||
/// ```
|
||||
@@ -110,8 +119,9 @@ impl ListState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let state = TableState::default();
|
||||
/// use ratatui::widgets::ListState;
|
||||
///
|
||||
/// let state = ListState::default();
|
||||
/// assert_eq!(state.selected(), None);
|
||||
/// ```
|
||||
pub const fn selected(&self) -> Option<usize> {
|
||||
@@ -125,7 +135,8 @@ impl ListState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListState;
|
||||
///
|
||||
/// let mut state = ListState::default();
|
||||
/// *state.selected_mut() = Some(1);
|
||||
/// ```
|
||||
@@ -140,7 +151,8 @@ impl ListState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListState;
|
||||
///
|
||||
/// let mut state = ListState::default();
|
||||
/// state.select(Some(1));
|
||||
/// ```
|
||||
@@ -159,7 +171,8 @@ impl ListState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListState;
|
||||
///
|
||||
/// let mut state = ListState::default();
|
||||
/// state.select_next();
|
||||
/// ```
|
||||
@@ -176,7 +189,8 @@ impl ListState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListState;
|
||||
///
|
||||
/// let mut state = ListState::default();
|
||||
/// state.select_previous();
|
||||
/// ```
|
||||
@@ -193,7 +207,8 @@ impl ListState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListState;
|
||||
///
|
||||
/// let mut state = ListState::default();
|
||||
/// state.select_first();
|
||||
/// ```
|
||||
@@ -209,7 +224,8 @@ impl ListState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListState;
|
||||
///
|
||||
/// let mut state = ListState::default();
|
||||
/// state.select_last();
|
||||
/// ```
|
||||
@@ -226,7 +242,8 @@ impl ListState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListState;
|
||||
///
|
||||
/// let mut state = ListState::default();
|
||||
/// state.scroll_down_by(4);
|
||||
/// ```
|
||||
@@ -244,7 +261,8 @@ impl ListState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::ListState;
|
||||
///
|
||||
/// let mut state = ListState::default();
|
||||
/// state.scroll_up_by(4);
|
||||
/// ```
|
||||
|
||||
236
src/widgets/logo.rs
Normal file
236
src/widgets/logo.rs
Normal file
@@ -0,0 +1,236 @@
|
||||
use indoc::indoc;
|
||||
|
||||
use crate::{buffer::Buffer, layout::Rect, text::Text, widgets::Widget};
|
||||
|
||||
/// A widget that renders the Ratatui logo
|
||||
///
|
||||
/// The Ratatui logo takes up two lines of text and comes in two sizes: `Tiny` and `Small`. This may
|
||||
/// be used in an application's help or about screen to show that it is powered by Ratatui.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// The [Ratatui-logo] example demonstrates how to use the `RatatuiLogo` widget. This can be run by
|
||||
/// cloning the Ratatui repository and then running the following command with an optional size
|
||||
/// argument:
|
||||
///
|
||||
/// ```shell
|
||||
/// cargo run --example ratatui-logo [size]
|
||||
/// ```
|
||||
///
|
||||
/// [Ratatui-logo]: https://github.com/ratatui/ratatui/blob/main/examples/ratatui-logo.rs
|
||||
///
|
||||
/// ## Tiny (default, 2x15 characters)
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::widgets::RatatuiLogo;
|
||||
///
|
||||
/// # fn draw(frame: &mut ratatui::Frame) {
|
||||
/// frame.render_widget(RatatuiLogo::tiny(), frame.area());
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Renders:
|
||||
///
|
||||
/// ```text
|
||||
/// ▛▚▗▀▖▜▘▞▚▝▛▐ ▌▌
|
||||
/// ▛▚▐▀▌▐ ▛▜ ▌▝▄▘▌
|
||||
/// ```
|
||||
///
|
||||
/// ## Small (2x27 characters)
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::widgets::RatatuiLogo;
|
||||
///
|
||||
/// # fn draw(frame: &mut ratatui::Frame) {
|
||||
/// frame.render_widget(RatatuiLogo::small(), frame.area());
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Renders:
|
||||
///
|
||||
/// ```text
|
||||
/// █▀▀▄ ▄▀▀▄▝▜▛▘▄▀▀▄▝▜▛▘█ █ █
|
||||
/// █▀▀▄ █▀▀█ ▐▌ █▀▀█ ▐▌ ▀▄▄▀ █
|
||||
/// ```
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct RatatuiLogo {
|
||||
size: Size,
|
||||
}
|
||||
|
||||
/// The size of the logo
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
pub enum Size {
|
||||
/// A tiny logo
|
||||
///
|
||||
/// The default size of the logo (2x15 characters)
|
||||
///
|
||||
/// ```text
|
||||
/// ▛▚▗▀▖▜▘▞▚▝▛▐ ▌▌
|
||||
/// ▛▚▐▀▌▐ ▛▜ ▌▝▄▘▌
|
||||
/// ```
|
||||
#[default]
|
||||
Tiny,
|
||||
/// A small logo
|
||||
///
|
||||
/// A slightly larger version of the logo (2x27 characters)
|
||||
///
|
||||
/// ```text
|
||||
/// █▀▀▄ ▄▀▀▄▝▜▛▘▄▀▀▄▝▜▛▘█ █ █
|
||||
/// █▀▀▄ █▀▀█ ▐▌ █▀▀█ ▐▌ ▀▄▄▀ █
|
||||
/// ```
|
||||
Small,
|
||||
}
|
||||
|
||||
impl RatatuiLogo {
|
||||
/// Create a new Ratatui logo widget
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::widgets::{RatatuiLogo, RatatuiLogoSize};
|
||||
///
|
||||
/// let logo = RatatuiLogo::new(RatatuiLogoSize::Tiny);
|
||||
/// ```
|
||||
pub const fn new(size: Size) -> Self {
|
||||
Self { size }
|
||||
}
|
||||
|
||||
/// Set the size of the logo
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::widgets::{RatatuiLogo, RatatuiLogoSize};
|
||||
///
|
||||
/// let logo = RatatuiLogo::default().size(RatatuiLogoSize::Small);
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub const fn size(self, size: Size) -> Self {
|
||||
let _ = self;
|
||||
Self { size }
|
||||
}
|
||||
|
||||
/// Create a new Ratatui logo widget with a tiny size
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::widgets::RatatuiLogo;
|
||||
///
|
||||
/// let logo = RatatuiLogo::tiny();
|
||||
/// ```
|
||||
pub const fn tiny() -> Self {
|
||||
Self::new(Size::Tiny)
|
||||
}
|
||||
|
||||
/// Create a new Ratatui logo widget with a small size
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::widgets::RatatuiLogo;
|
||||
///
|
||||
/// let logo = RatatuiLogo::small();
|
||||
/// ```
|
||||
pub const fn small() -> Self {
|
||||
Self::new(Size::Small)
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for RatatuiLogo {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let logo = self.size.as_str();
|
||||
Text::raw(logo).render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Size {
|
||||
const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Tiny => Self::tiny(),
|
||||
Self::Small => Self::small(),
|
||||
}
|
||||
}
|
||||
|
||||
const fn tiny() -> &'static str {
|
||||
indoc! {"
|
||||
▛▚▗▀▖▜▘▞▚▝▛▐ ▌▌
|
||||
▛▚▐▀▌▐ ▛▜ ▌▝▄▘▌
|
||||
"}
|
||||
}
|
||||
|
||||
const fn small() -> &'static str {
|
||||
indoc! {"
|
||||
█▀▀▄ ▄▀▀▄▝▜▛▘▄▀▀▄▝▜▛▘█ █ █
|
||||
█▀▀▄ █▀▀█ ▐▌ █▀▀█ ▐▌ ▀▄▄▀ █
|
||||
"}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[rstest]
|
||||
#[case::tiny(Size::Tiny)]
|
||||
#[case::small(Size::Small)]
|
||||
fn new_size(#[case] size: Size) {
|
||||
let logo = RatatuiLogo::new(size);
|
||||
assert_eq!(logo.size, size);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_logo_is_tiny() {
|
||||
let logo = RatatuiLogo::default();
|
||||
assert_eq!(logo.size, Size::Tiny);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_logo_size_to_small() {
|
||||
let logo = RatatuiLogo::default().size(Size::Small);
|
||||
assert_eq!(logo.size, Size::Small);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tiny_logo_constant() {
|
||||
let logo = RatatuiLogo::tiny();
|
||||
assert_eq!(logo.size, Size::Tiny);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn small_logo_constant() {
|
||||
let logo = RatatuiLogo::small();
|
||||
assert_eq!(logo.size, Size::Small);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn render_tiny() {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 2));
|
||||
RatatuiLogo::tiny().render(buf.area, &mut buf);
|
||||
assert_eq!(
|
||||
buf,
|
||||
Buffer::with_lines([
|
||||
"▛▚▗▀▖▜▘▞▚▝▛▐ ▌▌",
|
||||
"▛▚▐▀▌▐ ▛▜ ▌▝▄▘▌",
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn render_small() {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 27, 2));
|
||||
RatatuiLogo::small().render(buf.area, &mut buf);
|
||||
assert_eq!(
|
||||
buf,
|
||||
Buffer::with_lines([
|
||||
"█▀▀▄ ▄▀▀▄▝▜▛▘▄▀▀▄▝▜▛▘█ █ █",
|
||||
"█▀▀▄ █▀▀█ ▐▌ █▀▀█ ▐▌ ▀▄▄▀ █",
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{
|
||||
prelude::*,
|
||||
style::Styled,
|
||||
text::StyledGrapheme,
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Position, Rect},
|
||||
style::{Style, Styled},
|
||||
text::{Line, StyledGrapheme, Text},
|
||||
widgets::{
|
||||
block::BlockExt,
|
||||
reflow::{LineComposer, LineTruncator, WordWrapper, WrappedLine},
|
||||
Block,
|
||||
Block, Widget, WidgetRef,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -59,7 +61,12 @@ const fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Align
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// layout::Alignment,
|
||||
/// style::{Style, Stylize},
|
||||
/// text::{Line, Span},
|
||||
/// widgets::{Block, Paragraph, Wrap},
|
||||
/// };
|
||||
///
|
||||
/// let text = vec![
|
||||
/// Line::from(vec![
|
||||
@@ -76,6 +83,8 @@ const fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Align
|
||||
/// .alignment(Alignment::Center)
|
||||
/// .wrap(Wrap { trim: true });
|
||||
/// ```
|
||||
///
|
||||
/// [`Span`]: crate::text::Span
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Paragraph<'a> {
|
||||
/// A block to wrap the widget in
|
||||
@@ -97,7 +106,10 @@ pub struct Paragraph<'a> {
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// text::Text,
|
||||
/// widgets::{Paragraph, Wrap},
|
||||
/// };
|
||||
///
|
||||
/// let bullet_points = Text::from(
|
||||
/// r#"Some indented points:
|
||||
@@ -139,7 +151,12 @@ impl<'a> Paragraph<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::{Line, Text},
|
||||
/// widgets::Paragraph,
|
||||
/// };
|
||||
///
|
||||
/// let paragraph = Paragraph::new("Hello, world!");
|
||||
/// let paragraph = Paragraph::new(String::from("Hello, world!"));
|
||||
/// let paragraph = Paragraph::new(Text::raw("Hello, world!"));
|
||||
@@ -165,7 +182,8 @@ impl<'a> Paragraph<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::{Block, Paragraph};
|
||||
///
|
||||
/// let paragraph = Paragraph::new("Hello, world!").block(Block::bordered().title("Paragraph"));
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -185,9 +203,15 @@ impl<'a> Paragraph<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// widgets::Paragraph,
|
||||
/// };
|
||||
///
|
||||
/// let paragraph = Paragraph::new("Hello, world!").style(Style::new().red().on_white());
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
@@ -201,7 +225,8 @@ impl<'a> Paragraph<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::{Paragraph, Wrap};
|
||||
///
|
||||
/// let paragraph = Paragraph::new("Hello, world!").wrap(Wrap { trim: true });
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -238,7 +263,8 @@ impl<'a> Paragraph<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{layout::Alignment, widgets::Paragraph};
|
||||
///
|
||||
/// let paragraph = Paragraph::new("Hello World").alignment(Alignment::Center);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -254,7 +280,8 @@ impl<'a> Paragraph<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::Paragraph;
|
||||
///
|
||||
/// let paragraph = Paragraph::new("Hello World").left_aligned();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -269,7 +296,8 @@ impl<'a> Paragraph<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::Paragraph;
|
||||
///
|
||||
/// let paragraph = Paragraph::new("Hello World").centered();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -284,7 +312,8 @@ impl<'a> Paragraph<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::Paragraph;
|
||||
///
|
||||
/// let paragraph = Paragraph::new("Hello World").right_aligned();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -305,7 +334,8 @@ impl<'a> Paragraph<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{widgets::{Paragraph, Wrap}};
|
||||
///
|
||||
/// let paragraph = Paragraph::new("Hello World")
|
||||
/// .wrap(Wrap { trim: false });
|
||||
/// assert_eq!(paragraph.line_count(20), 1);
|
||||
@@ -359,7 +389,8 @@ impl<'a> Paragraph<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{widgets::Paragraph};
|
||||
///
|
||||
/// let paragraph = Paragraph::new("Hello World");
|
||||
/// assert_eq!(paragraph.line_width(), 11);
|
||||
///
|
||||
@@ -473,7 +504,12 @@ mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
backend::TestBackend,
|
||||
widgets::{block::Position, Borders},
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Rect},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
text::{Line, Span, Text},
|
||||
widgets::{block::Position, Borders, Widget},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
/// Tests the [`Paragraph`] widget against the expected [`Buffer`] by rendering it onto an equal
|
||||
|
||||
@@ -12,8 +12,11 @@ use strum::{Display, EnumString};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{
|
||||
prelude::*,
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::Style,
|
||||
symbols::scrollbar::{Set, DOUBLE_HORIZONTAL, DOUBLE_VERTICAL},
|
||||
widgets::StatefulWidget,
|
||||
};
|
||||
|
||||
/// A widget to display a scrollbar
|
||||
@@ -39,7 +42,15 @@ use crate::{
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// layout::{Margin, Rect},
|
||||
/// text::Line,
|
||||
/// widgets::{
|
||||
/// Block, Borders, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState,
|
||||
/// StatefulWidget,
|
||||
/// },
|
||||
/// Frame,
|
||||
/// };
|
||||
///
|
||||
/// # fn render_paragraph_with_scrollbar(frame: &mut Frame, area: Rect) {
|
||||
/// let vertical_scroll = 0; // from app state
|
||||
@@ -254,6 +265,8 @@ impl<'a> Scrollbar<'a> {
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// This is a fluent setter method which must be chained or used as it consumes self
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn thumb_style<S: Into<Style>>(mut self, thumb_style: S) -> Self {
|
||||
self.thumb_style = thumb_style.into();
|
||||
@@ -279,6 +292,8 @@ impl<'a> Scrollbar<'a> {
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// This is a fluent setter method which must be chained or used as it consumes self
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn track_style<S: Into<Style>>(mut self, track_style: S) -> Self {
|
||||
self.track_style = track_style.into();
|
||||
@@ -304,6 +319,8 @@ impl<'a> Scrollbar<'a> {
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// This is a fluent setter method which must be chained or used as it consumes self
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn begin_style<S: Into<Style>>(mut self, begin_style: S) -> Self {
|
||||
self.begin_style = begin_style.into();
|
||||
@@ -329,6 +346,8 @@ impl<'a> Scrollbar<'a> {
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// This is a fluent setter method which must be chained or used as it consumes self
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn end_style<S: Into<Style>>(mut self, end_style: S) -> Self {
|
||||
self.end_style = end_style.into();
|
||||
@@ -382,6 +401,8 @@ impl<'a> Scrollbar<'a> {
|
||||
/// ```
|
||||
///
|
||||
/// This is a fluent setter method which must be chained or used as it consumes self
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
let style = style.into();
|
||||
@@ -621,6 +642,7 @@ mod tests {
|
||||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
use crate::{text::Text, widgets::Widget};
|
||||
|
||||
#[test]
|
||||
fn scroll_direction_to_string() {
|
||||
|
||||
@@ -2,7 +2,13 @@ use std::cmp::min;
|
||||
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
use crate::{prelude::*, style::Styled, widgets::Block};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Style, Styled},
|
||||
symbols::{self},
|
||||
widgets::{block::BlockExt, Block, Widget, WidgetRef},
|
||||
};
|
||||
|
||||
/// Widget to render a sparkline over one or more lines.
|
||||
///
|
||||
@@ -21,7 +27,10 @@ use crate::{prelude::*, style::Styled, widgets::Block};
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// widgets::{Block, RenderDirection, Sparkline},
|
||||
/// };
|
||||
///
|
||||
/// Sparkline::default()
|
||||
/// .block(Block::bordered().title("Sparkline"))
|
||||
@@ -73,6 +82,8 @@ impl<'a> Sparkline<'a> {
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
///
|
||||
/// The foreground corresponds to the bars while the background is everything else.
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
@@ -84,7 +95,8 @@ impl<'a> Sparkline<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{layout::Rect, widgets::Sparkline, Frame};
|
||||
///
|
||||
/// # fn ui(frame: &mut Frame) {
|
||||
/// # let area = Rect::default();
|
||||
/// let sparkline = Sparkline::default().data(&[1, 2, 3]);
|
||||
@@ -211,7 +223,10 @@ mod tests {
|
||||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
use crate::buffer::Cell;
|
||||
use crate::{
|
||||
buffer::Cell,
|
||||
style::{Color, Modifier, Stylize},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn render_direction_to_string() {
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
use crate::{prelude::*, style::Styled};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Style, Styled},
|
||||
text::Text,
|
||||
widgets::WidgetRef,
|
||||
};
|
||||
|
||||
/// A [`Cell`] contains the [`Text`] to be displayed in a [`Row`] of a [`Table`].
|
||||
///
|
||||
@@ -16,13 +22,17 @@ use crate::{prelude::*, style::Styled};
|
||||
/// ```rust
|
||||
/// use std::borrow::Cow;
|
||||
///
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::Stylize,
|
||||
/// text::{Line, Span, Text},
|
||||
/// widgets::Cell,
|
||||
/// };
|
||||
///
|
||||
/// Cell::from("simple string");
|
||||
/// Cell::from(Span::from("span"));
|
||||
/// Cell::from(Line::from(vec![
|
||||
/// Span::raw("a vec of "),
|
||||
/// Span::styled("spans", Style::default().add_modifier(Modifier::BOLD)),
|
||||
/// Span::from("a vec of "),
|
||||
/// Span::from("spans").bold(),
|
||||
/// ]));
|
||||
/// Cell::from(Text::from("a text"));
|
||||
/// Cell::from(Text::from(Cow::Borrowed("hello")));
|
||||
@@ -32,12 +42,14 @@ use crate::{prelude::*, style::Styled};
|
||||
/// to set the style of the cell concisely.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{style::Stylize, widgets::Cell};
|
||||
///
|
||||
/// Cell::new("Cell 1").red().italic();
|
||||
/// ```
|
||||
///
|
||||
/// [`Row`]: super::Row
|
||||
/// [`Table`]: super::Table
|
||||
/// [`Row`]: crate::widgets::Row
|
||||
/// [`Table`]: crate::widgets::Table
|
||||
/// [`Stylize`]: crate::style::Stylize
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Cell<'a> {
|
||||
content: Text<'a>,
|
||||
@@ -52,12 +64,17 @@ impl<'a> Cell<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::Stylize,
|
||||
/// text::{Line, Span, Text},
|
||||
/// widgets::Cell,
|
||||
/// };
|
||||
///
|
||||
/// Cell::new("simple string");
|
||||
/// Cell::new(Span::from("span"));
|
||||
/// Cell::new(Line::from(vec![
|
||||
/// Span::raw("a vec of "),
|
||||
/// Span::styled("spans", Style::default().add_modifier(Modifier::BOLD)),
|
||||
/// Span::from("spans").bold(),
|
||||
/// ]));
|
||||
/// Cell::new(Text::from("a text"));
|
||||
/// ```
|
||||
@@ -80,12 +97,17 @@ impl<'a> Cell<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::Stylize,
|
||||
/// text::{Line, Span, Text},
|
||||
/// widgets::Cell,
|
||||
/// };
|
||||
///
|
||||
/// Cell::default().content("simple string");
|
||||
/// Cell::default().content(Span::from("span"));
|
||||
/// Cell::default().content(Line::from(vec![
|
||||
/// Span::raw("a vec of "),
|
||||
/// Span::styled("spans", Style::new().bold()),
|
||||
/// Span::from("spans").bold(),
|
||||
/// ]));
|
||||
/// Cell::default().content(Text::from("a text"));
|
||||
/// ```
|
||||
@@ -111,7 +133,11 @@ impl<'a> Cell<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// widgets::Cell,
|
||||
/// };
|
||||
///
|
||||
/// Cell::new("Cell 1").style(Style::new().red().italic());
|
||||
/// ```
|
||||
///
|
||||
@@ -119,11 +145,14 @@ impl<'a> Cell<'a> {
|
||||
/// the [`Stylize`] trait to set the style of the widget more concisely.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{style::Stylize, widgets::Cell};
|
||||
///
|
||||
/// Cell::new("Cell 1").red().italic();
|
||||
/// ```
|
||||
///
|
||||
/// [`Row`]: super::Row
|
||||
/// [`Row`]: crate::widgets::Row
|
||||
/// [`Color`]: crate::style::Color
|
||||
/// [`Stylize`]: crate::style::Stylize
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
@@ -165,6 +194,7 @@ impl<'a> Styled for Cell<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::style::{Color, Modifier, Stylize};
|
||||
|
||||
#[test]
|
||||
fn new() {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use super::Cell;
|
||||
use crate::{prelude::*, style::Styled};
|
||||
use crate::{
|
||||
style::{Style, Styled},
|
||||
widgets::table::Cell,
|
||||
};
|
||||
|
||||
/// A single row of data to be displayed in a [`Table`] widget.
|
||||
///
|
||||
@@ -16,7 +18,7 @@ use crate::{prelude::*, style::Styled};
|
||||
/// You can create `Row`s from simple strings.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::Row;
|
||||
///
|
||||
/// Row::new(vec!["Cell1", "Cell2", "Cell3"]);
|
||||
/// ```
|
||||
@@ -24,11 +26,14 @@ use crate::{prelude::*, style::Styled};
|
||||
/// If you need a bit more control over individual cells, you can explicitly create [`Cell`]s:
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::Stylize,
|
||||
/// widgets::{Cell, Row},
|
||||
/// };
|
||||
///
|
||||
/// Row::new(vec![
|
||||
/// Cell::from("Cell1"),
|
||||
/// Cell::from("Cell2").style(Style::default().fg(Color::Yellow)),
|
||||
/// Cell::from("Cell2").red().italic(),
|
||||
/// ]);
|
||||
/// ```
|
||||
///
|
||||
@@ -37,7 +42,7 @@ use crate::{prelude::*, style::Styled};
|
||||
/// ```rust
|
||||
/// use std::borrow::Cow;
|
||||
///
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::{Cell, Row};
|
||||
///
|
||||
/// Row::new(vec![
|
||||
/// Cow::Borrowed("hello"),
|
||||
@@ -57,12 +62,15 @@ use crate::{prelude::*, style::Styled};
|
||||
/// to set the style of the row concisely.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{style::Stylize, widgets::Row};
|
||||
///
|
||||
/// let cells = vec!["Cell1", "Cell2", "Cell3"];
|
||||
/// Row::new(cells).red().italic();
|
||||
/// ```
|
||||
///
|
||||
/// [`Table`]: super::Table
|
||||
/// [`Table`]: crate::widgets::Table
|
||||
/// [`Text`]: crate::text::Text
|
||||
/// [`Stylize`]: crate::style::Stylize
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Row<'a> {
|
||||
pub(crate) cells: Vec<Cell<'a>>,
|
||||
@@ -81,7 +89,8 @@ impl<'a> Row<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::{Cell, Row};
|
||||
///
|
||||
/// let row = Row::new(vec!["Cell 1", "Cell 2", "Cell 3"]);
|
||||
/// let row = Row::new(vec![
|
||||
/// Cell::new("Cell 1"),
|
||||
@@ -111,7 +120,8 @@ impl<'a> Row<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::{Cell, Row};
|
||||
///
|
||||
/// let row = Row::default().cells(vec!["Cell 1", "Cell 2", "Cell 3"]);
|
||||
/// let row = Row::default().cells(vec![
|
||||
/// Cell::new("Cell 1"),
|
||||
@@ -140,7 +150,8 @@ impl<'a> Row<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::Row;
|
||||
///
|
||||
/// let cells = vec!["Cell 1\nline 2", "Cell 2", "Cell 3"];
|
||||
/// let row = Row::new(cells).height(2);
|
||||
/// ```
|
||||
@@ -159,8 +170,9 @@ impl<'a> Row<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let cells = vec!["Cell 1", "Cell 2", "Cell 3"];
|
||||
/// use ratatui::widgets::Row;
|
||||
/// let cells = vec!["Cell 1", "Cell 2", "Cell 3"];
|
||||
///
|
||||
/// let row = Row::default().top_margin(1);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -178,8 +190,9 @@ impl<'a> Row<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let cells = vec!["Cell 1", "Cell 2", "Cell 3"];
|
||||
/// use ratatui::widgets::Row;
|
||||
///
|
||||
/// let cells = vec!["Cell 1", "Cell 2", "Cell 3"];
|
||||
/// let row = Row::default().bottom_margin(1);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -201,7 +214,10 @@ impl<'a> Row<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// widgets::Row,
|
||||
/// };
|
||||
/// let cells = vec!["Cell 1", "Cell 2", "Cell 3"];
|
||||
/// let row = Row::new(cells).style(Style::new().red().italic());
|
||||
/// ```
|
||||
@@ -210,10 +226,15 @@ impl<'a> Row<'a> {
|
||||
/// the [`Stylize`] trait to set the style of the widget more concisely.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{style::Stylize, widgets::Row};
|
||||
///
|
||||
/// let cells = vec!["Cell 1", "Cell 2", "Cell 3"];
|
||||
/// let row = Row::new(cells).red().italic();
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
/// [`Stylize`]: crate::style::Stylize
|
||||
/// [`Text`]: crate::text::Text
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
@@ -257,6 +278,7 @@ mod tests {
|
||||
use std::vec;
|
||||
|
||||
use super::*;
|
||||
use crate::style::{Color, Modifier, Stylize};
|
||||
|
||||
#[test]
|
||||
fn new() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,19 @@
|
||||
/// State of a [`Table`] widget
|
||||
///
|
||||
/// This state can be used to scroll through the rows and select one of them. When the table is
|
||||
/// rendered as a stateful widget, the selected row will be highlighted and the table will be
|
||||
/// shifted to ensure that the selected row is visible. This will modify the [`TableState`] object
|
||||
/// passed to the [`Frame::render_stateful_widget`] method.
|
||||
/// rendered as a stateful widget, the selected row, column and cell will be highlighted and the
|
||||
/// table will be shifted to ensure that the selected row is visible. This will modify the
|
||||
/// [`TableState`] object passed to the [`Frame::render_stateful_widget`] method.
|
||||
///
|
||||
/// The state consists of two fields:
|
||||
/// - [`offset`]: the index of the first row to be displayed
|
||||
/// - [`selected`]: the index of the selected row, which can be `None` if no row is selected
|
||||
/// - [`selected_column`]: the index of the selected column, which can be `None` if no column is
|
||||
/// selected
|
||||
///
|
||||
/// [`offset`]: TableState::offset()
|
||||
/// [`selected`]: TableState::selected()
|
||||
/// [`selected_column`]: TableState::selected_column()
|
||||
///
|
||||
/// See the `table` example and the `recipe` and `traceroute` tabs in the demo2 example in the
|
||||
/// [Examples] directory for a more in depth example of the various configuration options and for
|
||||
@@ -21,11 +24,16 @@
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// layout::{Constraint, Rect},
|
||||
/// widgets::{Row, Table, TableState},
|
||||
/// Frame,
|
||||
/// };
|
||||
///
|
||||
/// # fn ui(frame: &mut Frame) {
|
||||
/// # let area = Rect::default();
|
||||
/// # let rows = [Row::new(vec!["Cell1", "Cell2"])];
|
||||
/// # let widths = [Constraint::Length(5), Constraint::Length(5)];
|
||||
/// let rows = [Row::new(vec!["Cell1", "Cell2"])];
|
||||
/// let widths = [Constraint::Length(5), Constraint::Length(5)];
|
||||
/// let table = Table::new(rows, widths).widths(widths);
|
||||
///
|
||||
/// // Note: TableState should be stored in your application state (not constructed in your render
|
||||
@@ -33,6 +41,7 @@
|
||||
/// let mut table_state = TableState::default();
|
||||
/// *table_state.offset_mut() = 1; // display the second row and onwards
|
||||
/// table_state.select(Some(3)); // select the forth row (0-indexed)
|
||||
/// table_state.select_column(Some(2)); // select the third column (0-indexed)
|
||||
///
|
||||
/// frame.render_stateful_widget(table, area, &mut table_state);
|
||||
/// # }
|
||||
@@ -49,6 +58,7 @@
|
||||
pub struct TableState {
|
||||
pub(crate) offset: usize,
|
||||
pub(crate) selected: Option<usize>,
|
||||
pub(crate) selected_column: Option<usize>,
|
||||
}
|
||||
|
||||
impl TableState {
|
||||
@@ -57,13 +67,15 @@ impl TableState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::TableState;
|
||||
///
|
||||
/// let state = TableState::new();
|
||||
/// ```
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
offset: 0,
|
||||
selected: None,
|
||||
selected_column: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +86,8 @@ impl TableState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::TableState;
|
||||
///
|
||||
/// let state = TableState::new().with_offset(1);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -90,7 +103,8 @@ impl TableState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::TableState;
|
||||
///
|
||||
/// let state = TableState::new().with_selected(Some(1));
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -102,12 +116,58 @@ impl TableState {
|
||||
self
|
||||
}
|
||||
|
||||
/// Index of the first row to be displayed
|
||||
/// Sets the index of the selected column
|
||||
///
|
||||
/// This is a fluent setter method which must be chained or used as it consumes self
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let state = TableState::new().with_selected_column(Some(1));
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn with_selected_column<T>(mut self, selected: T) -> Self
|
||||
where
|
||||
T: Into<Option<usize>>,
|
||||
{
|
||||
self.selected_column = selected.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the indexes of the selected cell
|
||||
///
|
||||
/// This is a fluent setter method which must be chained or used as it consumes self
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let state = TableState::new().with_selected_cell(Some((1, 5)));
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn with_selected_cell<T>(mut self, selected: T) -> Self
|
||||
where
|
||||
T: Into<Option<(usize, usize)>>,
|
||||
{
|
||||
if let Some((r, c)) = selected.into() {
|
||||
self.selected = Some(r);
|
||||
self.selected_column = Some(c);
|
||||
} else {
|
||||
self.selected = None;
|
||||
self.selected_column = None;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Index of the first row to be displayed
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::widgets::TableState;
|
||||
///
|
||||
/// let state = TableState::new();
|
||||
/// assert_eq!(state.offset(), 0);
|
||||
/// ```
|
||||
@@ -120,7 +180,8 @@ impl TableState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::TableState;
|
||||
///
|
||||
/// let mut state = TableState::default();
|
||||
/// *state.offset_mut() = 1;
|
||||
/// ```
|
||||
@@ -135,7 +196,8 @@ impl TableState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::TableState;
|
||||
///
|
||||
/// let state = TableState::new();
|
||||
/// assert_eq!(state.selected(), None);
|
||||
/// ```
|
||||
@@ -143,6 +205,39 @@ impl TableState {
|
||||
self.selected
|
||||
}
|
||||
|
||||
/// Index of the selected column
|
||||
///
|
||||
/// Returns `None` if no column is selected
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let state = TableState::new();
|
||||
/// assert_eq!(state.selected_column(), None);
|
||||
/// ```
|
||||
pub const fn selected_column(&self) -> Option<usize> {
|
||||
self.selected_column
|
||||
}
|
||||
|
||||
/// Indexes of the selected cell
|
||||
///
|
||||
/// Returns `None` if no cell is selected
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let state = TableState::new();
|
||||
/// assert_eq!(state.selected_cell(), None);
|
||||
/// ```
|
||||
pub const fn selected_cell(&self) -> Option<(usize, usize)> {
|
||||
if let (Some(r), Some(c)) = (self.selected, self.selected_column) {
|
||||
return Some((r, c));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Mutable reference to the index of the selected row
|
||||
///
|
||||
/// Returns `None` if no row is selected
|
||||
@@ -150,7 +245,8 @@ impl TableState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::TableState;
|
||||
///
|
||||
/// let mut state = TableState::default();
|
||||
/// *state.selected_mut() = Some(1);
|
||||
/// ```
|
||||
@@ -158,6 +254,21 @@ impl TableState {
|
||||
&mut self.selected
|
||||
}
|
||||
|
||||
/// Mutable reference to the index of the selected column
|
||||
///
|
||||
/// Returns `None` if no column is selected
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let mut state = TableState::default();
|
||||
/// *state.selected_column_mut() = Some(1);
|
||||
/// ```
|
||||
pub fn selected_column_mut(&mut self) -> &mut Option<usize> {
|
||||
&mut self.selected_column
|
||||
}
|
||||
|
||||
/// Sets the index of the selected row
|
||||
///
|
||||
/// Set to `None` if no row is selected. This will also reset the offset to `0`.
|
||||
@@ -165,7 +276,8 @@ impl TableState {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::TableState;
|
||||
///
|
||||
/// let mut state = TableState::default();
|
||||
/// state.select(Some(1));
|
||||
/// ```
|
||||
@@ -176,16 +288,52 @@ impl TableState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Selects the next item or the first one if no item is selected
|
||||
///
|
||||
/// Note: until the table is rendered, the number of items is not known, so the index is set to
|
||||
/// `0` and will be corrected when the table is rendered
|
||||
/// Sets the index of the selected column
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let mut state = TableState::default();
|
||||
/// state.select_column(Some(1));
|
||||
/// ```
|
||||
pub fn select_column(&mut self, index: Option<usize>) {
|
||||
self.selected_column = index;
|
||||
}
|
||||
|
||||
/// Sets the indexes of the selected cell
|
||||
///
|
||||
/// Set to `None` if no cell is selected. This will also reset the row offset to `0`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let mut state = TableState::default();
|
||||
/// state.select_cell(Some((1, 5)));
|
||||
/// ```
|
||||
pub fn select_cell(&mut self, indexes: Option<(usize, usize)>) {
|
||||
if let Some((r, c)) = indexes {
|
||||
self.selected = Some(r);
|
||||
self.selected_column = Some(c);
|
||||
} else {
|
||||
self.offset = 0;
|
||||
self.selected = None;
|
||||
self.selected_column = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Selects the next row or the first one if no row is selected
|
||||
///
|
||||
/// Note: until the table is rendered, the number of rows is not known, so the index is set to
|
||||
/// `0` and will be corrected when the table is rendered
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::widgets::TableState;
|
||||
///
|
||||
/// let mut state = TableState::default();
|
||||
/// state.select_next();
|
||||
/// ```
|
||||
pub fn select_next(&mut self) {
|
||||
@@ -193,16 +341,34 @@ impl TableState {
|
||||
self.select(Some(next));
|
||||
}
|
||||
|
||||
/// Selects the previous item or the last one if no item is selected
|
||||
/// Selects the next column or the first one if no column is selected
|
||||
///
|
||||
/// Note: until the table is rendered, the number of items is not known, so the index is set to
|
||||
/// `usize::MAX` and will be corrected when the table is rendered
|
||||
/// Note: until the table is rendered, the number of columns is not known, so the index is set
|
||||
/// to `0` and will be corrected when the table is rendered
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let mut state = TableState::default();
|
||||
/// state.select_next_column();
|
||||
/// ```
|
||||
pub fn select_next_column(&mut self) {
|
||||
let next = self.selected_column.map_or(0, |i| i.saturating_add(1));
|
||||
self.select_column(Some(next));
|
||||
}
|
||||
|
||||
/// Selects the previous row or the last one if no item is selected
|
||||
///
|
||||
/// Note: until the table is rendered, the number of rows is not known, so the index is set to
|
||||
/// `usize::MAX` and will be corrected when the table is rendered
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::widgets::TableState;
|
||||
///
|
||||
/// let mut state = TableState::default();
|
||||
/// state.select_previous();
|
||||
/// ```
|
||||
pub fn select_previous(&mut self) {
|
||||
@@ -210,48 +376,102 @@ impl TableState {
|
||||
self.select(Some(previous));
|
||||
}
|
||||
|
||||
/// Selects the first item
|
||||
/// Selects the previous column or the last one if no column is selected
|
||||
///
|
||||
/// Note: until the table is rendered, the number of items is not known, so the index is set to
|
||||
/// `0` and will be corrected when the table is rendered
|
||||
/// Note: until the table is rendered, the number of columns is not known, so the index is set
|
||||
/// to `usize::MAX` and will be corrected when the table is rendered
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let mut state = TableState::default();
|
||||
/// state.select_previous_column();
|
||||
/// ```
|
||||
pub fn select_previous_column(&mut self) {
|
||||
let previous = self
|
||||
.selected_column
|
||||
.map_or(usize::MAX, |i| i.saturating_sub(1));
|
||||
self.select_column(Some(previous));
|
||||
}
|
||||
|
||||
/// Selects the first row
|
||||
///
|
||||
/// Note: until the table is rendered, the number of rows is not known, so the index is set to
|
||||
/// `0` and will be corrected when the table is rendered
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::widgets::TableState;
|
||||
///
|
||||
/// let mut state = TableState::default();
|
||||
/// state.select_first();
|
||||
/// ```
|
||||
pub fn select_first(&mut self) {
|
||||
self.select(Some(0));
|
||||
}
|
||||
|
||||
/// Selects the last item
|
||||
/// Selects the first column
|
||||
///
|
||||
/// Note: until the table is rendered, the number of items is not known, so the index is set to
|
||||
/// Note: until the table is rendered, the number of columns is not known, so the index is set
|
||||
/// to `0` and will be corrected when the table is rendered
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let mut state = TableState::default();
|
||||
/// state.select_first_column();
|
||||
/// ```
|
||||
pub fn select_first_column(&mut self) {
|
||||
self.select_column(Some(0));
|
||||
}
|
||||
|
||||
/// Selects the last row
|
||||
///
|
||||
/// Note: until the table is rendered, the number of rows is not known, so the index is set to
|
||||
/// `usize::MAX` and will be corrected when the table is rendered
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::widgets::TableState;
|
||||
///
|
||||
/// let mut state = TableState::default();
|
||||
/// state.select_last();
|
||||
/// ```
|
||||
pub fn select_last(&mut self) {
|
||||
self.select(Some(usize::MAX));
|
||||
}
|
||||
|
||||
/// Selects the last column
|
||||
///
|
||||
/// Note: until the table is rendered, the number of columns is not known, so the index is set
|
||||
/// to `usize::MAX` and will be corrected when the table is rendered
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let mut state = TableState::default();
|
||||
/// state.select_last();
|
||||
/// ```
|
||||
pub fn select_last(&mut self) {
|
||||
self.select(Some(usize::MAX));
|
||||
pub fn select_last_column(&mut self) {
|
||||
self.select_column(Some(usize::MAX));
|
||||
}
|
||||
|
||||
/// Scrolls down by a specified `amount` in the table.
|
||||
///
|
||||
/// This method updates the selected index by moving it down by the given `amount`.
|
||||
/// If the `amount` causes the index to go out of bounds (i.e., if the index is greater than
|
||||
/// the length of the table), the last item in the table will be selected.
|
||||
/// the number of rows in the table), the last row in the table will be selected.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::widgets::TableState;
|
||||
///
|
||||
/// let mut state = TableState::default();
|
||||
/// state.scroll_down_by(4);
|
||||
/// ```
|
||||
@@ -264,6 +484,43 @@ impl TableState {
|
||||
///
|
||||
/// This method updates the selected index by moving it up by the given `amount`.
|
||||
/// If the `amount` causes the index to go out of bounds (i.e., less than zero),
|
||||
/// the first row in the table will be selected.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::widgets::TableState;
|
||||
///
|
||||
/// let mut state = TableState::default();
|
||||
/// state.scroll_up_by(4);
|
||||
/// ```
|
||||
pub fn scroll_up_by(&mut self, amount: u16) {
|
||||
let selected = self.selected.unwrap_or_default();
|
||||
self.select(Some(selected.saturating_sub(amount as usize)));
|
||||
}
|
||||
|
||||
/// Scrolls right by a specified `amount` in the table.
|
||||
///
|
||||
/// This method updates the selected index by moving it right by the given `amount`.
|
||||
/// If the `amount` causes the index to go out of bounds (i.e., if the index is greater than
|
||||
/// the number of columns in the table), the last column in the table will be selected.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let mut state = TableState::default();
|
||||
/// state.scroll_right_by(4);
|
||||
/// ```
|
||||
pub fn scroll_right_by(&mut self, amount: u16) {
|
||||
let selected = self.selected_column.unwrap_or_default();
|
||||
self.select_column(Some(selected.saturating_add(amount as usize)));
|
||||
}
|
||||
|
||||
/// Scrolls left by a specified `amount` in the table.
|
||||
///
|
||||
/// This method updates the selected index by moving it left by the given `amount`.
|
||||
/// If the `amount` causes the index to go out of bounds (i.e., less than zero),
|
||||
/// the first item in the table will be selected.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -271,11 +528,11 @@ impl TableState {
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let mut state = TableState::default();
|
||||
/// state.scroll_up_by(4);
|
||||
/// state.scroll_left_by(4);
|
||||
/// ```
|
||||
pub fn scroll_up_by(&mut self, amount: u16) {
|
||||
let selected = self.selected.unwrap_or_default();
|
||||
self.select(Some(selected.saturating_sub(amount as usize)));
|
||||
pub fn scroll_left_by(&mut self, amount: u16) {
|
||||
let selected = self.selected_column.unwrap_or_default();
|
||||
self.select_column(Some(selected.saturating_sub(amount as usize)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,6 +545,7 @@ mod tests {
|
||||
let state = TableState::new();
|
||||
assert_eq!(state.offset, 0);
|
||||
assert_eq!(state.selected, None);
|
||||
assert_eq!(state.selected_column, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -302,6 +560,19 @@ mod tests {
|
||||
assert_eq!(state.selected, Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_selected_column() {
|
||||
let state = TableState::new().with_selected_column(Some(1));
|
||||
assert_eq!(state.selected_column, Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_selected_cell_none() {
|
||||
let state = TableState::new().with_selected_cell(None);
|
||||
assert_eq!(state.selected, None);
|
||||
assert_eq!(state.selected_column, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offset() {
|
||||
let state = TableState::new();
|
||||
@@ -321,6 +592,18 @@ mod tests {
|
||||
assert_eq!(state.selected(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_column() {
|
||||
let state = TableState::new();
|
||||
assert_eq!(state.selected_column(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_cell() {
|
||||
let state = TableState::new();
|
||||
assert_eq!(state.selected_cell(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_mut() {
|
||||
let mut state = TableState::new();
|
||||
@@ -328,6 +611,13 @@ mod tests {
|
||||
assert_eq!(state.selected, Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_column_mut() {
|
||||
let mut state = TableState::new();
|
||||
*state.selected_column_mut() = Some(1);
|
||||
assert_eq!(state.selected_column, Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select() {
|
||||
let mut state = TableState::new();
|
||||
@@ -342,6 +632,36 @@ mod tests {
|
||||
assert_eq!(state.selected, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_column() {
|
||||
let mut state = TableState::new();
|
||||
state.select_column(Some(1));
|
||||
assert_eq!(state.selected_column, Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_column_none() {
|
||||
let mut state = TableState::new().with_selected_column(Some(1));
|
||||
state.select_column(None);
|
||||
assert_eq!(state.selected_column, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_cell() {
|
||||
let mut state = TableState::new();
|
||||
state.select_cell(Some((1, 5)));
|
||||
assert_eq!(state.selected_cell(), Some((1, 5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_cell_none() {
|
||||
let mut state = TableState::new().with_selected_cell(Some((1, 5)));
|
||||
state.select_cell(None);
|
||||
assert_eq!(state.selected, None);
|
||||
assert_eq!(state.selected_column, None);
|
||||
assert_eq!(state.selected_cell(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_state_navigation() {
|
||||
let mut state = TableState::default();
|
||||
@@ -392,5 +712,37 @@ mod tests {
|
||||
|
||||
state.scroll_up_by(4);
|
||||
assert_eq!(state.selected, Some(0));
|
||||
|
||||
let mut state = TableState::default();
|
||||
state.select_first_column();
|
||||
assert_eq!(state.selected_column, Some(0));
|
||||
|
||||
state.select_previous_column();
|
||||
assert_eq!(state.selected_column, Some(0));
|
||||
|
||||
state.select_next_column();
|
||||
assert_eq!(state.selected_column, Some(1));
|
||||
|
||||
state.select_previous_column();
|
||||
assert_eq!(state.selected_column, Some(0));
|
||||
|
||||
state.select_last_column();
|
||||
assert_eq!(state.selected_column, Some(usize::MAX));
|
||||
|
||||
state.select_previous_column();
|
||||
assert_eq!(state.selected_column, Some(usize::MAX - 1));
|
||||
|
||||
let mut state = TableState::default().with_selected_column(Some(12));
|
||||
state.scroll_right_by(4);
|
||||
assert_eq!(state.selected_column, Some(16));
|
||||
|
||||
state.scroll_left_by(20);
|
||||
assert_eq!(state.selected_column, Some(0));
|
||||
|
||||
state.scroll_right_by(100);
|
||||
assert_eq!(state.selected_column, Some(100));
|
||||
|
||||
state.scroll_left_by(20);
|
||||
assert_eq!(state.selected_column, Some(80));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
use crate::{prelude::*, style::Styled, widgets::Block};
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Modifier, Style, Styled},
|
||||
symbols::{self},
|
||||
text::{Line, Span},
|
||||
widgets::{block::BlockExt, Block, Widget, WidgetRef},
|
||||
};
|
||||
|
||||
const DEFAULT_HIGHLIGHT_STYLE: Style = Style::new().add_modifier(Modifier::REVERSED);
|
||||
|
||||
@@ -14,7 +23,11 @@ const DEFAULT_HIGHLIGHT_STYLE: Style = Style::new().add_modifier(Modifier::REVER
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// use ratatui::{
|
||||
/// style::{Style, Stylize},
|
||||
/// symbols,
|
||||
/// widgets::{Block, Tabs},
|
||||
/// };
|
||||
///
|
||||
/// Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"])
|
||||
/// .block(Block::bordered().title("Tabs"))
|
||||
@@ -33,14 +46,14 @@ const DEFAULT_HIGHLIGHT_STYLE: Style = Style::new().add_modifier(Modifier::REVER
|
||||
///
|
||||
/// (0..5).map(|i| format!("Tab{i}")).collect::<Tabs>();
|
||||
/// ```
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Tabs<'a> {
|
||||
/// A block to wrap this widget in if necessary
|
||||
block: Option<Block<'a>>,
|
||||
/// One title for each tab
|
||||
titles: Vec<Line<'a>>,
|
||||
/// The index of the selected tabs
|
||||
selected: usize,
|
||||
selected: Option<usize>,
|
||||
/// The style used to draw the text
|
||||
style: Style,
|
||||
/// Style to apply to the selected item
|
||||
@@ -53,6 +66,30 @@ pub struct Tabs<'a> {
|
||||
padding_right: Line<'a>,
|
||||
}
|
||||
|
||||
impl Default for Tabs<'_> {
|
||||
/// Returns a default `Tabs` widget.
|
||||
///
|
||||
/// The default widget has:
|
||||
/// - No tabs
|
||||
/// - No selected tab
|
||||
/// - The highlight style is set to reversed.
|
||||
/// - The divider is set to a pipe (`|`).
|
||||
/// - The padding on the left and right is set to a space.
|
||||
///
|
||||
/// This is rarely useful on its own without calling [`Tabs::titles`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::widgets::Tabs;
|
||||
///
|
||||
/// let tabs = Tabs::default().titles(["Tab 1", "Tab 2"]);
|
||||
/// ```
|
||||
fn default() -> Self {
|
||||
Self::new(Vec::<Line>::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Tabs<'a> {
|
||||
/// Creates new `Tabs` from their titles.
|
||||
///
|
||||
@@ -75,13 +112,15 @@ impl<'a> Tabs<'a> {
|
||||
///
|
||||
/// Basic titles.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::Tabs};
|
||||
/// use ratatui::widgets::Tabs;
|
||||
///
|
||||
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]);
|
||||
/// ```
|
||||
///
|
||||
/// Styled titles
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::Tabs};
|
||||
/// use ratatui::{style::Stylize, widgets::Tabs};
|
||||
///
|
||||
/// let tabs = Tabs::new(vec!["Tab 1".red(), "Tab 2".blue()]);
|
||||
/// ```
|
||||
pub fn new<Iter>(titles: Iter) -> Self
|
||||
@@ -89,10 +128,12 @@ impl<'a> Tabs<'a> {
|
||||
Iter: IntoIterator,
|
||||
Iter::Item: Into<Line<'a>>,
|
||||
{
|
||||
let titles = titles.into_iter().map(Into::into).collect_vec();
|
||||
let selected = if titles.is_empty() { None } else { Some(0) };
|
||||
Self {
|
||||
block: None,
|
||||
titles: titles.into_iter().map(Into::into).collect(),
|
||||
selected: 0,
|
||||
titles,
|
||||
selected,
|
||||
style: Style::default(),
|
||||
highlight_style: DEFAULT_HIGHLIGHT_STYLE,
|
||||
divider: Span::raw(symbols::line::VERTICAL),
|
||||
@@ -101,6 +142,48 @@ impl<'a> Tabs<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the titles of the tabs.
|
||||
///
|
||||
/// `titles` is an iterator whose elements can be converted into `Line`.
|
||||
///
|
||||
/// The selected tab can be set with [`Tabs::select`]. The first tab has index 0 (this is also
|
||||
/// the default index).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic titles.
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::widgets::Tabs;
|
||||
///
|
||||
/// let tabs = Tabs::default().titles(vec!["Tab 1", "Tab 2"]);
|
||||
/// ```
|
||||
///
|
||||
/// Styled titles.
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{style::Stylize, widgets::Tabs};
|
||||
///
|
||||
/// let tabs = Tabs::default().titles(vec!["Tab 1".red(), "Tab 2".blue()]);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn titles<Iter>(mut self, titles: Iter) -> Self
|
||||
where
|
||||
Iter: IntoIterator,
|
||||
Iter::Item: Into<Line<'a>>,
|
||||
{
|
||||
self.titles = titles.into_iter().map(Into::into).collect_vec();
|
||||
self.selected = if self.titles.is_empty() {
|
||||
None
|
||||
} else {
|
||||
// Ensure selected is within bounds, and default to 0 if no selected tab
|
||||
self.selected
|
||||
.map(|selected| selected.min(self.titles.len() - 1))
|
||||
.or(Some(0))
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
/// Surrounds the `Tabs` with a [`Block`].
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn block(mut self, block: Block<'a>) -> Self {
|
||||
@@ -112,9 +195,27 @@ impl<'a> Tabs<'a> {
|
||||
///
|
||||
/// The first tab has index 0 (this is also the default index).
|
||||
/// The selected tab can have a different style with [`Tabs::highlight_style`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Select the second tab.
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::widgets::Tabs;
|
||||
///
|
||||
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).select(1);
|
||||
/// ```
|
||||
///
|
||||
/// Deselect the selected tab.
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::widgets::Tabs;
|
||||
///
|
||||
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).select(None);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn select(mut self, selected: usize) -> Self {
|
||||
self.selected = selected;
|
||||
pub fn select<T: Into<Option<usize>>>(mut self, selected: T) -> Self {
|
||||
self.selected = selected.into();
|
||||
self
|
||||
}
|
||||
|
||||
@@ -126,6 +227,8 @@ impl<'a> Tabs<'a> {
|
||||
/// This will set the given style on the entire render area.
|
||||
/// More precise style can be applied to the titles by styling the ones given to [`Tabs::new`].
|
||||
/// The selected tab can be styled differently using [`Tabs::highlight_style`].
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
@@ -139,6 +242,8 @@ impl<'a> Tabs<'a> {
|
||||
///
|
||||
/// Highlighted tab can be selected with [`Tabs::select`].
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
pub fn highlight_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.highlight_style = style.into();
|
||||
self
|
||||
@@ -152,12 +257,14 @@ impl<'a> Tabs<'a> {
|
||||
///
|
||||
/// Use a dot (`•`) as separator.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::Tabs, symbols};
|
||||
/// use ratatui::{symbols, widgets::Tabs};
|
||||
///
|
||||
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).divider(symbols::DOT);
|
||||
/// ```
|
||||
/// Use dash (`-`) as separator.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::Tabs};
|
||||
/// use ratatui::widgets::Tabs;
|
||||
///
|
||||
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).divider("-");
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -177,12 +284,14 @@ impl<'a> Tabs<'a> {
|
||||
///
|
||||
/// A space on either side of the tabs.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::Tabs};
|
||||
/// use ratatui::widgets::Tabs;
|
||||
///
|
||||
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).padding(" ", " ");
|
||||
/// ```
|
||||
/// Nothing on either side of the tabs.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::Tabs};
|
||||
/// use ratatui::widgets::Tabs;
|
||||
///
|
||||
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).padding("", "");
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -204,7 +313,8 @@ impl<'a> Tabs<'a> {
|
||||
///
|
||||
/// An arrow on the left of tabs.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::Tabs};
|
||||
/// use ratatui::widgets::Tabs;
|
||||
///
|
||||
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).padding_left("->");
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -224,7 +334,8 @@ impl<'a> Tabs<'a> {
|
||||
///
|
||||
/// An arrow on the right of tabs.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::Tabs};
|
||||
/// use ratatui::widgets::Tabs;
|
||||
///
|
||||
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).padding_right("<-");
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -290,7 +401,7 @@ impl Tabs<'_> {
|
||||
|
||||
// Title
|
||||
let pos = buf.set_line(x, tabs_area.top(), title, remaining_width);
|
||||
if i == self.selected {
|
||||
if Some(i) == self.selected {
|
||||
buf.set_style(
|
||||
Rect {
|
||||
x,
|
||||
@@ -333,6 +444,7 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::style::{Color, Stylize};
|
||||
|
||||
#[test]
|
||||
fn new() {
|
||||
@@ -348,7 +460,7 @@ mod tests {
|
||||
Line::from("Tab3"),
|
||||
Line::from("Tab4"),
|
||||
],
|
||||
selected: 0,
|
||||
selected: Some(0),
|
||||
style: Style::default(),
|
||||
highlight_style: DEFAULT_HIGHLIGHT_STYLE,
|
||||
divider: Span::raw(symbols::line::VERTICAL),
|
||||
@@ -358,6 +470,37 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
assert_eq!(
|
||||
Tabs::default(),
|
||||
Tabs {
|
||||
block: None,
|
||||
titles: vec![],
|
||||
selected: None,
|
||||
style: Style::default(),
|
||||
highlight_style: DEFAULT_HIGHLIGHT_STYLE,
|
||||
divider: Span::raw(symbols::line::VERTICAL),
|
||||
padding_right: Line::from(" "),
|
||||
padding_left: Line::from(" "),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_into() {
|
||||
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]);
|
||||
assert_eq!(tabs.clone().select(2).selected, Some(2));
|
||||
assert_eq!(tabs.clone().select(None).selected, None);
|
||||
assert_eq!(tabs.clone().select(1u8 as usize).selected, Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_before_titles() {
|
||||
let tabs = Tabs::default().select(1).titles(["Tab1", "Tab2"]);
|
||||
assert_eq!(tabs.selected, Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_from_vec_of_str() {
|
||||
Tabs::new(vec!["a", "b"]);
|
||||
@@ -386,7 +529,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_default() {
|
||||
fn render_new() {
|
||||
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]);
|
||||
let mut expected = Buffer::with_lines([" Tab1 │ Tab2 │ Tab3 │ Tab4 "]);
|
||||
// first tab selected
|
||||
@@ -466,6 +609,10 @@ mod tests {
|
||||
// out of bounds selects no tab
|
||||
let expected = Buffer::with_lines([" Tab1 │ Tab2 │ Tab3 │ Tab4 "]);
|
||||
test_case(tabs.clone().select(4), Rect::new(0, 0, 30, 1), &expected);
|
||||
|
||||
// deselect
|
||||
let expected = Buffer::with_lines([" Tab1 │ Tab2 │ Tab3 │ Tab4 "]);
|
||||
test_case(tabs.clone().select(None), Rect::new(0, 0, 30, 1), &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -14,7 +14,16 @@
|
||||
// not too happy about the redundancy in these tests,
|
||||
// but if that helps readability then it's ok i guess /shrug
|
||||
|
||||
use ratatui::{backend::TestBackend, prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
backend::TestBackend,
|
||||
layout::{Constraint, Direction, Layout},
|
||||
text::Line,
|
||||
widgets::{
|
||||
Block, Borders, List, ListState, Row, Scrollbar, ScrollbarOrientation, ScrollbarState,
|
||||
Table, TableState,
|
||||
},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
struct AppState {
|
||||
@@ -35,7 +44,7 @@ impl Default for AppState {
|
||||
impl AppState {
|
||||
fn select(&mut self, index: usize) {
|
||||
self.list.select(Some(index));
|
||||
self.table.select(Some(index));
|
||||
self.table.select_cell(Some((index, index)));
|
||||
self.scrollbar = self.scrollbar.position(index);
|
||||
}
|
||||
}
|
||||
@@ -98,7 +107,8 @@ const DEFAULT_STATE_REPR: &str = r#"{
|
||||
},
|
||||
"table": {
|
||||
"offset": 0,
|
||||
"selected": null
|
||||
"selected": null,
|
||||
"selected_column": null
|
||||
},
|
||||
"scrollbar": {
|
||||
"content_length": 10,
|
||||
@@ -135,7 +145,8 @@ const SELECTED_STATE_REPR: &str = r#"{
|
||||
},
|
||||
"table": {
|
||||
"offset": 0,
|
||||
"selected": 1
|
||||
"selected": 1,
|
||||
"selected_column": 0
|
||||
},
|
||||
"scrollbar": {
|
||||
"content_length": 10,
|
||||
@@ -174,7 +185,8 @@ const SCROLLED_STATE_REPR: &str = r#"{
|
||||
},
|
||||
"table": {
|
||||
"offset": 4,
|
||||
"selected": 8
|
||||
"selected": 8,
|
||||
"selected_column": 0
|
||||
},
|
||||
"scrollbar": {
|
||||
"content_length": 10,
|
||||
@@ -197,3 +209,23 @@ fn scrolled_state_deserialize() {
|
||||
let mut state: AppState = serde_json::from_str(SCROLLED_STATE_REPR).unwrap();
|
||||
assert_buffer(&mut state, SCROLLED_STATE_BUFFER);
|
||||
}
|
||||
|
||||
// For backwards compatibility these fields should be enough to deserialize the state.
|
||||
const OLD_TABLE_DESERIALIZE: &str = r#"{
|
||||
"offset": 0,
|
||||
"selected": 1
|
||||
}"#;
|
||||
|
||||
const NEW_TABLE_DESERIALIZE: &str = r#"{
|
||||
"offset": 0,
|
||||
"selected": 1,
|
||||
"selected_column": null
|
||||
}"#;
|
||||
|
||||
// This test is to check for backwards compatibility with the old states.
|
||||
#[test]
|
||||
fn table_state_backwards_compatibility() {
|
||||
let old_state: TableState = serde_json::from_str(OLD_TABLE_DESERIALIZE).unwrap();
|
||||
let new_state: TableState = serde_json::from_str(NEW_TABLE_DESERIALIZE).unwrap();
|
||||
assert_eq!(old_state, new_state);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
use std::error::Error;
|
||||
|
||||
use ratatui::{
|
||||
backend::{Backend, TestBackend},
|
||||
backend::TestBackend,
|
||||
layout::Rect,
|
||||
widgets::{Block, Paragraph, Widget},
|
||||
Terminal, TerminalOptions, Viewport,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn terminal_buffer_size_should_be_limited() {
|
||||
let backend = TestBackend::new(400, 400);
|
||||
let terminal = Terminal::new(backend).unwrap();
|
||||
let size = terminal.backend().size().unwrap();
|
||||
assert_eq!(size.width, 255);
|
||||
assert_eq!(size.height, 255);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swap_buffer_clears_prev_buffer() {
|
||||
let backend = TestBackend::new(100, 50);
|
||||
@@ -115,6 +106,46 @@ fn terminal_insert_before_moves_viewport() -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn terminal_insert_before_moves_viewport_does_not_clobber() -> Result<(), Box<dyn Error>> {
|
||||
// This is like terminal_insert_before_moves_viewport, except it draws first before calling
|
||||
// insert_before, and doesn't draw again afterwards. When using scrolling regions, we
|
||||
// shouldn't clobber the viewport.
|
||||
|
||||
let backend = TestBackend::new(20, 5);
|
||||
let mut terminal = Terminal::with_options(
|
||||
backend,
|
||||
TerminalOptions {
|
||||
viewport: Viewport::Inline(1),
|
||||
},
|
||||
)?;
|
||||
|
||||
terminal.draw(|f| {
|
||||
let paragraph = Paragraph::new("[---- Viewport ----]");
|
||||
f.render_widget(paragraph, f.area());
|
||||
})?;
|
||||
|
||||
terminal.insert_before(2, |buf| {
|
||||
Paragraph::new(vec![
|
||||
"------ Line 1 ------".into(),
|
||||
"------ Line 2 ------".into(),
|
||||
])
|
||||
.render(buf.area, buf);
|
||||
})?;
|
||||
|
||||
terminal.backend().assert_scrollback_empty();
|
||||
terminal.backend().assert_buffer_lines([
|
||||
"------ Line 1 ------",
|
||||
"------ Line 2 ------",
|
||||
"[---- Viewport ----]",
|
||||
" ",
|
||||
" ",
|
||||
]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_insert_before_scrolls_on_large_input() -> Result<(), Box<dyn Error>> {
|
||||
// When we have a terminal with 5 lines, and a single line viewport, if we insert many
|
||||
@@ -160,6 +191,51 @@ fn terminal_insert_before_scrolls_on_large_input() -> Result<(), Box<dyn Error>>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn terminal_insert_before_scrolls_on_large_input_does_not_clobber() -> Result<(), Box<dyn Error>> {
|
||||
// This is like terminal_insert_scrolls_on_large_input, except it draws first before calling
|
||||
// insert_before, and doesn't draw again afterwards. When using scrolling regions, we
|
||||
// shouldn't clobber the viewport.
|
||||
|
||||
let backend = TestBackend::new(20, 5);
|
||||
let mut terminal = Terminal::with_options(
|
||||
backend,
|
||||
TerminalOptions {
|
||||
viewport: Viewport::Inline(1),
|
||||
},
|
||||
)?;
|
||||
|
||||
terminal.draw(|f| {
|
||||
let paragraph = Paragraph::new("[---- Viewport ----]");
|
||||
f.render_widget(paragraph, f.area());
|
||||
})?;
|
||||
|
||||
terminal.insert_before(5, |buf| {
|
||||
Paragraph::new(vec![
|
||||
"------ Line 1 ------".into(),
|
||||
"------ Line 2 ------".into(),
|
||||
"------ Line 3 ------".into(),
|
||||
"------ Line 4 ------".into(),
|
||||
"------ Line 5 ------".into(),
|
||||
])
|
||||
.render(buf.area, buf);
|
||||
})?;
|
||||
|
||||
terminal
|
||||
.backend()
|
||||
.assert_scrollback_lines(["------ Line 1 ------"]);
|
||||
terminal.backend().assert_buffer_lines([
|
||||
"------ Line 2 ------",
|
||||
"------ Line 3 ------",
|
||||
"------ Line 4 ------",
|
||||
"------ Line 5 ------",
|
||||
"[---- Viewport ----]",
|
||||
]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_insert_before_scrolls_on_many_inserts() -> Result<(), Box<dyn Error>> {
|
||||
// This test ensures similar behaviour to `terminal_insert_before_scrolls_on_large_input`
|
||||
@@ -215,6 +291,60 @@ fn terminal_insert_before_scrolls_on_many_inserts() -> Result<(), Box<dyn Error>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn terminal_insert_before_scrolls_on_many_inserts_does_not_clobber() -> Result<(), Box<dyn Error>> {
|
||||
// This is like terminal_insert_before_scrolls_on_many_inserts, except it draws first before
|
||||
// calling insert_before, and doesn't draw again afterwards. When using scrolling regions, we
|
||||
// shouldn't clobber the viewport.
|
||||
|
||||
let backend = TestBackend::new(20, 5);
|
||||
let mut terminal = Terminal::with_options(
|
||||
backend,
|
||||
TerminalOptions {
|
||||
viewport: Viewport::Inline(1),
|
||||
},
|
||||
)?;
|
||||
|
||||
terminal.draw(|f| {
|
||||
let paragraph = Paragraph::new("[---- Viewport ----]");
|
||||
f.render_widget(paragraph, f.area());
|
||||
})?;
|
||||
|
||||
terminal.insert_before(1, |buf| {
|
||||
Paragraph::new(vec!["------ Line 1 ------".into()]).render(buf.area, buf);
|
||||
})?;
|
||||
|
||||
terminal.insert_before(1, |buf| {
|
||||
Paragraph::new(vec!["------ Line 2 ------".into()]).render(buf.area, buf);
|
||||
})?;
|
||||
|
||||
terminal.insert_before(1, |buf| {
|
||||
Paragraph::new(vec!["------ Line 3 ------".into()]).render(buf.area, buf);
|
||||
})?;
|
||||
|
||||
terminal.insert_before(1, |buf| {
|
||||
Paragraph::new(vec!["------ Line 4 ------".into()]).render(buf.area, buf);
|
||||
})?;
|
||||
|
||||
terminal.insert_before(1, |buf| {
|
||||
Paragraph::new(vec!["------ Line 5 ------".into()]).render(buf.area, buf);
|
||||
})?;
|
||||
|
||||
terminal
|
||||
.backend()
|
||||
.assert_scrollback_lines(["------ Line 1 ------"]);
|
||||
terminal.backend().assert_buffer_lines([
|
||||
"------ Line 2 ------",
|
||||
"------ Line 3 ------",
|
||||
"------ Line 4 ------",
|
||||
"------ Line 5 ------",
|
||||
"[---- Viewport ----]",
|
||||
]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_insert_before_large_viewport() -> Result<(), Box<dyn Error>> {
|
||||
// This test covers a bug previously present whereby doing an insert_before when the
|
||||
@@ -282,3 +412,73 @@ fn terminal_insert_before_large_viewport() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn terminal_insert_before_large_viewport_does_not_clobber() -> Result<(), Box<dyn Error>> {
|
||||
// This is like terminal_insert_before_large_viewport, except it draws first before calling
|
||||
// insert_before, and doesn't draw again afterwards. When using scrolling regions, we shouldn't
|
||||
// clobber the viewport.
|
||||
|
||||
let backend = TestBackend::new(20, 3);
|
||||
let mut terminal = Terminal::with_options(
|
||||
backend,
|
||||
TerminalOptions {
|
||||
viewport: Viewport::Inline(3),
|
||||
},
|
||||
)?;
|
||||
|
||||
terminal.draw(|f| {
|
||||
let paragraph = Paragraph::new("Viewport")
|
||||
.centered()
|
||||
.block(Block::bordered());
|
||||
f.render_widget(paragraph, f.area());
|
||||
})?;
|
||||
|
||||
terminal.insert_before(1, |buf| {
|
||||
Paragraph::new(vec!["------ Line 1 ------".into()]).render(buf.area, buf);
|
||||
})?;
|
||||
|
||||
terminal.insert_before(3, |buf| {
|
||||
Paragraph::new(vec![
|
||||
"------ Line 2 ------".into(),
|
||||
"------ Line 3 ------".into(),
|
||||
"------ Line 4 ------".into(),
|
||||
])
|
||||
.render(buf.area, buf);
|
||||
})?;
|
||||
|
||||
terminal.insert_before(7, |buf| {
|
||||
Paragraph::new(vec![
|
||||
"------ Line 5 ------".into(),
|
||||
"------ Line 6 ------".into(),
|
||||
"------ Line 7 ------".into(),
|
||||
"------ Line 8 ------".into(),
|
||||
"------ Line 9 ------".into(),
|
||||
"----- Line 10 ------".into(),
|
||||
"----- Line 11 ------".into(),
|
||||
])
|
||||
.render(buf.area, buf);
|
||||
})?;
|
||||
|
||||
terminal.backend().assert_buffer_lines([
|
||||
"┌──────────────────┐",
|
||||
"│ Viewport │",
|
||||
"└──────────────────┘",
|
||||
]);
|
||||
terminal.backend().assert_scrollback_lines([
|
||||
"------ Line 1 ------",
|
||||
"------ Line 2 ------",
|
||||
"------ Line 3 ------",
|
||||
"------ Line 4 ------",
|
||||
"------ Line 5 ------",
|
||||
"------ Line 6 ------",
|
||||
"------ Line 7 ------",
|
||||
"------ Line 8 ------",
|
||||
"------ Line 9 ------",
|
||||
"----- Line 10 ------",
|
||||
"----- Line 11 ------",
|
||||
]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -632,6 +632,7 @@ fn widgets_table_can_have_elements_styled_individually() {
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
let mut state = TableState::default();
|
||||
state.select(Some(0));
|
||||
state.select_column(Some(1));
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let table = Table::new(
|
||||
@@ -658,7 +659,9 @@ fn widgets_table_can_have_elements_styled_individually() {
|
||||
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
||||
.block(Block::new().borders(Borders::LEFT | Borders::RIGHT))
|
||||
.highlight_symbol(">> ")
|
||||
.highlight_style(Style::default().add_modifier(Modifier::BOLD))
|
||||
.row_highlight_style(Style::default().add_modifier(Modifier::BOLD))
|
||||
.column_highlight_style(Style::default().add_modifier(Modifier::ITALIC))
|
||||
.cell_highlight_style(Style::default().add_modifier(Modifier::DIM))
|
||||
.column_spacing(1);
|
||||
f.render_stateful_widget(table, f.area(), &mut state);
|
||||
})
|
||||
@@ -678,6 +681,19 @@ fn widgets_table_can_have_elements_styled_individually() {
|
||||
.add_modifier(Modifier::BOLD),
|
||||
);
|
||||
}
|
||||
|
||||
// Second column highlight style
|
||||
for row in 2..=3 {
|
||||
for col in 11..=16 {
|
||||
expected[(col, row)].set_style(Style::default().add_modifier(Modifier::ITALIC));
|
||||
}
|
||||
}
|
||||
|
||||
// First row, second column highlight style (cell highlight)
|
||||
for col in 11..=16 {
|
||||
expected[(col, 2)].set_style(Style::default().add_modifier(Modifier::DIM));
|
||||
}
|
||||
|
||||
// Second row:
|
||||
// 1. row color
|
||||
for col in 1..=28 {
|
||||
|
||||
Reference in New Issue
Block a user