Compare commits

..

1 Commits

Author SHA1 Message Date
Josh McKinney
26163effdf refactor: modularize
WIP - this is just a PoC to understand whether this would work fine, termion and termwiz disabled for now
2024-09-27 15:01:08 -07:00
210 changed files with 3316 additions and 5903 deletions

View File

@@ -47,7 +47,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- uses: bnjbvr/cargo-machete@v0.7.0
- uses: bnjbvr/cargo-machete@v0.6.2
clippy:
runs-on: ubuntu-latest

View File

@@ -10,14 +10,8 @@ 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>>`
@@ -71,77 +65,6 @@ 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])
@@ -211,7 +134,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` ([#1293])
### `Frame::size` is deprecated and renamed to `Frame::area`
[#1293]: https://github.com/ratatui/ratatui/pull/1293

View File

@@ -1,64 +1,50 @@
[package]
name = "ratatui"
version = "0.28.1" # crate version
authors = ["Florian Dehau <work@fdehau.com>", "The Ratatui Developers"]
description = "A library that's all about cooking up terminal user interfaces"
documentation = "https://docs.rs/ratatui/latest/ratatui/"
repository = "https://github.com/ratatui/ratatui"
homepage = "https://ratatui.rs"
keywords = ["tui", "terminal", "dashboard"]
categories = ["command-line-interface"]
readme = "README.md"
license = "MIT"
exclude = [
"assets/*",
".github",
"Makefile.toml",
"CONTRIBUTING.md",
"*.log",
"tags",
]
edition = "2021"
rust-version = "1.74.0"
[workspace]
resolver = "2"
members = ["ratatui*"]
# disabled for now because of the orphan rule on conversions
# <https://github.com/ratatui/ratatui/issues/1388#issuecomment-2379895747>
exclude = ["ratatui-termion", "ratatui-termwiz"]
[workspace.dependencies]
ratatui = { path = "ratatui" }
ratatui-core = { path = "ratatui-core" }
ratatui-crossterm = { path = "ratatui-crossterm" }
ratatui-termion = { path = "ratatui-termion" }
ratatui-termwiz = { path = "ratatui-termwiz" }
ratatui-widgets = { path = "ratatui-widgets" }
[dependencies]
bitflags = "2.3"
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"
crossterm = { version = "0.28.1" }
document-features = { version = "0.2.7" }
instability = "0.3.1"
itertools = "0.13"
lru = "0.12.0"
paste = "1.0.2"
palette = { version = "0.7.6", optional = true }
serde = { version = "1", optional = true, features = ["derive"] }
palette = { version = "0.7.6" }
serde = { version = "1", features = ["derive"] }
strum = { version = "0.26.3", features = ["derive"] }
termwiz = { version = "0.22.0", optional = true }
time = { version = "0.3.11", optional = true, features = ["local-offset"] }
termion = { version = "4.0.0" }
termwiz = { version = "0.22.0" }
time = { version = "0.3.11", features = ["local-offset"] }
unicode-segmentation = "1.10"
unicode-truncate = "1"
unicode-width = "=0.1.13"
[target.'cfg(not(windows))'.dependencies]
# termion is not supported on Windows
termion = { version = "4.0.0", optional = true }
[dev-dependencies]
# dev-dependencies
argh = "0.1.12"
color-eyre = "0.6.2"
criterion = { version = "0.5.1", features = ["html_reports"] }
crossterm = { version = "0.28.1", features = ["event-stream"] }
fakeit = "1.1"
font8x8 = "0.3.1"
futures = "0.3.30"
indoc = "2"
octocrab = "0.41.0"
octocrab = "0.40.0"
pretty_assertions = "1.4.0"
rand = "0.8.5"
rand_chacha = "0.3.1"
rstest = "0.23.0"
rstest = "0.22.0"
serde_json = "1.0.109"
tokio = { version = "1.39.2", features = [
"rt",
@@ -69,317 +55,3 @@ tokio = { version = "1.39.2", features = [
tracing = "0.1.40"
tracing-appender = "0.2.3"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
cargo = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
cast_possible_truncation = "allow"
cast_possible_wrap = "allow"
cast_precision_loss = "allow"
cast_sign_loss = "allow"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
module_name_repetitions = "allow"
must_use_candidate = "allow"
# we often split up a module into multiple files with the main type in a file named after the
# module, so we want to allow this pattern
module_inception = "allow"
# nursery or restricted
as_underscore = "warn"
deref_by_slicing = "warn"
else_if_without_else = "warn"
empty_line_after_doc_comments = "warn"
equatable_if_let = "warn"
fn_to_numeric_cast_any = "warn"
format_push_string = "warn"
map_err_ignore = "warn"
missing_const_for_fn = "warn"
mixed_read_write_in_expression = "warn"
mod_module_files = "warn"
needless_pass_by_ref_mut = "warn"
needless_raw_strings = "warn"
or_fun_call = "warn"
redundant_type_annotations = "warn"
rest_pat_in_fully_bound_structs = "warn"
string_lit_chars_any = "warn"
string_slice = "warn"
string_to_string = "warn"
unnecessary_self_imports = "warn"
use_self = "warn"
[features]
#! The crate provides a set of optional features that can be enabled in your `cargo.toml` file.
#!
## By default, we enable the crossterm backend as this is a reasonable choice for most applications
## as it is supported on Linux/Mac/Windows systems. We also enable the `underline-color` feature
## which allows you to set the underline color of text.
default = ["crossterm", "underline-color"]
#! Generally an application will only use one backend, so you should only enable one of the following features:
## enables the [`CrosstermBackend`](backend::CrosstermBackend) backend and adds a dependency on [`crossterm`].
crossterm = ["dep:crossterm"]
## enables the [`TermionBackend`](backend::TermionBackend) backend and adds a dependency on [`termion`].
termion = ["dep:termion"]
## enables the [`TermwizBackend`](backend::TermwizBackend) backend and adds a dependency on [`termwiz`].
termwiz = ["dep:termwiz"]
#! The following optional features are available for all backends:
## enables serialization and deserialization of style and color types using the [`serde`] crate.
## This is useful if you want to save themes to a file.
serde = ["dep:serde", "bitflags/serde", "compact_str/serde"]
## enables the [`border!`] macro.
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"]
#! Widgets that add dependencies are gated behind feature flags to prevent unused transitive
#! dependencies. The available features are:
## enables the [`calendar`](widgets::calendar) widget module and adds a dependency on [`time`].
widget-calendar = ["dep:time"]
#! The following optional features are only available for some backends:
## enables the backend code that sets the underline color.
## Underline color is only supported by the [`CrosstermBackend`](backend::CrosstermBackend) backend,
## and is not supported on Windows 7.
underline-color = ["dep:crossterm"]
#! The following features are unstable and may change in the future:
## Enable all unstable features.
unstable = [
"unstable-rendered-line-info",
"unstable-widget-ref",
"unstable-backend-writer",
]
## Enables the [`Paragraph::line_count`](widgets::Paragraph::line_count)
## [`Paragraph::line_width`](widgets::Paragraph::line_width) methods
## which are experimental and may change in the future.
## See [Issue 293](https://github.com/ratatui/ratatui/issues/293) for more details.
unstable-rendered-line-info = []
## Enables the [`WidgetRef`](widgets::WidgetRef) and [`StatefulWidgetRef`](widgets::StatefulWidgetRef) traits which are experimental and may change in
## the future.
unstable-widget-ref = []
## Enables getting access to backends' writers.
unstable-backend-writer = []
[package.metadata.docs.rs]
all-features = true
# see https://doc.rust-lang.org/nightly/rustdoc/scraped-examples.html
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
rustdoc-args = ["--cfg", "docsrs"]
# Improve benchmark consistency
[profile.bench]
codegen-units = 1
lto = true
[lib]
bench = false
[[bench]]
name = "main"
harness = false
[[example]]
name = "async"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "barchart"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "barchart-grouped"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "block"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "calendar"
required-features = ["crossterm", "widget-calendar"]
doc-scrape-examples = true
[[example]]
name = "canvas"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "chart"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "colors"
required-features = ["crossterm"]
# this example is a bit verbose, so we don't want to include it in the docs
doc-scrape-examples = false
[[example]]
name = "colors_rgb"
required-features = ["crossterm", "palette"]
doc-scrape-examples = true
[[example]]
name = "constraint-explorer"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "constraints"
required-features = ["crossterm"]
doc-scrape-examples = false
[[example]]
name = "custom_widget"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "demo"
# this runs for all of the terminal backends, so it can't be built using --all-features or scraped
doc-scrape-examples = false
[[example]]
name = "demo2"
required-features = ["crossterm", "palette", "widget-calendar"]
doc-scrape-examples = true
[[example]]
name = "docsrs"
required-features = ["crossterm"]
doc-scrape-examples = false
[[example]]
name = "flex"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "gauge"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "hello_world"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "inline"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "layout"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "line_gauge"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "hyperlink"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "list"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "minimal"
required-features = ["crossterm"]
# prefer to show the more featureful examples in the docs
doc-scrape-examples = false
[[example]]
name = "modifiers"
required-features = ["crossterm"]
# this example is a bit verbose, so we don't want to include it in the docs
doc-scrape-examples = false
[[example]]
name = "panic"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "paragraph"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "popup"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "ratatui-logo"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "scrollbar"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "sparkline"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "table"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "tabs"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "tracing"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "user_input"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "widget_impl"
required-features = ["crossterm", "unstable-widget-ref"]
doc-scrape-examples = true
[[test]]
name = "state_serde"
required-features = ["serde"]

View File

@@ -1,69 +0,0 @@
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);

View File

@@ -11,7 +11,6 @@ allow = [
"MIT",
"Unicode-DFS-2016",
"WTFPL",
"Zlib",
]
[advisories]

View File

@@ -1,54 +0,0 @@
//! # [Ratatui] Logo example
//!
//! The latest version of this example is available in the [examples] folder in the repository.
//!
//! Please note that the examples are designed to be run against the `main` branch of the Github
//! repository. This means that you may not be able to compile with the latest release version on
//! crates.io, or the one that you have installed locally.
//!
//! See the [examples readme] for more information on finding examples that match the version of the
//! library you are using.
//!
//! [Ratatui]: https://github.com/ratatui/ratatui
//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
use std::env::args;
use color_eyre::Result;
use crossterm::event::{self, Event};
use ratatui::{
layout::{Constraint, Layout},
widgets::{RatatuiLogo, RatatuiLogoSize},
DefaultTerminal, TerminalOptions, Viewport,
};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init_with_options(TerminalOptions {
viewport: Viewport::Inline(3),
});
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!();
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(());
}
}
}

50
ratatui-core/Cargo.toml Normal file
View File

@@ -0,0 +1,50 @@
[package]
name = "ratatui-core"
version = "0.1.0"
edition = "2021"
[features]
underline-color = []
unstable-widget-ref = []
[dependencies]
bitflags.workspace = true
cassowary.workspace = true
compact_str.workspace = true
document-features.workspace = true
instability.workspace = true
itertools.workspace = true
lru.workspace = true
paste.workspace = true
palette = { workspace = true, optional = true }
serde = { workspace = true, optional = true, features = ["derive"] }
strum = { workspace = true, features = ["derive"] }
termwiz = { workspace = true, optional = true }
unicode-segmentation.workspace = true
unicode-truncate.workspace = true
unicode-width.workspace = true
[dev-dependencies]
argh = "0.1.12"
color-eyre = "0.6.2"
criterion = { version = "0.5.1", features = ["html_reports"] }
crossterm = { version = "0.28.1", features = ["event-stream"] }
fakeit = "1.1"
font8x8 = "0.3.1"
futures = "0.3.30"
indoc = "2"
octocrab = "0.40.0"
pretty_assertions = "1.4.0"
rand = "0.8.5"
rand_chacha = "0.3.1"
rstest = "0.22.0"
serde_json = "1.0.109"
tokio = { version = "1.39.2", features = [
"rt",
"macros",
"time",
"rt-multi-thread",
] }
tracing = "0.1.40"
tracing-appender = "0.2.3"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }

View File

@@ -27,7 +27,7 @@
//! ```rust,no_run
//! use std::io::stdout;
//!
//! use ratatui::{backend::CrosstermBackend, Terminal};
//! use ratatui::prelude::*;
//!
//! let backend = CrosstermBackend::new(stdout());
//! let mut terminal = Terminal::new(backend)?;
@@ -109,21 +109,6 @@ use crate::{
layout::{Position, Size},
};
#[cfg(all(not(windows), feature = "termion"))]
mod termion;
#[cfg(all(not(windows), feature = "termion"))]
pub use self::termion::TermionBackend;
#[cfg(feature = "crossterm")]
mod crossterm;
#[cfg(feature = "crossterm")]
pub use self::crossterm::CrosstermBackend;
#[cfg(feature = "termwiz")]
mod termwiz;
#[cfg(feature = "termwiz")]
pub use self::termwiz::TermwizBackend;
mod test;
pub use self::test::TestBackend;
@@ -187,10 +172,8 @@ pub trait Backend {
/// # Example
///
/// ```rust
/// # use ratatui::backend::{TestBackend};
/// # use ratatui::backend::{Backend, TestBackend};
/// # let mut backend = TestBackend::new(80, 25);
/// use ratatui::backend::Backend;
///
/// backend.hide_cursor()?;
/// // do something with hidden cursor
/// backend.show_cursor()?;
@@ -224,10 +207,9 @@ pub trait Backend {
/// # Example
///
/// ```rust
/// # use ratatui::backend::{TestBackend};
/// # use ratatui::backend::{Backend, TestBackend};
/// # use ratatui::layout::Position;
/// # 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(())
@@ -257,10 +239,8 @@ pub trait Backend {
/// # Example
///
/// ```rust,no_run
/// # use ratatui::backend::{TestBackend};
/// # use ratatui::backend::{Backend, TestBackend};
/// # let mut backend = TestBackend::new(80, 25);
/// use ratatui::backend::Backend;
///
/// backend.clear()?;
/// # std::io::Result::Ok(())
/// ```
@@ -275,10 +255,8 @@ pub trait Backend {
/// # Example
///
/// ```rust,no_run
/// # use ratatui::{backend::{TestBackend}};
/// # use ratatui::{prelude::*, backend::{TestBackend, ClearType}};
/// # let mut backend = TestBackend::new(80, 25);
/// use ratatui::backend::{Backend, ClearType};
///
/// backend.clear_region(ClearType::All)?;
/// # std::io::Result::Ok(())
/// ```
@@ -309,10 +287,8 @@ pub trait Backend {
/// # Example
///
/// ```rust
/// # use ratatui::{backend::{TestBackend}};
/// # let backend = TestBackend::new(80, 25);
/// use ratatui::{backend::Backend, layout::Size};
///
/// # use ratatui::{prelude::*, backend::TestBackend};
/// let backend = TestBackend::new(80, 25);
/// assert_eq!(backend.size()?, Size::new(80, 25));
/// # std::io::Result::Ok(())
/// ```
@@ -327,64 +303,6 @@ 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)]

View File

@@ -6,6 +6,8 @@ use std::{
io, iter,
};
use unicode_width::UnicodeWidthStr;
use crate::{
backend::{Backend, ClearType, WindowSize},
buffer::{Buffer, Cell},
@@ -22,7 +24,7 @@ use crate::{
/// # Example
///
/// ```rust
/// use ratatui::backend::{Backend, TestBackend};
/// use ratatui::{backend::TestBackend, prelude::*};
///
/// let mut backend = TestBackend::new(10, 2);
/// backend.clear()?;
@@ -50,13 +52,13 @@ fn buffer_view(buffer: &Buffer) -> String {
let mut overwritten = vec![];
let mut skip: usize = 0;
view.push('"');
for (x, cell) in cells.iter().enumerate() {
for (x, c) in cells.iter().enumerate() {
if skip == 0 {
view.push_str(cell.symbol());
view.push_str(c.symbol());
} else {
overwritten.push((x, cell.symbol()));
overwritten.push((x, c.symbol()));
}
skip = std::cmp::max(skip, cell.width()).saturating_sub(1);
skip = std::cmp::max(skip, c.symbol().width()).saturating_sub(1);
}
view.push('"');
if !overwritten.is_empty() {
@@ -78,28 +80,6 @@ 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
@@ -267,7 +247,7 @@ impl Backend for TestBackend {
Ok(())
}
fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
fn clear_region(&mut self, clear_type: super::ClearType) -> io::Result<()> {
let region = match clear_type {
ClearType::All => return self.clear(),
ClearType::AfterCursor => {
@@ -363,77 +343,6 @@ 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
@@ -583,7 +492,8 @@ mod tests {
#[test]
fn clear_region_all() {
let mut backend = TestBackend::with_lines([
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"aaaaaaaaaa",
"aaaaaaaaaa",
@@ -603,7 +513,8 @@ mod tests {
#[test]
fn clear_region_after_cursor() {
let mut backend = TestBackend::with_lines([
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"aaaaaaaaaa",
"aaaaaaaaaa",
@@ -626,7 +537,8 @@ mod tests {
#[test]
fn clear_region_before_cursor() {
let mut backend = TestBackend::with_lines([
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"aaaaaaaaaa",
"aaaaaaaaaa",
@@ -649,7 +561,8 @@ mod tests {
#[test]
fn clear_region_current_line() {
let mut backend = TestBackend::with_lines([
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"aaaaaaaaaa",
"aaaaaaaaaa",
@@ -672,7 +585,8 @@ mod tests {
#[test]
fn clear_region_until_new_line() {
let mut backend = TestBackend::with_lines([
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"aaaaaaaaaa",
"aaaaaaaaaa",
@@ -695,7 +609,8 @@ mod tests {
#[test]
fn append_lines_not_at_last_line() {
let mut backend = TestBackend::with_lines([
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccc",
@@ -733,7 +648,8 @@ mod tests {
#[test]
fn append_lines_at_last_line() {
let mut backend = TestBackend::with_lines([
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccc",
@@ -765,7 +681,8 @@ mod tests {
#[test]
fn append_multiple_lines_not_at_last_line() {
let mut backend = TestBackend::with_lines([
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccc",
@@ -794,7 +711,8 @@ mod tests {
#[test]
fn append_multiple_lines_past_last_line() {
let mut backend = TestBackend::with_lines([
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccc",
@@ -821,7 +739,8 @@ mod tests {
#[test]
fn append_multiple_lines_where_cursor_at_end_appends_height_lines() {
let mut backend = TestBackend::with_lines([
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccc",
@@ -854,7 +773,8 @@ mod tests {
#[test]
fn append_multiple_lines_where_cursor_appends_height_lines() {
let mut backend = TestBackend::with_lines([
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccc",
@@ -879,7 +799,8 @@ mod tests {
#[test]
fn append_multiple_lines_where_cursor_at_end_appends_more_than_height_lines() {
let mut backend = TestBackend::with_lines([
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccc",
@@ -995,81 +916,4 @@ 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);
}
}
}

View File

@@ -41,11 +41,7 @@ macro_rules! assert_buffer_eq {
#[allow(deprecated)]
#[cfg(test)]
mod tests {
use crate::{
buffer::Buffer,
layout::Rect,
style::{Color, Style},
};
use crate::prelude::*;
#[test]
fn assert_buffer_eq_does_not_panic_on_equal_buffers() {

View File

@@ -6,12 +6,7 @@ use std::{
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use crate::{
buffer::Cell,
layout::{Position, Rect},
style::Style,
text::{Line, Span},
};
use crate::{buffer::Cell, layout::Position, prelude::*};
/// A buffer that maps to the desired content of the terminal after the draw call
///
@@ -79,7 +74,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
@@ -168,11 +163,7 @@ impl Buffer {
/// # Examples
///
/// ```rust
/// use ratatui::{
/// buffer::{Buffer, Cell},
/// layout::{Position, Rect},
/// };
///
/// # use ratatui::{prelude::*, buffer::Cell, layout::Position};
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
///
/// assert_eq!(buffer.cell(Position::new(0, 0)), Some(&Cell::default()));
@@ -199,11 +190,7 @@ impl Buffer {
/// # Examples
///
/// ```rust
/// use ratatui::{
/// buffer::{Buffer, Cell},
/// layout::{Position, Rect},
/// style::{Color, Style},
/// };
/// # use ratatui::{prelude::*, buffer::Cell, layout::Position};
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
///
/// if let Some(cell) = buffer.cell_mut(Position::new(0, 0)) {
@@ -227,8 +214,7 @@ impl Buffer {
/// # Examples
///
/// ```
/// use ratatui::{buffer::Buffer, layout::Rect};
///
/// # use ratatui::prelude::*;
/// 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);
@@ -239,8 +225,7 @@ impl Buffer {
/// Panics when given an coordinate that is outside of this Buffer's area.
///
/// ```should_panic
/// use ratatui::{buffer::Buffer, layout::Rect};
///
/// # use ratatui::prelude::*;
/// 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).
@@ -269,10 +254,9 @@ impl Buffer {
return None;
}
// remove offset
let y = (position.y - self.area.y) as usize;
let x = (position.x - self.area.x) as usize;
let width = self.area.width as usize;
Some(y * width + x)
let y = position.y - self.area.y;
let x = position.x - self.area.x;
Some((y * self.area.width + x) as usize)
}
/// Returns the (global) coordinates of a cell given its index
@@ -282,8 +266,7 @@ impl Buffer {
/// # Examples
///
/// ```
/// use ratatui::{buffer::Buffer, layout::Rect};
///
/// # use ratatui::prelude::*;
/// let rect = Rect::new(200, 100, 10, 10);
/// let buffer = Buffer::empty(rect);
/// assert_eq!(buffer.pos_of(0), (200, 100));
@@ -295,8 +278,7 @@ impl Buffer {
/// Panics when given an index that is outside the Buffer's content.
///
/// ```should_panic
/// use ratatui::{buffer::Buffer, layout::Rect};
///
/// # use ratatui::prelude::*;
/// 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.
@@ -395,8 +377,6 @@ 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);
@@ -414,7 +394,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;
}
@@ -429,7 +409,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;
@@ -499,8 +479,9 @@ impl Buffer {
updates.push((x, y, &next_buffer[i]));
}
to_skip = current.width().saturating_sub(1);
let affected_width = std::cmp::max(current.width(), previous.width());
to_skip = current.symbol().width().saturating_sub(1);
let affected_width = std::cmp::max(current.symbol().width(), previous.symbol().width());
invalidated = std::cmp::max(affected_width, invalidated).saturating_sub(1);
}
updates
@@ -523,11 +504,7 @@ impl<P: Into<Position>> Index<P> for Buffer {
/// # Examples
///
/// ```
/// use ratatui::{
/// buffer::{Buffer, Cell},
/// layout::{Position, Rect},
/// };
///
/// # use ratatui::{prelude::*, buffer::Cell, layout::Position};
/// let buf = Buffer::empty(Rect::new(0, 0, 10, 10));
/// let cell = &buf[(0, 0)];
/// let cell = &buf[Position::new(0, 0)];
@@ -553,11 +530,7 @@ impl<P: Into<Position>> IndexMut<P> for Buffer {
/// # Examples
///
/// ```
/// use ratatui::{
/// buffer::{Buffer, Cell},
/// layout::{Position, Rect},
/// };
///
/// # use ratatui::{prelude::*, buffer::Cell, layout::Position};
/// let mut buf = Buffer::empty(Rect::new(0, 0, 10, 10));
/// buf[(0, 0)].set_symbol("A");
/// buf[Position::new(0, 0)].set_symbol("B");
@@ -597,7 +570,7 @@ impl fmt::Debug for Buffer {
} else {
overwritten.push((x, c.symbol()));
}
skip = std::cmp::max(skip, c.width()).saturating_sub(1);
skip = std::cmp::max(skip, c.symbol().width()).saturating_sub(1);
#[cfg(feature = "underline-color")]
{
let style = (c.fg, c.bg, c.underline_color, c.modifier);
@@ -649,7 +622,6 @@ mod tests {
use rstest::{fixture, rstest};
use super::*;
use crate::style::{Color, Modifier, Stylize};
#[test]
fn debug_empty_buffer() {

View File

@@ -1,12 +1,9 @@
use std::hash::{Hash, Hasher};
use compact_str::CompactString;
use unicode_width::UnicodeWidthStr;
use crate::style::{Color, Modifier, Style};
use crate::prelude::*;
/// A buffer cell
#[derive(Debug, Clone, Eq)]
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Cell {
/// The string to be drawn in the cell.
@@ -34,57 +31,11 @@ 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 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)),
}
}
pub const EMPTY: Self = Self::new(" ");
/// Creates a new `Cell` with the given symbol.
///
@@ -101,7 +52,6 @@ impl Cell {
underline_color: Color::Reset,
modifier: Modifier::empty(),
skip: false,
width: std::cell::Cell::new(None),
}
}
@@ -114,7 +64,6 @@ 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
}
@@ -123,7 +72,6 @@ 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
}
@@ -131,7 +79,6 @@ 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
}
@@ -201,33 +148,18 @@ 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
}
@@ -250,20 +182,19 @@ 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
@@ -272,7 +203,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}");
@@ -280,28 +211,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);
@@ -309,14 +240,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);
@@ -330,7 +261,7 @@ mod tests {
#[test]
fn style() {
let cell = Cell::empty();
let cell = Cell::EMPTY;
assert_eq!(
cell.style(),
Style {
@@ -363,12 +294,4 @@ 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
}
}

View File

@@ -17,5 +17,5 @@ pub use flex::Flex;
pub use layout::Layout;
pub use margin::Margin;
pub use position::Position;
pub use rect::{Columns, Offset, Positions, Rect, Rows};
pub use rect::*;
pub use size::Size;

View File

@@ -26,8 +26,7 @@ use strum::EnumIs;
/// `Constraint` provides helper methods to create lists of constraints from various input formats.
///
/// ```rust
/// use ratatui::layout::Constraint;
///
/// # use ratatui::prelude::*;
/// // Create a layout with specified lengths for each element
/// let constraints = Constraint::from_lengths([10, 20, 10]);
///
@@ -224,8 +223,7 @@ impl Constraint {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Constraint, Layout, Rect};
///
/// # use ratatui::prelude::*;
/// # let area = Rect::default();
/// let constraints = Constraint::from_lengths([1, 2, 3]);
/// let layout = Layout::default().constraints(constraints).split(area);
@@ -242,8 +240,7 @@ impl Constraint {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Constraint, Layout, Rect};
///
/// # use ratatui::prelude::*;
/// # let area = Rect::default();
/// let constraints = Constraint::from_ratios([(1, 4), (1, 2), (1, 4)]);
/// let layout = Layout::default().constraints(constraints).split(area);
@@ -260,8 +257,7 @@ impl Constraint {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Constraint, Layout, Rect};
///
/// # use ratatui::prelude::*;
/// # let area = Rect::default();
/// let constraints = Constraint::from_percentages([25, 50, 25]);
/// let layout = Layout::default().constraints(constraints).split(area);
@@ -278,8 +274,7 @@ impl Constraint {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Constraint, Layout, Rect};
///
/// # use ratatui::prelude::*;
/// # let area = Rect::default();
/// let constraints = Constraint::from_maxes([1, 2, 3]);
/// let layout = Layout::default().constraints(constraints).split(area);
@@ -296,8 +291,7 @@ impl Constraint {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Constraint, Layout, Rect};
///
/// # use ratatui::prelude::*;
/// # let area = Rect::default();
/// let constraints = Constraint::from_mins([1, 2, 3]);
/// let layout = Layout::default().constraints(constraints).split(area);
@@ -314,8 +308,7 @@ impl Constraint {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Constraint, Layout, Rect};
///
/// # use ratatui::prelude::*;
/// # let area = Rect::default();
/// let constraints = Constraint::from_fills([1, 2, 3]);
/// let layout = Layout::default().constraints(constraints).split(area);
@@ -337,8 +330,7 @@ impl From<u16> for Constraint {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Constraint, Direction, Layout, Rect};
///
/// # use ratatui::prelude::*;
/// # let area = Rect::default();
/// let layout = Layout::new(Direction::Vertical, [1, 2, 3]).split(area);
/// let layout = Layout::horizontal([1, 2, 3]).split(area);

View File

@@ -1,7 +1,7 @@
use strum::{Display, EnumIs, EnumString};
#[allow(unused_imports)]
use crate::layout::Constraint;
use super::constraint::Constraint;
/// Defines the options for layout flex justify content in a container.
///

View File

@@ -12,7 +12,8 @@ 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 crate::layout::{Constraint, Direction, Flex, Margin, Rect};
use super::Flex;
use crate::prelude::*;
type Rects = Rc<[Rect]>;
type Segments = Rects;
@@ -86,11 +87,7 @@ thread_local! {
/// # Example
///
/// ```rust
/// use ratatui::{
/// layout::{Constraint, Direction, Layout, Rect},
/// widgets::Paragraph,
/// Frame,
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// fn render(frame: &mut Frame, area: Rect) {
/// let layout = Layout::new(
@@ -144,8 +141,7 @@ impl Layout {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Constraint, Direction, Layout};
///
/// # use ratatui::prelude::*;
/// Layout::new(
/// Direction::Horizontal,
/// [Constraint::Length(5), Constraint::Min(0)],
@@ -178,8 +174,7 @@ impl Layout {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Constraint, Layout};
///
/// # use ratatui::prelude::*;
/// let layout = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
/// ```
pub fn vertical<I>(constraints: I) -> Self
@@ -198,8 +193,7 @@ impl Layout {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Constraint, Layout};
///
/// # use ratatui::prelude::*;
/// let layout = Layout::horizontal([Constraint::Length(5), Constraint::Min(0)]);
/// ```
pub fn horizontal<I>(constraints: I) -> Self
@@ -227,8 +221,7 @@ impl Layout {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Constraint, Direction, Layout, Rect};
///
/// # use ratatui::prelude::*;
/// let layout = Layout::default()
/// .direction(Direction::Horizontal)
/// .constraints([Constraint::Length(5), Constraint::Min(0)])
@@ -262,8 +255,7 @@ impl Layout {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Constraint, Layout, Rect};
///
/// # use ratatui::prelude::*;
/// let layout = Layout::default()
/// .constraints([
/// Constraint::Percentage(20),
@@ -307,8 +299,7 @@ impl Layout {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Constraint, Layout, Rect};
///
/// # use ratatui::prelude::*;
/// let layout = Layout::default()
/// .constraints([Constraint::Min(0)])
/// .margin(2)
@@ -329,8 +320,7 @@ impl Layout {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Constraint, Layout, Rect};
///
/// # use ratatui::prelude::*;
/// let layout = Layout::default()
/// .constraints([Constraint::Min(0)])
/// .horizontal_margin(2)
@@ -348,8 +338,7 @@ impl Layout {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Constraint, Layout, Rect};
///
/// # use ratatui::prelude::*;
/// let layout = Layout::default()
/// .constraints([Constraint::Min(0)])
/// .vertical_margin(2)
@@ -380,8 +369,7 @@ impl Layout {
/// In this example, the items in the layout will be aligned to the start.
///
/// ```rust
/// use ratatui::layout::{Constraint::*, Flex, Layout};
///
/// # use ratatui::layout::{Flex, Layout, Constraint::*};
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).flex(Flex::Start);
/// ```
///
@@ -389,8 +377,7 @@ impl Layout {
/// space.
///
/// ```rust
/// use ratatui::layout::{Constraint::*, Flex, Layout};
///
/// # use ratatui::layout::{Flex, Layout, Constraint::*};
/// 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"]
@@ -410,8 +397,7 @@ impl Layout {
/// In this example, the spacing between each item in the layout is set to 2 cells.
///
/// ```rust
/// use ratatui::layout::{Constraint::*, Layout};
///
/// # use ratatui::layout::{Layout, Constraint::*};
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).spacing(2);
/// ```
///
@@ -440,8 +426,7 @@ impl Layout {
/// # Examples
///
/// ```rust
/// use ratatui::{layout::{Layout, Constraint}, Frame};
///
/// # use ratatui::prelude::*;
/// # fn render(frame: &mut Frame) {
/// let area = frame.area();
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
@@ -473,8 +458,7 @@ impl Layout {
/// # Examples
///
/// ```rust
/// use ratatui::{layout::{Layout, Constraint}, Frame};
///
/// # use ratatui::prelude::*;
/// # fn render(frame: &mut Frame) {
/// let area = frame.area();
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
@@ -513,7 +497,7 @@ impl Layout {
/// # Examples
///
/// ```
/// use ratatui::layout::{Constraint, Direction, Layout, Rect};
/// # use ratatui::prelude::*;
/// let layout = Layout::default()
/// .direction(Direction::Vertical)
/// .constraints([Constraint::Length(5), Constraint::Min(0)])
@@ -545,8 +529,7 @@ impl Layout {
/// # Examples
///
/// ```
/// use ratatui::layout::{Constraint, Direction, Layout, Rect};
///
/// # use ratatui::prelude::*;
/// let (areas, spacers) = Layout::default()
/// .direction(Direction::Vertical)
/// .constraints([Constraint::Length(5), Constraint::Min(0)])
@@ -719,35 +702,35 @@ fn configure_constraints(
constraints: &[Constraint],
flex: Flex,
) -> Result<(), AddConstraintError> {
for (&constraint, &segment) in constraints.iter().zip(segments.iter()) {
for (&constraint, &element) in constraints.iter().zip(segments.iter()) {
match constraint {
Constraint::Max(max) => {
solver.add_constraint(segment.has_max_size(max, MAX_SIZE_LE))?;
solver.add_constraint(segment.has_int_size(max, MAX_SIZE_EQ))?;
solver.add_constraint(element.has_max_size(max, MAX_SIZE_LE))?;
solver.add_constraint(element.has_int_size(max, MAX_SIZE_EQ))?;
}
Constraint::Min(min) => {
solver.add_constraint(segment.has_min_size(min, MIN_SIZE_GE))?;
solver.add_constraint(element.has_min_size(min, MIN_SIZE_GE))?;
if flex.is_legacy() {
solver.add_constraint(segment.has_int_size(min, MIN_SIZE_EQ))?;
solver.add_constraint(element.has_int_size(min, MIN_SIZE_EQ))?;
} else {
solver.add_constraint(segment.has_size(area, FILL_GROW))?;
solver.add_constraint(element.has_size(area, FILL_GROW))?;
}
}
Constraint::Length(length) => {
solver.add_constraint(segment.has_int_size(length, LENGTH_SIZE_EQ))?;
solver.add_constraint(element.has_int_size(length, LENGTH_SIZE_EQ))?;
}
Constraint::Percentage(p) => {
let size = area.size() * f64::from(p) / 100.00;
solver.add_constraint(segment.has_size(size, PERCENTAGE_SIZE_EQ))?;
solver.add_constraint(element.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(segment.has_size(size, RATIO_SIZE_EQ))?;
solver.add_constraint(element.has_size(size, RATIO_SIZE_EQ))?;
}
Constraint::Fill(_) => {
// given no other constraints, this segment will grow as much as possible.
solver.add_constraint(segment.has_size(area, FILL_GROW))?;
solver.add_constraint(element.has_size(area, FILL_GROW))?;
}
}
}
@@ -854,7 +837,7 @@ fn configure_fill_constraints(
constraints: &[Constraint],
flex: Flex,
) -> Result<(), AddConstraintError> {
for ((&left_constraint, &left_segment), (&right_constraint, &right_segment)) in constraints
for ((&left_constraint, &left_element), (&right_constraint, &right_element)) in constraints
.iter()
.zip(segments.iter())
.filter(|(c, _)| c.is_fill() || (!flex.is_legacy() && c.is_min()))
@@ -871,9 +854,9 @@ fn configure_fill_constraints(
_ => unreachable!(),
};
solver.add_constraint(
(right_scaling_factor * left_segment.size())
(right_scaling_factor * left_element.size())
| EQ(GROW)
| (left_scaling_factor * right_segment.size()),
| (left_scaling_factor * right_element.size()),
)?;
}
Ok(())
@@ -1310,9 +1293,9 @@ mod tests {
use rstest::rstest;
use crate::{
buffer::Buffer,
layout::{Constraint, Constraint::*, Direction, Flex, Layout, Rect},
widgets::{Paragraph, Widget},
layout::flex::Flex,
prelude::{Constraint::*, *},
// widgets::Paragraph, // TODO
};
/// Test that the given constraints applied to the given area result in the expected layout.
@@ -1334,7 +1317,7 @@ mod tests {
let mut buffer = Buffer::empty(area);
for (c, &area) in ('a'..='z').take(constraints.len()).zip(layout.iter()) {
let s = c.to_string().repeat(area.width as usize);
Paragraph::new(s).render(area, &mut buffer);
// Paragraph::new(s).render(area, &mut buffer); // TODO
}
assert_eq!(buffer, Buffer::with_lines([expected]));
}
@@ -1864,7 +1847,7 @@ mod tests {
])
.split(target);
assert_eq!(chunks.iter().map(|r| r.height).sum::<u16>(), target.height);
assert_eq!(target.height, chunks.iter().map(|r| r.height).sum::<u16>());
chunks.windows(2).for_each(|w| assert!(w[0].y <= w[1].y));
}
@@ -1957,7 +1940,7 @@ mod tests {
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(r, expected);
assert_eq!(expected, r);
}
#[rstest]
@@ -1976,7 +1959,7 @@ mod tests {
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(r, expected);
assert_eq!(expected, r);
}
#[rstest]
@@ -2007,7 +1990,7 @@ mod tests {
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(r, expected);
assert_eq!(expected, r);
}
#[rstest]
@@ -2037,7 +2020,7 @@ mod tests {
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(r, expected);
assert_eq!(expected, r);
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(&constraints)
@@ -2046,7 +2029,7 @@ mod tests {
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(r, expected);
assert_eq!(expected, r);
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(&constraints)
@@ -2055,7 +2038,7 @@ mod tests {
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(r, expected);
assert_eq!(expected, r);
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(&constraints)
@@ -2064,7 +2047,7 @@ mod tests {
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(r, expected);
assert_eq!(expected, r);
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(&constraints)
@@ -2073,7 +2056,7 @@ mod tests {
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(r, expected);
assert_eq!(expected, r);
}
#[rstest]
@@ -2087,7 +2070,7 @@ mod tests {
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(r, expected);
assert_eq!(expected, r);
}
#[rstest]
@@ -2134,7 +2117,7 @@ mod tests {
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(r, expected);
assert_eq!(expected, r);
}
#[rstest]
@@ -2151,7 +2134,7 @@ mod tests {
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(r, expected);
assert_eq!(expected, r);
}
#[rstest]
@@ -2169,7 +2152,7 @@ mod tests {
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(r, expected);
assert_eq!(expected, r);
}
#[rstest]
@@ -2233,7 +2216,7 @@ mod tests {
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(r, expected);
assert_eq!(expected, r);
}
#[rstest]
@@ -2259,7 +2242,7 @@ mod tests {
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(result, expected);
assert_eq!(expected, result);
}
#[rstest]
@@ -2291,7 +2274,7 @@ mod tests {
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(r, expected);
assert_eq!(expected, r);
}
#[rstest]
@@ -2315,7 +2298,7 @@ mod tests {
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(r, expected);
assert_eq!(expected, r);
}
#[rstest]
@@ -2340,7 +2323,7 @@ mod tests {
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(result, expected);
assert_eq!(expected, result);
}
#[rstest]
@@ -2383,7 +2366,7 @@ mod tests {
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(result, expected);
assert_eq!(expected, result);
}
#[rstest]
@@ -2403,7 +2386,7 @@ mod tests {
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(result, expected);
assert_eq!(expected, result);
}
#[rstest]
@@ -2427,7 +2410,7 @@ mod tests {
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(result, expected);
assert_eq!(expected, result);
}
#[rstest]
@@ -2453,7 +2436,7 @@ mod tests {
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(result, expected);
assert_eq!(expected, result);
}
#[rstest]
@@ -2479,7 +2462,7 @@ mod tests {
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(result, expected);
assert_eq!(expected, result);
}
#[rstest]
@@ -2500,7 +2483,7 @@ mod tests {
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(result, expected);
assert_eq!(expected, result);
}
}

View File

@@ -4,7 +4,8 @@ use std::{
fmt,
};
use crate::layout::{Margin, Position, Size};
use super::{Position, Size};
use crate::prelude::*;
mod iter;
pub use iter::*;
@@ -26,7 +27,7 @@ pub struct Rect {
pub height: u16,
}
/// Amounts by which to move a [`Rect`](crate::layout::Rect).
/// Amounts by which to move a [`Rect`](super::Rect).
///
/// Positive numbers move to the right/bottom and negative to the left/top.
///
@@ -55,41 +56,32 @@ impl Rect {
height: 0,
};
/// 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
};
/// 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)
};
Self {
x,
y,
width,
height,
width: clipped_width,
height: clipped_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) -> u32 {
(self.width as u32) * (self.height as u32)
pub const fn area(self) -> u16 {
self.width.saturating_mul(self.height)
}
/// Returns true if the `Rect` has no area.
@@ -213,8 +205,7 @@ impl Rect {
/// # Examples
///
/// ```rust
/// use ratatui::layout::{Position, Rect};
///
/// # use ratatui::{prelude::*, layout::Position};
/// let rect = Rect::new(1, 2, 3, 4);
/// assert!(rect.contains(Position { x: 1, y: 2 }));
/// ````
@@ -243,8 +234,7 @@ impl Rect {
/// # Examples
///
/// ```rust
/// use ratatui::{layout::Rect, Frame};
///
/// # use ratatui::prelude::*;
/// # fn render(frame: &mut Frame) {
/// let area = frame.area();
/// let rect = Rect::new(0, 0, 100, 100).clamp(area);
@@ -264,8 +254,7 @@ impl Rect {
/// # Example
///
/// ```
/// use ratatui::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
///
/// # use ratatui::prelude::*;
/// fn render(area: Rect, buf: &mut Buffer) {
/// for row in area.rows() {
/// Line::raw("Hello, world!").render(row, buf);
@@ -281,11 +270,7 @@ impl Rect {
/// # Example
///
/// ```
/// use ratatui::{
/// buffer::Buffer,
/// layout::Rect,
/// widgets::{Block, Borders, Widget},
/// };
/// # use ratatui::{prelude::*, widgets::*};
/// fn render(area: Rect, buf: &mut Buffer) {
/// if let Some(left) = area.columns().next() {
/// Block::new().borders(Borders::LEFT).render(left, buf);
@@ -303,8 +288,7 @@ impl Rect {
/// # Example
///
/// ```
/// use ratatui::{buffer::Buffer, layout::Rect};
///
/// # use ratatui::prelude::*;
/// fn render(area: Rect, buf: &mut Buffer) {
/// for position in area.positions() {
/// buf[(position.x, position.y)].set_symbol("x");
@@ -320,8 +304,7 @@ impl Rect {
/// # Examples
///
/// ```
/// use ratatui::layout::Rect;
///
/// # use ratatui::prelude::*;
/// let rect = Rect::new(1, 2, 3, 4);
/// let position = rect.as_position();
/// ````
@@ -369,7 +352,6 @@ mod tests {
use rstest::rstest;
use super::*;
use crate::layout::{Constraint, Layout};
#[test]
fn to_string() {
@@ -514,28 +496,46 @@ mod tests {
#[test]
fn size_truncation() {
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
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
);
}
);
}
// 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() {
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
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);
}
);
}
// 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: u32 = RECT.area();
const _AREA: u16 = RECT.area();
const _LEFT: u16 = RECT.left();
const _RIGHT: u16 = RECT.right();
const _TOP: u16 = RECT.top();

View File

@@ -0,0 +1,143 @@
use crate::prelude::*;
/// 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,
}
impl Rows {
/// Creates a new `Rows` iterator.
pub const fn new(rect: Rect) -> Self {
Self {
rect,
current_row: rect.y,
}
}
}
impl Iterator for Rows {
type Item = Rect;
/// Retrieves the next row within the `Rect`.
///
/// 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() {
return None;
}
let row = Rect::new(self.rect.x, self.current_row, self.rect.width, 1);
self.current_row += 1;
Some(row)
}
}
/// 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,
}
impl Columns {
/// Creates a new `Columns` iterator.
pub const fn new(rect: Rect) -> Self {
Self {
rect,
current_column: rect.x,
}
}
}
impl Iterator for Columns {
type Item = Rect;
/// Retrieves the next column within the `Rect`.
///
/// 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() {
return None;
}
let column = Rect::new(self.current_column, self.rect.y, 1, self.rect.height);
self.current_column += 1;
Some(column)
}
}
/// An iterator over positions within a `Rect`.
///
/// The iterator will yield all positions within the `Rect` in a row-major order.
pub struct Positions {
/// The `Rect` associated with the positions.
pub rect: Rect,
/// The current position within the `Rect`.
pub current_position: Position,
}
impl Positions {
/// Creates a new `Positions` iterator.
pub const fn new(rect: Rect) -> Self {
Self {
rect,
current_position: Position::new(rect.x, rect.y),
}
}
}
impl Iterator for Positions {
type Item = Position;
/// Retrieves the next position within the `Rect`.
///
/// Returns `None` when there are no more positions to iterate through.
fn next(&mut self) -> Option<Self::Item> {
if self.current_position.y >= self.rect.bottom() {
return None;
}
let position = self.current_position;
self.current_position.x += 1;
if self.current_position.x >= self.rect.right() {
self.current_position.x = self.rect.x;
self.current_position.y += 1;
}
Some(position)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rows() {
let rect = Rect::new(0, 0, 2, 2);
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(), None);
}
#[test]
fn columns() {
let rect = Rect::new(0, 0, 2, 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(), None);
}
#[test]
fn positions() {
let rect = Rect::new(0, 0, 2, 2);
let mut positions = Positions::new(rect);
assert_eq!(positions.next(), Some(Position::new(0, 0)));
assert_eq!(positions.next(), Some(Position::new(1, 0)));
assert_eq!(positions.next(), Some(Position::new(0, 1)));
assert_eq!(positions.next(), Some(Position::new(1, 1)));
assert_eq!(positions.next(), None);
}
}

View File

@@ -1,7 +1,7 @@
#![warn(missing_docs)]
use std::fmt;
use crate::layout::Rect;
use crate::prelude::*;
/// A simple size struct
///

View File

@@ -0,0 +1,41 @@
//! A prelude for conveniently writing applications using this library.
//!
//! ```rust,no_run
//! use ratatui::prelude::*;
//! ```
//!
//! Aside from the main types that are used in the library, this prelude also re-exports several
//! modules to make it easy to qualify types that would otherwise collide. E.g.:
//!
//! ```rust
//! use ratatui::{prelude::*, widgets::*};
//!
//! #[derive(Debug, Default, PartialEq, Eq)]
//! struct Line;
//!
//! assert_eq!(Line::default(), Line);
//! assert_eq!(text::Line::default(), ratatui::text::Line::from(vec![]));
//! ```
// TODO: re-export the following modules:
// #[cfg(feature = "crossterm")]
// pub use crate::backend::CrosstermBackend;
// #[cfg(all(not(windows), feature = "termion"))]
// 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},
layout::{self, Alignment, Constraint, Direction, Layout, Margin, Position, Rect, Size},
style::{self, Color, Modifier, Style, Stylize},
symbols::{self},
text::{self, Line, Masked, Span, Text},
widgets::{
// block::BlockExt, // TODO
StatefulWidget,
Widget,
},
Frame, Terminal,
};

View File

@@ -13,10 +13,7 @@
//! ## Example
//!
//! ```
//! use ratatui::{
//! style::{Color, Modifier, Style},
//! text::Span,
//! };
//! use ratatui::prelude::*;
//!
//! let heading_style = Style::new()
//! .fg(Color::Black)
@@ -44,11 +41,7 @@
//! ## Example
//!
//! ```
//! use ratatui::{
//! style::{Color, Modifier, Style, Stylize},
//! text::Span,
//! widgets::Paragraph,
//! };
//! use ratatui::{prelude::*, widgets::*};
//!
//! assert_eq!(
//! "hello".red().on_blue().bold(),
@@ -99,7 +92,7 @@ bitflags! {
/// ## Examples
///
/// ```rust
/// use ratatui::style::Modifier;
/// use ratatui::{prelude::*};
///
/// let m = Modifier::BOLD | Modifier::ITALIC;
/// ```
@@ -135,7 +128,7 @@ impl fmt::Debug for Modifier {
/// Style lets you control the main characteristics of the displayed elements.
///
/// ```rust
/// use ratatui::style::{Color, Modifier, Style};
/// use ratatui::prelude::*;
///
/// Style::default()
/// .fg(Color::Black)
@@ -146,8 +139,7 @@ impl fmt::Debug for Modifier {
/// Styles can also be created with a [shorthand notation](crate::style#using-style-shorthands).
///
/// ```rust
/// use ratatui::style::{Style, Stylize};
///
/// # use ratatui::prelude::*;
/// Style::new().black().on_green().italic().bold();
/// ```
///
@@ -157,11 +149,7 @@ impl fmt::Debug for Modifier {
/// anywhere that accepts `Into<Style>`.
///
/// ```rust
/// use ratatui::{
/// style::{Color, Modifier, Style},
/// text::Line,
/// };
///
/// # use ratatui::prelude::*;
/// Line::styled("hello", Style::new().fg(Color::Red));
/// // simplifies to
/// Line::styled("hello", Color::Red);
@@ -176,11 +164,7 @@ impl fmt::Debug for Modifier {
/// just S3.
///
/// ```rust
/// use ratatui::{
/// buffer::Buffer,
/// layout::Rect,
/// style::{Color, Modifier, Style},
/// };
/// use ratatui::prelude::*;
///
/// let styles = [
/// Style::default()
@@ -216,11 +200,7 @@ impl fmt::Debug for Modifier {
/// reset all properties until that point use [`Style::reset`].
///
/// ```
/// use ratatui::{
/// buffer::Buffer,
/// layout::Rect,
/// style::{Color, Modifier, Style},
/// };
/// use ratatui::prelude::*;
///
/// let styles = [
/// Style::default()
@@ -260,7 +240,46 @@ pub struct Style {
impl fmt::Debug for Style {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Style::new()")?;
self.fmt_stylize(f)?;
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(())
}
}
@@ -306,8 +325,7 @@ impl Style {
/// ## Examples
///
/// ```rust
/// use ratatui::style::{Color, Style};
///
/// # use ratatui::prelude::*;
/// let style = Style::default().fg(Color::Blue);
/// let diff = Style::default().fg(Color::Red);
/// assert_eq!(style.patch(diff), Style::default().fg(Color::Red));
@@ -323,8 +341,7 @@ impl Style {
/// ## Examples
///
/// ```rust
/// use ratatui::style::{Color, Style};
///
/// # use ratatui::prelude::*;
/// let style = Style::default().bg(Color::Blue);
/// let diff = Style::default().bg(Color::Red);
/// assert_eq!(style.patch(diff), Style::default().bg(Color::Red));
@@ -348,8 +365,7 @@ impl Style {
/// ## Examples
///
/// ```rust
/// use ratatui::style::{Color, Modifier, Style};
///
/// # use ratatui::prelude::*;
/// let style = Style::default()
/// .underline_color(Color::Blue)
/// .add_modifier(Modifier::UNDERLINED);
@@ -377,8 +393,7 @@ impl Style {
/// ## Examples
///
/// ```rust
/// use ratatui::style::{Modifier, Style};
///
/// # use ratatui::prelude::*;
/// let style = Style::default().add_modifier(Modifier::BOLD);
/// let diff = Style::default().add_modifier(Modifier::ITALIC);
/// let patched = style.patch(diff);
@@ -399,8 +414,7 @@ impl Style {
/// ## Examples
///
/// ```rust
/// use ratatui::style::{Modifier, Style};
///
/// # use ratatui::prelude::*;
/// let style = Style::default().add_modifier(Modifier::BOLD | Modifier::ITALIC);
/// let diff = Style::default().remove_modifier(Modifier::ITALIC);
/// let patched = style.patch(diff);
@@ -422,8 +436,7 @@ impl Style {
///
/// ## Examples
/// ```
/// use ratatui::style::{Color, Modifier, Style};
///
/// # use ratatui::prelude::*;
/// let style_1 = Style::default().fg(Color::Yellow);
/// let style_2 = Style::default().bg(Color::Red);
/// let combined = style_1.patch(style_2);
@@ -450,54 +463,6 @@ 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 {
@@ -508,8 +473,7 @@ impl From<Color> for Style {
/// # Example
///
/// ```rust
/// use ratatui::style::{Color, Style};
///
/// # use ratatui::prelude::*;
/// let style = Style::from(Color::Red);
/// ```
fn from(color: Color) -> Self {
@@ -523,8 +487,7 @@ impl From<(Color, Color)> for Style {
/// # Example
///
/// ```rust
/// use ratatui::style::{Color, Style};
///
/// # use ratatui::prelude::*;
/// // red foreground, blue background
/// let style = Style::from((Color::Red, Color::Blue));
/// // default foreground, blue background
@@ -546,8 +509,7 @@ impl From<Modifier> for Style {
/// # Example
///
/// ```rust
/// use ratatui::style::{Style, Modifier};
///
/// # use ratatui::prelude::*;
/// // add bold and italic
/// let style = Style::from(Modifier::BOLD|Modifier::ITALIC);
fn from(modifier: Modifier) -> Self {
@@ -561,8 +523,7 @@ impl From<(Modifier, Modifier)> for Style {
/// # Example
///
/// ```rust
/// use ratatui::style::{Modifier, Style};
///
/// # use ratatui::prelude::*;
/// // add bold and italic, remove dim
/// let style = Style::from((Modifier::BOLD | Modifier::ITALIC, Modifier::DIM));
/// ```
@@ -581,8 +542,7 @@ impl From<(Color, Modifier)> for Style {
/// # Example
///
/// ```rust
/// use ratatui::style::{Color, Modifier, Style};
///
/// # use ratatui::prelude::*;
/// // red foreground, add bold and italic
/// let style = Style::from((Color::Red, Modifier::BOLD | Modifier::ITALIC));
/// ```
@@ -599,8 +559,7 @@ impl From<(Color, Color, Modifier)> for Style {
/// # Example
///
/// ```rust
/// use ratatui::style::{Color, Modifier, Style};
///
/// # use ratatui::prelude::*;
/// // red foreground, blue background, add bold and italic
/// let style = Style::from((Color::Red, Color::Blue, Modifier::BOLD | Modifier::ITALIC));
/// ```
@@ -616,8 +575,7 @@ impl From<(Color, Color, Modifier, Modifier)> for Style {
/// # Example
///
/// ```rust
/// use ratatui::style::{Color, Modifier, Style};
///
/// # use ratatui::prelude::*;
/// // red foreground, blue background, add bold and italic, remove dim
/// let style = Style::from((
/// Color::Red,
@@ -647,10 +605,6 @@ 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);
}

View File

@@ -44,7 +44,7 @@ use crate::style::stylize::{ColorDebug, ColorDebugKind};
/// ```
/// use std::str::FromStr;
///
/// use ratatui::style::Color;
/// use ratatui::prelude::*;
///
/// assert_eq!(Color::from_str("red"), Ok(Color::Red));
/// assert_eq!("red".parse(), Ok(Color::Red));
@@ -168,9 +168,7 @@ impl<'de> serde::Deserialize<'de> for Color {
/// # Examples
///
/// ```
/// use std::str::FromStr;
///
/// use ratatui::style::Color;
/// use ratatui::prelude::*;
///
/// #[derive(Debug, serde::Deserialize)]
/// struct Theme {
@@ -265,7 +263,7 @@ impl std::error::Error for ParseColorError {}
/// ```
/// use std::str::FromStr;
///
/// use ratatui::style::Color;
/// use ratatui::prelude::*;
///
/// let color: Color = Color::from_str("blue").unwrap();
/// assert_eq!(color, Color::Blue);
@@ -381,7 +379,7 @@ impl Color {
/// # Examples
///
/// ```
/// use ratatui::style::Color;
/// use ratatui::prelude::*;
///
/// let color: Color = Color::from_hsl(360.0, 100.0, 100.0);
/// assert_eq!(color, Color::Rgb(255, 255, 255));
@@ -398,41 +396,6 @@ 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
@@ -548,34 +511,6 @@ 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));

View File

@@ -403,10 +403,8 @@
//! # Example
//!
//! ```rust
//! use ratatui::style::{
//! palette::material::{BLUE, RED},
//! Color,
//! };
//! # use ratatui::prelude::*;
//! use ratatui::style::palette::material::{BLUE, RED};
//!
//! assert_eq!(RED.c500, Color::Rgb(244, 67, 54));
//! assert_eq!(BLUE.c500, Color::Rgb(33, 150, 243));
@@ -414,7 +412,7 @@
//!
//! [`matdesign-color` crate]: https://crates.io/crates/matdesign-color
use crate::style::Color;
use crate::prelude::*;
/// A palette of colors for use in Material design with accent colors
///

View File

@@ -268,16 +268,14 @@
//! # Example
//!
//! ```rust
//! use ratatui::style::{
//! palette::tailwind::{BLUE, RED},
//! Color,
//! };
//! # use ratatui::prelude::*;
//! use ratatui::style::palette::tailwind::{BLUE, RED};
//!
//! assert_eq!(RED.c500, Color::Rgb(239, 68, 68));
//! assert_eq!(BLUE.c500, Color::Rgb(59, 130, 246));
//! ```
use crate::style::Color;
use crate::prelude::*;
pub struct Palette {
pub c50: Color,

View File

@@ -7,7 +7,7 @@ use ::palette::{
};
use palette::{stimulus::IntoStimulus, Srgb};
use crate::style::Color;
use super::Color;
/// Convert an [`palette::Srgb`] color to a [`Color`].
///

View File

@@ -196,11 +196,7 @@ macro_rules! modifier {
///
/// # Examples
/// ```
/// use ratatui::{
/// style::{Color, Modifier, Style, Stylize},
/// text::Line,
/// widgets::{Block, Paragraph},
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// let span = "hello".red().on_blue().bold();
/// let line = Line::from(vec![

View File

@@ -155,7 +155,7 @@ pub enum Marker {
}
pub mod scrollbar {
use crate::symbols::{block, line};
use super::{block, line};
/// Scrollbar Set
/// ```text

View File

@@ -1,4 +1,4 @@
use crate::symbols::{block, line};
use super::{block, line};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct Set {

View File

@@ -13,7 +13,7 @@
//! ```rust,no_run
//! use std::io::stdout;
//!
//! use ratatui::{backend::CrosstermBackend, widgets::Paragraph, Terminal};
//! use ratatui::{prelude::*, widgets::Paragraph};
//!
//! let backend = CrosstermBackend::new(stdout());
//! let mut terminal = Terminal::new(backend)?;
@@ -32,15 +32,9 @@
//! [`Buffer`]: crate::buffer::Buffer
mod frame;
#[cfg(feature = "crossterm")]
mod init;
mod terminal;
mod viewport;
pub use frame::{CompletedFrame, Frame};
#[cfg(feature = "crossterm")]
pub use init::{
init, init_with_options, restore, try_init, try_init_with_options, try_restore, DefaultTerminal,
};
pub use terminal::{Options as TerminalOptions, Terminal};
pub use viewport::Viewport;

View File

@@ -1,8 +1,4 @@
use crate::{
buffer::Buffer,
layout::{Position, Rect},
widgets::{StatefulWidget, StatefulWidgetRef, Widget, WidgetRef},
};
use crate::prelude::*;
/// A consistent view into the terminal state for rendering a single frame.
///
@@ -14,7 +10,6 @@ use crate::{
/// 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?
@@ -36,8 +31,6 @@ 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.
@@ -80,12 +73,10 @@ impl Frame<'_> {
/// # Example
///
/// ```rust
/// # use ratatui::{backend::TestBackend, Terminal};
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::Block};
/// # 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);
@@ -105,12 +96,10 @@ impl Frame<'_> {
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// # use ratatui::{backend::TestBackend, Terminal};
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::Block};
/// # 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);
@@ -133,15 +122,10 @@ impl Frame<'_> {
/// # Example
///
/// ```rust
/// # use ratatui::{backend::TestBackend, Terminal};
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::*};
/// # 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);
@@ -169,15 +153,10 @@ impl Frame<'_> {
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// # use ratatui::{backend::TestBackend, Terminal};
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::*};
/// # 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);
@@ -199,10 +178,6 @@ 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());
}
@@ -213,10 +188,6 @@ 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 });
@@ -244,7 +215,7 @@ impl Frame<'_> {
/// # Examples
///
/// ```rust
/// # use ratatui::{backend::TestBackend, Terminal};
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::*};
/// # let backend = TestBackend::new(5, 5);
/// # let mut terminal = Terminal::new(backend).unwrap();
/// # let mut frame = terminal.get_frame();

View File

@@ -1,10 +1,7 @@
use std::io;
use crate::{
backend::{Backend, ClearType},
buffer::{Buffer, Cell},
layout::{Position, Rect, Size},
CompletedFrame, Frame, TerminalOptions, Viewport,
backend::ClearType, buffer::Cell, prelude::*, CompletedFrame, TerminalOptions, Viewport,
};
/// An interface to interact and draw [`Frame`]s on the user's terminal.
@@ -33,9 +30,10 @@ use crate::{
/// # Examples
///
/// ```rust,no_run
/// # use ratatui::prelude::*;
/// use std::io::stdout;
///
/// use ratatui::{backend::CrosstermBackend, widgets::Paragraph, Terminal};
/// use ratatui::widgets::Paragraph;
///
/// let backend = CrosstermBackend::new(stdout());
/// let mut terminal = Terminal::new(backend)?;
@@ -109,10 +107,8 @@ where
/// # Example
///
/// ```rust,no_run
/// use std::io::stdout;
///
/// use ratatui::{backend::CrosstermBackend, Terminal};
///
/// # use std::io::stdout;
/// # use ratatui::prelude::*;
/// let backend = CrosstermBackend::new(stdout());
/// let terminal = Terminal::new(backend)?;
/// # std::io::Result::Ok(())
@@ -131,10 +127,8 @@ where
/// # Example
///
/// ```rust
/// use std::io::stdout;
///
/// use ratatui::{backend::CrosstermBackend, layout::Rect, Terminal, TerminalOptions, Viewport};
///
/// # use std::io::stdout;
/// # use ratatui::{prelude::*, backend::TestBackend, Viewport, TerminalOptions};
/// let backend = CrosstermBackend::new(stdout());
/// let viewport = Viewport::Fixed(Rect::new(0, 0, 10, 10));
/// let terminal = Terminal::with_options(backend, TerminalOptions { viewport })?;
@@ -282,9 +276,10 @@ where
/// # Examples
///
/// ```
/// # use ratatui::layout::Position;
/// # let backend = ratatui::backend::TestBackend::new(10, 10);
/// # let mut terminal = ratatui::Terminal::new(backend)?;
/// use ratatui::{layout::Position, widgets::Paragraph};
/// use ratatui::widgets::Paragraph;
///
/// // with a closure
/// terminal.draw(|frame| {
@@ -557,13 +552,7 @@ where
/// ## Insert a single line before the current viewport
///
/// ```rust
/// use ratatui::{
/// backend::TestBackend,
/// style::{Color, Style},
/// text::{Line, Span},
/// widgets::{Paragraph, Widget},
/// Terminal,
/// };
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::*};
/// # let backend = TestBackend::new(10, 10);
/// # let mut terminal = Terminal::new(backend).unwrap();
/// terminal.insert_before(1, |buf| {
@@ -579,22 +568,10 @@ where
where
F: FnOnce(&mut Buffer),
{
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(()),
if !matches!(self.viewport, Viewport::Inline(_)) {
return 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.
@@ -680,86 +657,6 @@ 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>(
@@ -781,33 +678,7 @@ 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(

View File

@@ -1,6 +1,6 @@
use std::fmt;
use crate::layout::Rect;
use crate::prelude::*;
/// 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,8 +14,6 @@ use crate::layout::Rect;
/// 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

View File

@@ -19,11 +19,7 @@
//! its `title` property (which is a [`Line`] under the hood):
//!
//! ```rust
//! use ratatui::{
//! style::{Color, Style},
//! text::{Line, Span},
//! widgets::Block,
//! };
//! use ratatui::{prelude::*, widgets::*};
//!
//! // A simple string with no styling.
//! // Converted to Line(vec![

View File

@@ -1,4 +1,4 @@
use crate::style::{Style, Styled};
use crate::{prelude::*, style::Styled};
const NBSP: &str = "\u{00a0}";
const ZWSP: &str = "\u{200b}";
@@ -19,8 +19,6 @@ 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,
@@ -28,7 +26,7 @@ impl<'a> StyledGrapheme<'a> {
}
}
pub(crate) fn is_whitespace(&self) -> bool {
pub fn is_whitespace(&self) -> bool {
let symbol = self.symbol;
symbol == ZWSP || symbol.chars().all(char::is_whitespace) && symbol != NBSP
}
@@ -50,7 +48,6 @@ impl<'a> Styled for StyledGrapheme<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::style::Stylize;
#[test]
fn new() {

View File

@@ -4,13 +4,7 @@ use std::{borrow::Cow, fmt};
use unicode_truncate::UnicodeTruncateStr;
use crate::{
buffer::Buffer,
layout::{Alignment, Rect},
style::{Style, Styled},
text::{Span, StyledGrapheme, Text},
widgets::{Widget, WidgetRef},
};
use crate::{prelude::*, style::Styled, text::StyledGrapheme};
/// A line of text, consisting of one or more [`Span`]s.
///
@@ -75,10 +69,7 @@ use crate::{
/// [`Style`].
///
/// ```rust
/// use ratatui::{
/// style::{Color, Modifier, Style, Stylize},
/// text::{Line, Span},
/// };
/// use ratatui::prelude::*;
///
/// let style = Style::new().yellow();
/// let line = Line::raw("Hello, world!").style(style);
@@ -102,11 +93,7 @@ use crate::{
/// methods of the [`Stylize`] trait.
///
/// ```rust
/// use ratatui::{
/// style::{Color, Modifier, Style, Stylize},
/// text::Line,
/// };
///
/// # use ratatui::prelude::*;
/// 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));
@@ -121,8 +108,7 @@ use crate::{
/// ignored and the line is truncated.
///
/// ```rust
/// use ratatui::{layout::Alignment, text::Line};
///
/// # use ratatui::prelude::*;
/// let line = Line::from("Hello world!").alignment(Alignment::Right);
/// let line = Line::from("Hello world!").centered();
/// let line = Line::from("Hello world!").left_aligned();
@@ -134,15 +120,7 @@ use crate::{
/// `Line` implements the [`Widget`] trait, which means it can be rendered to a [`Buffer`].
///
/// ```rust
/// use ratatui::{
/// buffer::Buffer,
/// layout::Rect,
/// style::{Style, Stylize},
/// text::Line,
/// widgets::Widget,
/// Frame,
/// };
///
/// # use ratatui::prelude::*;
/// # fn render(area: Rect, buf: &mut Buffer) {
/// // in another widget's render method
/// let line = Line::from("Hello world!").style(Style::new().yellow().italic());
@@ -161,14 +139,7 @@ use crate::{
/// provides more functionality.
///
/// ```rust
/// use ratatui::{
/// buffer::Buffer,
/// layout::Rect,
/// style::Stylize,
/// text::Line,
/// widgets::{Paragraph, Widget, Wrap},
/// };
///
/// # use ratatui::{prelude::*, widgets::*};
/// # fn render(area: Rect, buf: &mut Buffer) {
/// let line = Line::from("Hello world!").yellow().italic();
/// Paragraph::new(line)
@@ -178,7 +149,6 @@ use crate::{
/// ```
///
/// [`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.
@@ -193,28 +163,18 @@ pub struct Line<'a> {
impl fmt::Debug for Line<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
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(")")?;
if self.style == Style::default() && self.alignment.is_none() {
f.write_str("Line ")?;
return f.debug_list().entries(&self.spans).finish();
}
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(()),
let mut debug = f.debug_struct("Line");
if self.style != Style::default() {
debug.field("style", &self.style);
}
if let Some(alignment) = self.alignment {
debug.field("alignment", &format!("Alignment::{alignment}"));
}
debug.field("spans", &self.spans).finish()
}
}
@@ -239,10 +199,8 @@ impl<'a> Line<'a> {
/// # Examples
///
/// ```rust
/// use std::borrow::Cow;
///
/// use ratatui::text::Line;
///
/// # use ratatui::prelude::*;
/// # use std::borrow::Cow;
/// Line::raw("test content");
/// Line::raw(String::from("test content"));
/// Line::raw(Cow::from("test content"));
@@ -270,20 +228,13 @@ impl<'a> Line<'a> {
/// Any newlines in the content are removed.
///
/// ```rust
/// use std::borrow::Cow;
///
/// use ratatui::{
/// style::{Style, Stylize},
/// text::Line,
/// };
///
/// # use ratatui::prelude::*;
/// # use std::borrow::Cow;
/// 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>>,
@@ -304,8 +255,7 @@ impl<'a> Line<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{style::Stylize, text::Line};
///
/// # use ratatui::prelude::*;
/// let line = Line::default().spans(vec!["Hello".blue(), " world!".green()]);
/// let line = Line::default().spans([1, 2, 3].iter().map(|i| format!("Item {}", i)));
/// ```
@@ -332,15 +282,9 @@ impl<'a> Line<'a> {
///
/// # Examples
/// ```rust
/// use ratatui::{
/// style::{Style, Stylize},
/// text::Line,
/// };
///
/// # use ratatui::prelude::*;
/// 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();
@@ -356,8 +300,7 @@ impl<'a> Line<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{layout::Alignment, text::Line};
///
/// # use ratatui::prelude::*;
/// let mut line = Line::from("Hi, what's up?");
/// assert_eq!(None, line.alignment);
/// assert_eq!(
@@ -382,8 +325,7 @@ impl<'a> Line<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::text::Line;
///
/// # use ratatui::prelude::*;
/// let line = Line::from("Hi, what's up?").left_aligned();
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -400,8 +342,7 @@ impl<'a> Line<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::text::Line;
///
/// # use ratatui::prelude::*;
/// let line = Line::from("Hi, what's up?").centered();
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -418,8 +359,7 @@ impl<'a> Line<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::text::Line;
///
/// # use ratatui::prelude::*;
/// let line = Line::from("Hi, what's up?").right_aligned();
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -432,8 +372,7 @@ impl<'a> Line<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{style::Stylize, text::Line};
///
/// # use ratatui::prelude::*;
/// let line = Line::from(vec!["Hello".blue(), " world!".green()]);
/// assert_eq!(12, line.width());
/// ```
@@ -454,10 +393,7 @@ impl<'a> Line<'a> {
/// ```rust
/// use std::iter::Iterator;
///
/// use ratatui::{
/// style::{Color, Style},
/// text::{Line, StyledGrapheme},
/// };
/// use ratatui::{prelude::*, text::StyledGrapheme};
///
/// let line = Line::styled("Text", Style::default().fg(Color::Yellow));
/// let style = Style::default().fg(Color::Green).bg(Color::Black);
@@ -472,8 +408,6 @@ impl<'a> Line<'a> {
/// ]
/// );
/// ```
///
/// [`Color`]: crate::style::Color
pub fn styled_graphemes<S: Into<Style>>(
&'a self,
base_style: S,
@@ -498,19 +432,13 @@ impl<'a> Line<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{
/// style::{Color, Modifier},
/// text::Line,
/// };
///
/// # use ratatui::prelude::*;
/// 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);
@@ -526,12 +454,8 @@ 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);
@@ -559,8 +483,7 @@ impl<'a> Line<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::text::{Line, Span};
///
/// # use ratatui::prelude::*;
/// let mut line = Line::from("Hello, ");
/// line.push_span(Span::raw("world!"));
/// line.push_span(" How are you?");
@@ -609,12 +532,6 @@ 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 {
@@ -832,7 +749,6 @@ mod tests {
use rstest::{fixture, rstest};
use super::*;
use crate::style::{Color, Modifier, Stylize};
#[fixture]
fn small_buf() -> Buffer {
@@ -1606,49 +1522,4 @@ 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);
}
}

View File

@@ -1,6 +1,6 @@
use std::{borrow::Cow, fmt};
use crate::text::Text;
use super::Text;
/// A wrapper around a string that is masked when displayed.
///
@@ -10,12 +10,7 @@ use crate::text::Text;
/// # Examples
///
/// ```rust
/// use ratatui::{
/// buffer::Buffer,
/// layout::Rect,
/// text::Masked,
/// widgets::{Paragraph, Widget},
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 1));
/// let password = Masked::new("12345", 'x');

View File

@@ -3,13 +3,7 @@ use std::{borrow::Cow, fmt};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use crate::{
buffer::Buffer,
layout::Rect,
style::{Style, Styled},
text::{Line, StyledGrapheme},
widgets::{Widget, WidgetRef},
};
use crate::{prelude::*, style::Styled, text::StyledGrapheme};
/// Represents a part of a line that is contiguous and where all characters share the same style.
///
@@ -42,7 +36,7 @@ use crate::{
/// any type convertible to [`Cow<str>`].
///
/// ```rust
/// use ratatui::text::Span;
/// use ratatui::prelude::*;
///
/// let span = Span::raw("test content");
/// let span = Span::raw(String::from("test content"));
@@ -56,10 +50,7 @@ use crate::{
/// the [`Stylize`] trait.
///
/// ```rust
/// use ratatui::{
/// style::{Style, Stylize},
/// text::Span,
/// };
/// use ratatui::prelude::*;
///
/// let span = Span::styled("test content", Style::new().green());
/// let span = Span::styled(String::from("test content"), Style::new().green());
@@ -73,7 +64,7 @@ use crate::{
/// defined in the [`Stylize`] trait.
///
/// ```rust
/// use ratatui::{style::Stylize, text::Span};
/// use ratatui::prelude::*;
///
/// let span = Span::raw("test content").green().on_yellow().italic();
/// let span = Span::raw(String::from("test content"))
@@ -87,7 +78,7 @@ use crate::{
/// wrapping and alignment for you.
///
/// ```rust
/// use ratatui::{style::Stylize, Frame};
/// use ratatui::prelude::*;
///
/// # fn render_frame(frame: &mut Frame) {
/// frame.render_widget("test content".green().on_yellow().italic(), frame.area());
@@ -107,15 +98,13 @@ pub struct Span<'a> {
impl fmt::Debug for Span<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.content.is_empty() {
write!(f, "Span::default()")?;
} else {
write!(f, "Span::from({:?})", self.content)?;
if self.style == Style::default() {
return write!(f, "Span({:?})", self.content);
}
if self.style != Style::default() {
self.style.fmt_stylize(f)?;
}
Ok(())
f.debug_struct("Span")
.field("style", &self.style)
.field("content", &self.content)
.finish()
}
}
@@ -125,8 +114,7 @@ impl<'a> Span<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::text::Span;
///
/// # use ratatui::prelude::*;
/// Span::raw("test content");
/// Span::raw(String::from("test content"));
/// ```
@@ -151,17 +139,11 @@ impl<'a> Span<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{
/// style::{Style, Stylize},
/// text::Span,
/// };
///
/// # use ratatui::prelude::*;
/// 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>>,
@@ -183,8 +165,7 @@ impl<'a> Span<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::text::Span;
///
/// # use ratatui::prelude::*;
/// let mut span = Span::default().content("content");
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -209,15 +190,9 @@ impl<'a> Span<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{
/// style::{Style, Stylize},
/// text::Span,
/// };
///
/// # use ratatui::prelude::*;
/// 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();
@@ -234,17 +209,11 @@ impl<'a> Span<'a> {
/// # Example
///
/// ```rust
/// use ratatui::{
/// style::{Style, Stylize},
/// text::Span,
/// };
///
/// # use ratatui::prelude::*;
/// 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);
@@ -260,11 +229,7 @@ impl<'a> Span<'a> {
/// # Example
///
/// ```rust
/// use ratatui::{
/// style::{Style, Stylize},
/// text::Span,
/// };
///
/// # use ratatui::prelude::*;
/// let span = Span::styled(
/// "Test Content",
/// Style::new().dark_gray().on_yellow().italic(),
@@ -295,10 +260,7 @@ impl<'a> Span<'a> {
/// ```rust
/// use std::iter::Iterator;
///
/// use ratatui::{
/// style::{Style, Stylize},
/// text::{Span, StyledGrapheme},
/// };
/// use ratatui::{prelude::*, text::StyledGrapheme};
///
/// let span = Span::styled("Test", Style::new().green().italic());
/// let style = Style::new().red().on_yellow();
@@ -313,8 +275,6 @@ impl<'a> Span<'a> {
/// ],
/// );
/// ```
///
/// [`Color`]: crate::style::Color
pub fn styled_graphemes<S: Into<Style>>(
&'a self,
base_style: S,
@@ -332,8 +292,7 @@ impl<'a> Span<'a> {
/// # Example
///
/// ```rust
/// use ratatui::style::Stylize;
///
/// # use ratatui::prelude::*;
/// let line = "Test Content".green().italic().into_left_aligned_line();
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -352,8 +311,7 @@ impl<'a> Span<'a> {
/// # Example
///
/// ```rust
/// use ratatui::style::Stylize;
///
/// # use ratatui::prelude::*;
/// let line = "Test Content".green().italic().into_centered_line();
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -372,8 +330,7 @@ impl<'a> Span<'a> {
/// # Example
///
/// ```rust
/// use ratatui::style::Stylize;
///
/// # use ratatui::prelude::*;
/// let line = "Test Content".green().italic().into_right_aligned_line();
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -507,10 +464,10 @@ impl fmt::Display for Span<'_> {
#[cfg(test)]
mod tests {
use rstest::{fixture, rstest};
use buffer::Cell;
use rstest::fixture;
use super::*;
use crate::{buffer::Cell, layout::Alignment, style::Stylize};
#[fixture]
fn small_buf() -> Buffer {
@@ -886,16 +843,4 @@ 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);
}
}

View File

@@ -1,13 +1,7 @@
#![warn(missing_docs)]
use std::{borrow::Cow, fmt};
use crate::{
buffer::Buffer,
layout::{Alignment, Rect},
style::{Style, Styled},
text::{Line, Span},
widgets::{Widget, WidgetRef},
};
use crate::{prelude::*, style::Styled};
/// A string split over one or more lines.
///
@@ -68,10 +62,7 @@ use crate::{
/// ```rust
/// use std::{borrow::Cow, iter};
///
/// use ratatui::{
/// style::{Color, Modifier, Style, Stylize},
/// text::{Line, Span, Text},
/// };
/// use ratatui::prelude::*;
///
/// let style = Style::new().yellow().italic();
/// let text = Text::raw("The first line\nThe second line").style(style);
@@ -108,11 +99,7 @@ use crate::{
/// [`Stylize`] trait.
///
/// ```rust
/// use ratatui::{
/// style::{Color, Modifier, Style, Stylize},
/// text::{Line, Text},
/// };
///
/// # use ratatui::prelude::*;
/// 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()
@@ -129,11 +116,7 @@ use crate::{
/// Lines composing the text can also be individually aligned with [`Line::alignment`].
///
/// ```rust
/// use ratatui::{
/// layout::Alignment,
/// text::{Line, Text},
/// };
///
/// # use ratatui::prelude::*;
/// 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![
@@ -149,9 +132,7 @@ use crate::{
/// [`Frame`].
///
/// ```rust
/// # use ratatui::{buffer::Buffer, layout::Rect};
/// use ratatui::{text::Text, widgets::Widget, Frame};
///
/// # use ratatui::prelude::*;
/// // within another widget's `render` method:
/// # fn render(area: Rect, buf: &mut Buffer) {
/// let text = Text::from("The first line\nThe second line");
@@ -171,13 +152,7 @@ use crate::{
/// provides more functionality.
///
/// ```rust
/// use ratatui::{
/// buffer::Buffer,
/// layout::Rect,
/// text::Text,
/// widgets::{Paragraph, Widget, Wrap},
/// };
///
/// # use ratatui::{prelude::*, widgets::*};
/// # fn render(area: Rect, buf: &mut Buffer) {
/// let text = Text::from("The first line\nThe second line");
/// let paragraph = Paragraph::new(text)
@@ -188,8 +163,6 @@ use crate::{
/// ```
///
/// [`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.
@@ -202,23 +175,19 @@ pub struct Text<'a> {
impl fmt::Debug for Text<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.lines.is_empty() {
f.write_str("Text::default()")?;
} else if self.lines.len() == 1 {
write!(f, "Text::from({:?})", self.lines[0])?;
if self.style == Style::default() && self.alignment.is_none() {
f.write_str("Text ")?;
f.debug_list().entries(&self.lines).finish()
} else {
f.write_str("Text::from_iter(")?;
f.debug_list().entries(self.lines.iter()).finish()?;
f.write_str(")")?;
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()
}
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(())
}
}
@@ -228,8 +197,7 @@ impl<'a> Text<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::text::Text;
///
/// # use ratatui::prelude::*;
/// Text::raw("The first line\nThe second line");
/// Text::raw(String::from("The first line\nThe second line"));
/// ```
@@ -254,19 +222,13 @@ impl<'a> Text<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{
/// style::{Color, Modifier, Style},
/// text::Text,
/// };
///
/// # use ratatui::prelude::*;
/// 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>>,
@@ -280,8 +242,7 @@ impl<'a> Text<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::text::Text;
///
/// # use ratatui::prelude::*;
/// let text = Text::from("The first line\nThe second line");
/// assert_eq!(15, text.width());
/// ```
@@ -294,8 +255,7 @@ impl<'a> Text<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::text::Text;
///
/// # use ratatui::prelude::*;
/// let text = Text::from("The first line\nThe second line");
/// assert_eq!(2, text.height());
/// ```
@@ -316,15 +276,9 @@ impl<'a> Text<'a> {
///
/// # Examples
/// ```rust
/// use ratatui::{
/// style::{Style, Stylize},
/// text::Text,
/// };
///
/// # use ratatui::prelude::*;
/// 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();
@@ -348,11 +302,7 @@ impl<'a> Text<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{
/// style::{Color, Modifier},
/// text::Text,
/// };
///
/// # use ratatui::prelude::*;
/// 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"),
@@ -363,9 +313,6 @@ 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);
@@ -381,11 +328,7 @@ impl<'a> Text<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{
/// style::{Color, Modifier, Style},
/// text::Text,
/// };
///
/// # use ratatui::prelude::*;
/// let text = Text::styled(
/// "The first line\nThe second line",
/// (Color::Yellow, Modifier::ITALIC),
@@ -412,8 +355,7 @@ impl<'a> Text<'a> {
/// Set alignment to the whole text.
///
/// ```rust
/// use ratatui::{layout::Alignment, text::Text};
///
/// # use ratatui::prelude::*;
/// let mut text = Text::from("Hi, what's up?");
/// assert_eq!(None, text.alignment);
/// assert_eq!(
@@ -425,11 +367,7 @@ impl<'a> Text<'a> {
/// Set a default alignment and override it on a per line basis.
///
/// ```rust
/// use ratatui::{
/// layout::Alignment,
/// text::{Line, Text},
/// };
///
/// # use ratatui::prelude::*;
/// let text = Text::from(vec![
/// Line::from("left").alignment(Alignment::Left),
/// Line::from("default"),
@@ -466,8 +404,7 @@ impl<'a> Text<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::text::Text;
///
/// # use ratatui::prelude::*;
/// let text = Text::from("Hi, what's up?").left_aligned();
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -486,8 +423,7 @@ impl<'a> Text<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::text::Text;
///
/// # use ratatui::prelude::*;
/// let text = Text::from("Hi, what's up?").centered();
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -506,8 +442,7 @@ impl<'a> Text<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::text::Text;
///
/// # use ratatui::prelude::*;
/// let text = Text::from("Hi, what's up?").right_aligned();
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -533,8 +468,7 @@ impl<'a> Text<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::text::{Line, Span, Text};
///
/// # use ratatui::prelude::*;
/// let mut text = Text::from("Hello, world!");
/// text.push_line(Line::from("How are you?"));
/// text.push_line(Span::from("How are you?"));
@@ -552,8 +486,7 @@ impl<'a> Text<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::text::{Span, Text};
///
/// # use ratatui::prelude::*;
/// let mut text = Text::from("Hello, world!");
/// text.push_span(Span::from("How are you?"));
/// text.push_span("How are you?");
@@ -775,7 +708,6 @@ mod tests {
use rstest::{fixture, rstest};
use super::*;
use crate::style::{Color, Modifier, Stylize};
#[fixture]
fn small_buf() -> Buffer {
@@ -1319,68 +1251,45 @@ mod tests {
}
}
#[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);
}
mod debug {
use super::*;
#[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()"#}
);
#[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());
}
}
}

View File

@@ -21,39 +21,7 @@
//! - [`Tabs`]: displays a tab bar and allows selection.
//!
//! [`Canvas`]: crate::widgets::canvas::Canvas
mod barchart;
pub mod block;
mod borders;
#[cfg(feature = "widget-calendar")]
pub mod calendar;
pub mod canvas;
mod chart;
mod clear;
mod gauge;
mod list;
mod logo;
mod paragraph;
mod reflow;
mod scrollbar;
mod sparkline;
mod table;
mod tabs;
pub use self::{
barchart::{Bar, BarChart, BarGroup},
block::{Block, BorderType, Padding},
borders::*,
chart::{Axis, Chart, Dataset, GraphType, LegendPosition},
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},
table::{Cell, HighlightSpacing, Row, Table, TableState},
tabs::Tabs,
};
use crate::{buffer::Buffer, layout::Rect, style::Style};
/// A `Widget` is a type that can be drawn on a [`Buffer`] in a given [`Rect`].
@@ -85,11 +53,7 @@ use crate::{buffer::Buffer, layout::Rect, style::Style};
/// # Examples
///
/// ```rust,no_run
/// use ratatui::{
/// backend::TestBackend,
/// widgets::{Clear, Widget},
/// Terminal,
/// };
/// use ratatui::{backend::TestBackend, prelude::*, widgets::*};
/// # let backend = TestBackend::new(5, 5);
/// # let mut terminal = Terminal::new(backend).unwrap();
///
@@ -101,7 +65,7 @@ use crate::{buffer::Buffer, layout::Rect, style::Style};
/// It's common to render widgets inside other widgets:
///
/// ```rust
/// use ratatui::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
/// use ratatui::{prelude::*, widgets::*};
///
/// struct MyWidget;
///
@@ -139,11 +103,7 @@ pub trait Widget {
/// ```rust,no_run
/// use std::io;
///
/// use ratatui::{
/// backend::TestBackend,
/// widgets::{List, ListItem, ListState, StatefulWidget, Widget},
/// Terminal,
/// };
/// use ratatui::{backend::TestBackend, prelude::*, widgets::*};
///
/// // Let's say we have some events to display.
/// struct Events {
@@ -267,12 +227,7 @@ pub trait StatefulWidget {
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui::{
/// buffer::Buffer,
/// layout::Rect,
/// text::Line,
/// widgets::{Widget, WidgetRef},
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// struct Greeting;
///
@@ -347,12 +302,7 @@ impl<W: WidgetRef> Widget for &W {
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui::{
/// buffer::Buffer,
/// layout::Rect,
/// text::Line,
/// widgets::{Widget, WidgetRef},
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// struct Parent {
/// child: Option<Child>,
@@ -401,13 +351,7 @@ impl<W: WidgetRef> WidgetRef for Option<W> {
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui::{
/// buffer::Buffer,
/// layout::Rect,
/// style::Stylize,
/// text::Line,
/// widgets::{StatefulWidget, StatefulWidgetRef, Widget},
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// struct PersonalGreeting;
///

View File

@@ -0,0 +1,20 @@
[package]
name = "ratatui-crossterm"
version = "0.1.0"
edition = "2021"
[features]
default = ["underline-color"]
## enables the backend code that sets the underline color.
## Underline color is only supported by the [`CrosstermBackend`](backend::CrosstermBackend) backend,
## and is not supported on Windows 7.
underline-color = []
[dependencies]
ratatui-core = { workspace = true }
crossterm.workspace = true
instability.workspace = true
[dev-dependencies]
rstest.workspace = true

View File

@@ -0,0 +1,681 @@
//! This module provides the [`CrosstermBackend`] implementation for the [`Backend`] trait. It uses
//! the [Crossterm] crate to interact with the terminal.
//!
//! [Crossterm]: https://crates.io/crates/crossterm
use std::io::{self, Write};
#[cfg(feature = "underline-color")]
use crossterm::style::SetUnderlineColor;
use crossterm::{
cursor::{Hide, MoveTo, Show},
execute, queue,
style::{
Attribute as CrosstermAttribute, Attributes as CrosstermAttributes,
Color as CrosstermColor, Colors, ContentStyle, Print, SetAttribute, SetBackgroundColor,
SetColors, SetForegroundColor,
},
terminal::{self, Clear},
};
use ratatui_core::{
backend::{Backend, ClearType, WindowSize},
buffer::Cell,
layout::{Position, Size},
style::{Color, Modifier, Style},
};
/// A [`Backend`] implementation that uses [Crossterm] to render to the terminal.
///
/// The `CrosstermBackend` struct is a wrapper around a writer implementing [`Write`], which is
/// used to send commands to the terminal. It provides methods for drawing content, manipulating
/// the cursor, and clearing the terminal screen.
///
/// Most applications should not call the methods on `CrosstermBackend` directly, but will instead
/// use the [`Terminal`] struct, which provides a more ergonomic interface.
///
/// Usually applications will enable raw mode and switch to alternate screen mode after creating
/// a `CrosstermBackend`. This is done by calling [`crossterm::terminal::enable_raw_mode`] and
/// [`crossterm::terminal::EnterAlternateScreen`] (and the corresponding disable/leave functions
/// when the application exits). This is not done automatically by the backend because it is
/// possible that the application may want to use the terminal for other purposes (like showing
/// help text) before entering alternate screen mode.
///
/// # Example
///
/// ```rust,no_run
/// use std::io::{stderr, stdout};
///
/// use ratatui::{
/// crossterm::{
/// terminal::{
/// disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
/// },
/// ExecutableCommand,
/// },
/// prelude::*,
/// };
///
/// let mut backend = CrosstermBackend::new(stdout());
/// // or
/// let backend = CrosstermBackend::new(stderr());
/// let mut terminal = Terminal::new(backend)?;
///
/// enable_raw_mode()?;
/// stdout().execute(EnterAlternateScreen)?;
///
/// terminal.clear()?;
/// terminal.draw(|frame| {
/// // -- snip --
/// })?;
///
/// stdout().execute(LeaveAlternateScreen)?;
/// disable_raw_mode()?;
///
/// # std::io::Result::Ok(())
/// ```
///
/// See the the [Examples] directory for more examples. See the [`backend`] module documentation
/// for more details on raw mode and alternate screen.
///
/// [`Write`]: std::io::Write
/// [`Terminal`]: crate::terminal::Terminal
/// [`backend`]: crate::backend
/// [Crossterm]: https://crates.io/crates/crossterm
/// [Examples]: https://github.com/ratatui/ratatui/tree/main/examples/README.md
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct CrosstermBackend<W: Write> {
/// The writer used to send commands to the terminal.
writer: W,
}
impl<W> CrosstermBackend<W>
where
W: Write,
{
/// Creates a new `CrosstermBackend` with the given writer.
///
/// Most applications will use either [`stdout`](std::io::stdout) or
/// [`stderr`](std::io::stderr) as writer. See the [FAQ] to determine which one to use.
///
/// [FAQ]: https://ratatui.rs/faq/#should-i-use-stdout-or-stderr
///
/// # Example
///
/// ```rust,no_run
/// # use std::io::stdout;
/// # use ratatui::prelude::*;
/// let backend = CrosstermBackend::new(stdout());
/// ```
pub const fn new(writer: W) -> Self {
Self { writer }
}
/// Gets the writer.
#[instability::unstable(
feature = "backend-writer",
issue = "https://github.com/ratatui/ratatui/pull/991"
)]
pub const fn writer(&self) -> &W {
&self.writer
}
/// Gets the writer as a mutable reference.
///
/// Note: writing to the writer may cause incorrect output after the write. This is due to the
/// way that the Terminal implements diffing Buffers.
#[instability::unstable(
feature = "backend-writer",
issue = "https://github.com/ratatui/ratatui/pull/991"
)]
pub fn writer_mut(&mut self) -> &mut W {
&mut self.writer
}
}
impl<W> Write for CrosstermBackend<W>
where
W: Write,
{
/// Writes a buffer of bytes to the underlying buffer.
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.writer.write(buf)
}
/// Flushes the underlying buffer.
fn flush(&mut self) -> io::Result<()> {
self.writer.flush()
}
}
impl<W> Backend for CrosstermBackend<W>
where
W: Write,
{
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
where
I: Iterator<Item = (u16, u16, &'a Cell)>,
{
let mut fg = Color::Reset;
let mut bg = Color::Reset;
#[cfg(feature = "underline-color")]
let mut underline_color = Color::Reset;
let mut modifier = Modifier::empty();
let mut last_pos: Option<Position> = None;
for (x, y, cell) in content {
// Move the cursor if the previous location was not (x - 1, y)
if !matches!(last_pos, Some(p) if x == p.x + 1 && y == p.y) {
queue!(self.writer, MoveTo(x, y))?;
}
last_pos = Some(Position { x, y });
if cell.modifier != modifier {
let diff = ModifierDiff {
from: modifier,
to: cell.modifier,
};
diff.queue(&mut self.writer)?;
modifier = cell.modifier;
}
if cell.fg != fg || cell.bg != bg {
queue!(
self.writer,
SetColors(Colors::new(
from_ratatui_color(cell.fg),
from_ratatui_color(cell.bg)
))
)?;
fg = cell.fg;
bg = cell.bg;
}
#[cfg(feature = "underline-color")]
if cell.underline_color != underline_color {
let color = from_ratatui_color(cell.underline_color);
queue!(self.writer, SetUnderlineColor(color))?;
underline_color = cell.underline_color;
}
queue!(self.writer, Print(cell.symbol()))?;
}
#[cfg(feature = "underline-color")]
return queue!(
self.writer,
SetForegroundColor(CrosstermColor::Reset),
SetBackgroundColor(CrosstermColor::Reset),
SetUnderlineColor(CrosstermColor::Reset),
SetAttribute(CrosstermAttribute::Reset),
);
#[cfg(not(feature = "underline-color"))]
return queue!(
self.writer,
SetForegroundColor(CrosstermColor::Reset),
SetBackgroundColor(CrosstermColor::Reset),
SetAttribute(CrosstermAttribute::Reset),
);
}
fn hide_cursor(&mut self) -> io::Result<()> {
execute!(self.writer, Hide)
}
fn show_cursor(&mut self) -> io::Result<()> {
execute!(self.writer, Show)
}
fn get_cursor_position(&mut self) -> io::Result<Position> {
crossterm::cursor::position()
.map(|(x, y)| Position { x, y })
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
}
fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> {
let Position { x, y } = position.into();
execute!(self.writer, MoveTo(x, y))
}
fn clear(&mut self) -> io::Result<()> {
self.clear_region(ClearType::All)
}
fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
execute!(
self.writer,
Clear(match clear_type {
ClearType::All => crossterm::terminal::ClearType::All,
ClearType::AfterCursor => crossterm::terminal::ClearType::FromCursorDown,
ClearType::BeforeCursor => crossterm::terminal::ClearType::FromCursorUp,
ClearType::CurrentLine => crossterm::terminal::ClearType::CurrentLine,
ClearType::UntilNewLine => crossterm::terminal::ClearType::UntilNewLine,
})
)
}
fn append_lines(&mut self, n: u16) -> io::Result<()> {
for _ in 0..n {
queue!(self.writer, Print("\n"))?;
}
self.writer.flush()
}
fn size(&self) -> io::Result<Size> {
let (width, height) = terminal::size()?;
Ok(Size { width, height })
}
fn window_size(&mut self) -> io::Result<WindowSize> {
let crossterm::terminal::WindowSize {
columns,
rows,
width,
height,
} = terminal::window_size()?;
Ok(WindowSize {
columns_rows: Size {
width: columns,
height: rows,
},
pixels: Size { width, height },
})
}
fn flush(&mut self) -> io::Result<()> {
self.writer.flush()
}
}
fn from_ratatui_color(color: Color) -> CrosstermColor {
match color {
Color::Reset => CrosstermColor::Reset,
Color::Black => CrosstermColor::Black,
Color::Red => CrosstermColor::DarkRed,
Color::Green => CrosstermColor::DarkGreen,
Color::Yellow => CrosstermColor::DarkYellow,
Color::Blue => CrosstermColor::DarkBlue,
Color::Magenta => CrosstermColor::DarkMagenta,
Color::Cyan => CrosstermColor::DarkCyan,
Color::Gray => CrosstermColor::Grey,
Color::DarkGray => CrosstermColor::DarkGrey,
Color::LightRed => CrosstermColor::Red,
Color::LightGreen => CrosstermColor::Green,
Color::LightBlue => CrosstermColor::Blue,
Color::LightYellow => CrosstermColor::Yellow,
Color::LightMagenta => CrosstermColor::Magenta,
Color::LightCyan => CrosstermColor::Cyan,
Color::White => CrosstermColor::White,
Color::Indexed(i) => CrosstermColor::AnsiValue(i),
Color::Rgb(r, g, b) => CrosstermColor::Rgb { r, g, b },
}
}
fn from_crossterm_color(value: CrosstermColor) -> Color {
match value {
CrosstermColor::Reset => Color::Reset,
CrosstermColor::Black => Color::Black,
CrosstermColor::DarkRed => Color::Red,
CrosstermColor::DarkGreen => Color::Green,
CrosstermColor::DarkYellow => Color::Yellow,
CrosstermColor::DarkBlue => Color::Blue,
CrosstermColor::DarkMagenta => Color::Magenta,
CrosstermColor::DarkCyan => Color::Cyan,
CrosstermColor::Grey => Color::Gray,
CrosstermColor::DarkGrey => Color::DarkGray,
CrosstermColor::Red => Color::LightRed,
CrosstermColor::Green => Color::LightGreen,
CrosstermColor::Blue => Color::LightBlue,
CrosstermColor::Yellow => Color::LightYellow,
CrosstermColor::Magenta => Color::LightMagenta,
CrosstermColor::Cyan => Color::LightCyan,
CrosstermColor::White => Color::White,
CrosstermColor::Rgb { r, g, b } => Color::Rgb(r, g, b),
CrosstermColor::AnsiValue(v) => Color::Indexed(v),
}
}
/// The `ModifierDiff` struct is used to calculate the difference between two `Modifier`
/// values. This is useful when updating the terminal display, as it allows for more
/// efficient updates by only sending the necessary changes.
struct ModifierDiff {
pub from: Modifier,
pub to: Modifier,
}
impl ModifierDiff {
fn queue<W>(self, mut w: W) -> io::Result<()>
where
W: io::Write,
{
//use crossterm::Attribute;
let removed = self.from - self.to;
if removed.contains(Modifier::REVERSED) {
queue!(w, SetAttribute(CrosstermAttribute::NoReverse))?;
}
if removed.contains(Modifier::BOLD) {
queue!(w, SetAttribute(CrosstermAttribute::NormalIntensity))?;
if self.to.contains(Modifier::DIM) {
queue!(w, SetAttribute(CrosstermAttribute::Dim))?;
}
}
if removed.contains(Modifier::ITALIC) {
queue!(w, SetAttribute(CrosstermAttribute::NoItalic))?;
}
if removed.contains(Modifier::UNDERLINED) {
queue!(w, SetAttribute(CrosstermAttribute::NoUnderline))?;
}
if removed.contains(Modifier::DIM) {
queue!(w, SetAttribute(CrosstermAttribute::NormalIntensity))?;
}
if removed.contains(Modifier::CROSSED_OUT) {
queue!(w, SetAttribute(CrosstermAttribute::NotCrossedOut))?;
}
if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
queue!(w, SetAttribute(CrosstermAttribute::NoBlink))?;
}
let added = self.to - self.from;
if added.contains(Modifier::REVERSED) {
queue!(w, SetAttribute(CrosstermAttribute::Reverse))?;
}
if added.contains(Modifier::BOLD) {
queue!(w, SetAttribute(CrosstermAttribute::Bold))?;
}
if added.contains(Modifier::ITALIC) {
queue!(w, SetAttribute(CrosstermAttribute::Italic))?;
}
if added.contains(Modifier::UNDERLINED) {
queue!(w, SetAttribute(CrosstermAttribute::Underlined))?;
}
if added.contains(Modifier::DIM) {
queue!(w, SetAttribute(CrosstermAttribute::Dim))?;
}
if added.contains(Modifier::CROSSED_OUT) {
queue!(w, SetAttribute(CrosstermAttribute::CrossedOut))?;
}
if added.contains(Modifier::SLOW_BLINK) {
queue!(w, SetAttribute(CrosstermAttribute::SlowBlink))?;
}
if added.contains(Modifier::RAPID_BLINK) {
queue!(w, SetAttribute(CrosstermAttribute::RapidBlink))?;
}
Ok(())
}
}
fn from_crossterm_attribute(value: CrosstermAttribute) -> Modifier {
// `Attribute*s*` (note the *s*) contains multiple `Attribute`
// We convert `Attribute` to `Attribute*s*` (containing only 1 value) to avoid implementing
// the conversion again
from_crossterm_attributes(CrosstermAttributes::from(value))
}
fn from_crossterm_attributes(value: CrosstermAttributes) -> Modifier {
let mut res = Modifier::empty();
if value.has(CrosstermAttribute::Bold) {
res |= Modifier::BOLD;
}
if value.has(CrosstermAttribute::Dim) {
res |= Modifier::DIM;
}
if value.has(CrosstermAttribute::Italic) {
res |= Modifier::ITALIC;
}
if value.has(CrosstermAttribute::Underlined)
|| value.has(CrosstermAttribute::DoubleUnderlined)
|| value.has(CrosstermAttribute::Undercurled)
|| value.has(CrosstermAttribute::Underdotted)
|| value.has(CrosstermAttribute::Underdashed)
{
res |= Modifier::UNDERLINED;
}
if value.has(CrosstermAttribute::SlowBlink) {
res |= Modifier::SLOW_BLINK;
}
if value.has(CrosstermAttribute::RapidBlink) {
res |= Modifier::RAPID_BLINK;
}
if value.has(CrosstermAttribute::Reverse) {
res |= Modifier::REVERSED;
}
if value.has(CrosstermAttribute::Hidden) {
res |= Modifier::HIDDEN;
}
if value.has(CrosstermAttribute::CrossedOut) {
res |= Modifier::CROSSED_OUT;
}
res
}
fn from_crossterm_style(value: ContentStyle) -> Style {
let mut sub_modifier = Modifier::empty();
if value.attributes.has(CrosstermAttribute::NoBold) {
sub_modifier |= Modifier::BOLD;
}
if value.attributes.has(CrosstermAttribute::NoItalic) {
sub_modifier |= Modifier::ITALIC;
}
if value.attributes.has(CrosstermAttribute::NotCrossedOut) {
sub_modifier |= Modifier::CROSSED_OUT;
}
if value.attributes.has(CrosstermAttribute::NoUnderline) {
sub_modifier |= Modifier::UNDERLINED;
}
if value.attributes.has(CrosstermAttribute::NoHidden) {
sub_modifier |= Modifier::HIDDEN;
}
if value.attributes.has(CrosstermAttribute::NoBlink) {
sub_modifier |= Modifier::RAPID_BLINK | Modifier::SLOW_BLINK;
}
if value.attributes.has(CrosstermAttribute::NoReverse) {
sub_modifier |= Modifier::REVERSED;
}
Style {
fg: value.foreground_color.map(from_crossterm_color),
bg: value.background_color.map(from_crossterm_color),
#[cfg(feature = "underline-color")]
underline_color: value.underline_color.map(from_crossterm_color),
add_modifier: from_crossterm_attributes(value.attributes),
sub_modifier,
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case(CrosstermColor::Reset, Color::Reset)]
#[case(CrosstermColor::Black, Color::Black)]
#[case(CrosstermColor::DarkGrey, Color::DarkGray)]
#[case(CrosstermColor::Red, Color::LightRed)]
#[case(CrosstermColor::DarkRed, Color::Red)]
#[case(CrosstermColor::Green, Color::LightGreen)]
#[case(CrosstermColor::DarkGreen, Color::Green)]
#[case(CrosstermColor::Yellow, Color::LightYellow)]
#[case(CrosstermColor::DarkYellow, Color::Yellow)]
#[case(CrosstermColor::Blue, Color::LightBlue)]
#[case(CrosstermColor::DarkBlue, Color::Blue)]
#[case(CrosstermColor::Magenta, Color::LightMagenta)]
#[case(CrosstermColor::DarkMagenta, Color::Magenta)]
#[case(CrosstermColor::Cyan, Color::LightCyan)]
#[case(CrosstermColor::DarkCyan, Color::Cyan)]
#[case(CrosstermColor::White, Color::White)]
#[case(CrosstermColor::Grey, Color::Gray)]
#[case(CrosstermColor::Rgb { r: 0, g: 0, b: 0 }, Color::Rgb(0, 0, 0))]
#[case(CrosstermColor::Rgb { r: 10, g: 20, b: 30 }, Color::Rgb(10, 20, 30))]
#[case(CrosstermColor::AnsiValue(32), Color::Indexed(32))]
#[case(CrosstermColor::AnsiValue(37), Color::Indexed(37))]
fn convert_from_crossterm_color(#[case] value: CrosstermColor, #[case] expected: Color) {
assert_eq!(from_crossterm_color(value), expected);
}
mod modifier {
use super::*;
#[rstest]
#[rstest]
#[case(CrosstermAttribute::Reset, Modifier::empty())]
#[case(CrosstermAttribute::Bold, Modifier::BOLD)]
#[case(CrosstermAttribute::Italic, Modifier::ITALIC)]
#[case(CrosstermAttribute::Underlined, Modifier::UNDERLINED)]
#[case(CrosstermAttribute::DoubleUnderlined, Modifier::UNDERLINED)]
#[case(CrosstermAttribute::Underdotted, Modifier::UNDERLINED)]
#[case(CrosstermAttribute::Dim, Modifier::DIM)]
#[case(CrosstermAttribute::NormalIntensity, Modifier::empty())]
#[case(CrosstermAttribute::CrossedOut, Modifier::CROSSED_OUT)]
#[case(CrosstermAttribute::NoUnderline, Modifier::empty())]
#[case(CrosstermAttribute::OverLined, Modifier::empty())]
#[case(CrosstermAttribute::SlowBlink, Modifier::SLOW_BLINK)]
#[case(CrosstermAttribute::RapidBlink, Modifier::RAPID_BLINK)]
#[case(CrosstermAttribute::Hidden, Modifier::HIDDEN)]
#[case(CrosstermAttribute::NoHidden, Modifier::empty())]
#[case(CrosstermAttribute::Reverse, Modifier::REVERSED)]
fn convert_from_crossterm_attribute(
#[case] value: CrosstermAttribute,
#[case] expected: Modifier,
) {
assert_eq!(from_crossterm_attribute(value), expected);
}
#[rstest]
#[case(&[CrosstermAttribute::Bold], Modifier::BOLD)]
#[case(
&[CrosstermAttribute::Bold, CrosstermAttribute::Italic],
Modifier::BOLD | Modifier::ITALIC
)]
#[case(
&[CrosstermAttribute::Bold, CrosstermAttribute::NotCrossedOut],
Modifier::BOLD
)]
#[case(
&[CrosstermAttribute::Dim, CrosstermAttribute::Underdotted],
Modifier::DIM | Modifier::UNDERLINED
)]
#[case(
&[CrosstermAttribute::Dim, CrosstermAttribute::SlowBlink, CrosstermAttribute::Italic],
Modifier::DIM | Modifier::SLOW_BLINK | Modifier::ITALIC
)]
#[case(
&[CrosstermAttribute::Hidden, CrosstermAttribute::NoUnderline, CrosstermAttribute::NotCrossedOut],
Modifier::HIDDEN
)]
#[case(
&[CrosstermAttribute::Reverse],
Modifier::REVERSED
)]
#[case(
&[CrosstermAttribute::Reset],
Modifier::empty()
)]
#[case(
&[CrosstermAttribute::RapidBlink, CrosstermAttribute::CrossedOut],
Modifier::RAPID_BLINK | Modifier::CROSSED_OUT
)]
#[case(
&[CrosstermAttribute::DoubleUnderlined, CrosstermAttribute::OverLined],
Modifier::UNDERLINED
)]
#[case(
&[CrosstermAttribute::Undercurled, CrosstermAttribute::Underdashed],
Modifier::UNDERLINED
)]
#[case(
&[CrosstermAttribute::NoBold, CrosstermAttribute::NoItalic],
Modifier::empty()
)]
#[case(
&[CrosstermAttribute::NoBlink, CrosstermAttribute::NoReverse],
Modifier::empty()
)]
fn convert_from_crossterm_attributes(
#[case] value: &[CrosstermAttribute],
#[case] expected: Modifier,
) {
assert_eq!(
from_crossterm_attributes(CrosstermAttributes::from(value)),
expected
);
}
}
#[rstest]
#[case(ContentStyle::default(), Style::default())]
#[case(
ContentStyle {
foreground_color: Some(CrosstermColor::DarkYellow),
..Default::default()
},
Style::default().fg(Color::Yellow)
)]
#[case(
ContentStyle {
background_color: Some(CrosstermColor::DarkYellow),
..Default::default()
},
Style::default().bg(Color::Yellow)
)]
#[case(
ContentStyle {
attributes: CrosstermAttributes::from(CrosstermAttribute::Bold),
..Default::default()
},
Style::default().add_modifier(Modifier::BOLD)
)]
#[case(
ContentStyle {
attributes: CrosstermAttributes::from(CrosstermAttribute::NoBold),
..Default::default()
},
Style::default().remove_modifier(Modifier::BOLD)
)]
#[case(
ContentStyle {
attributes: CrosstermAttributes::from(CrosstermAttribute::Italic),
..Default::default()
},
Style::default().add_modifier(Modifier::ITALIC)
)]
#[case(
ContentStyle {
attributes: CrosstermAttributes::from(CrosstermAttribute::NoItalic),
..Default::default()
},
Style::default().remove_modifier(Modifier::ITALIC)
)]
#[case(
ContentStyle {
attributes: CrosstermAttributes::from(
[CrosstermAttribute::Bold, CrosstermAttribute::Italic].as_ref()
),
..Default::default()
},
Style::default()
.add_modifier(Modifier::BOLD)
.add_modifier(Modifier::ITALIC)
)]
#[case(
ContentStyle {
attributes: CrosstermAttributes::from(
[CrosstermAttribute::NoBold, CrosstermAttribute::NoItalic].as_ref()
),
..Default::default()
},
Style::default()
.remove_modifier(Modifier::BOLD)
.remove_modifier(Modifier::ITALIC)
)]
#[cfg(feature = "underline-color")]
#[case(
ContentStyle {
underline_color: Some(CrosstermColor::DarkRed),
..Default::default()
},
Style::default().underline_color(Color::Red)
)]
fn convert_from_crossterm_content_style(#[case] value: ContentStyle, #[case] expected: Style) {
assert_eq!(from_crossterm_style(value), expected);
}
}

View File

@@ -0,0 +1,9 @@
[package]
name = "ratatui-termion"
version = "0.1.0"
edition = "2021"
[dependencies]
instability = { workspace = true }
ratatui-core = { workspace = true }
termion.workspace = true

View File

@@ -9,13 +9,13 @@ use std::{
io::{self, Write},
};
use crate::{
use ratatui_core::{
backend::{Backend, ClearType, WindowSize},
buffer::Cell,
layout::{Position, Size},
style::{Color, Modifier, Style},
termion::{self, color as tcolor, color::Color as _, style as tstyle},
};
use termion::{color as tcolor, color::Color as _, style as tstyle};
/// A [`Backend`] implementation that uses [Termion] to render to the terminal.
///
@@ -40,9 +40,8 @@ use crate::{
/// use std::io::{stderr, stdout};
///
/// use ratatui::{
/// backend::TermionBackend,
/// prelude::*,
/// termion::{raw::IntoRawMode, screen::IntoAlternateScreen},
/// Terminal,
/// };
///
/// let writer = stdout().into_raw_mode()?.into_alternate_screen()?;
@@ -85,10 +84,8 @@ where
/// # Example
///
/// ```rust,no_run
/// use std::io::stdout;
///
/// use ratatui::backend::TermionBackend;
///
/// # use std::io::stdout;
/// # use ratatui::prelude::*;
/// let backend = TermionBackend::new(stdout());
/// ```
pub const fn new(writer: W) -> Self {
@@ -239,30 +236,6 @@ 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);
@@ -492,26 +465,6 @@ 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::*;

View File

@@ -0,0 +1,7 @@
[package]
name = "ratatui-termwiz"
version = "0.1.0"
edition = "2021"
[dependencies]
ratatui-core = { workspace = true }

View File

@@ -38,7 +38,7 @@ use crate::{
/// # Example
///
/// ```rust,no_run
/// use ratatui::{backend::TermwizBackend, Terminal};
/// use ratatui::prelude::*;
///
/// let backend = TermwizBackend::new()?;
/// let mut terminal = Terminal::new(backend)?;
@@ -78,8 +78,7 @@ impl TermwizBackend {
/// # Example
///
/// ```rust,no_run
/// use ratatui::backend::TermwizBackend;
///
/// # use ratatui::prelude::*;
/// let backend = TermwizBackend::new()?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
@@ -250,52 +249,6 @@ 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 {

View File

@@ -0,0 +1,40 @@
[package]
name = "ratatui-widgets"
version = "0.1.0"
edition = "2021"
[features]
# TODO: remove unstable-widget-ref, consider whether to keep all-widgets
default = ["all-widgets", "unstable-widget-ref"]
## enables serialization and deserialization of style and color types using the [`serde`] crate.
## This is useful if you want to save themes to a file.
serde = ["dep:serde", "bitflags/serde"]
## enables all widgets.
all-widgets = ["widget-calendar"]
#! Widgets that add dependencies are gated behind feature flags to prevent unused transitive
#! dependencies. The available features are:
## enables the [`calendar`](widgets::calendar) widget module and adds a dependency on [`time`].
widget-calendar = ["dep:time"]
unstable-widget-ref = ["ratatui-core/unstable-widget-ref"]
[dependencies]
bitflags.workspace = true
instability.workspace = true
itertools.workspace = true
ratatui-core.workspace = true
serde = { workspace = true, optional = true }
strum.workspace = true
time = { workspace = true, optional = true }
unicode-segmentation.workspace = true
unicode-width.workspace = true
[dev-dependencies]
rstest.workspace = true
indoc.workspace = true
pretty_assertions.workspace = true
time.workspace = true

View File

@@ -1,10 +1,7 @@
use crate::{
buffer::Buffer,
layout::{Direction, Rect},
style::{Style, Styled},
symbols::{self},
text::Line,
widgets::{block::BlockExt, Block, Widget, WidgetRef},
use ratatui_core::{
prelude::{symbols, Buffer, Direction, Line, Rect, Style, Stylize, Widget},
style::Styled,
widgets::WidgetRef,
};
mod bar;
@@ -13,6 +10,8 @@ mod bar_group;
pub use bar::Bar;
pub use bar_group::BarGroup;
use crate::{block::BlockExt, Block};
/// A chart showing values as [bars](Bar).
///
/// Here is a possible `BarChart` output.
@@ -49,10 +48,7 @@ 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::{
/// style::{Style, Stylize},
/// widgets::{Bar, BarChart, BarGroup, Block},
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// BarChart::default()
/// .block(Block::bordered().title("BarChart"))
@@ -123,8 +119,7 @@ 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::widgets::{Bar, BarChart, BarGroup};
///
/// # use ratatui::{prelude::*, widgets::*};
/// BarChart::default()
/// .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
/// .data(BarGroup::default().bars(&[Bar::default().value(10), Bar::default().value(20)]));
@@ -154,7 +149,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::widgets::BarChart;
/// # use ratatui::{prelude::*, widgets::*};
/// BarChart::default().data(&[("foo", 1), ("bar", 2), ("baz", 100)]);
/// // Renders
/// // █
@@ -165,8 +160,7 @@ impl<'a> BarChart<'a> {
/// This example shows a custom max value.
/// The maximum height being `2`, `bar` & `baz` render as the max.
/// ```
/// use ratatui::widgets::BarChart;
///
/// # use ratatui::{prelude::*, widgets::*};
/// BarChart::default()
/// .data(&[("foo", 1), ("bar", 2), ("baz", 100)])
/// .max(2);
@@ -188,8 +182,6 @@ 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();
@@ -218,8 +210,7 @@ impl<'a> BarChart<'a> {
///
/// This shows two bars with a gap of `3`. Notice the labels will always stay under the bar.
/// ```
/// use ratatui::widgets::BarChart;
///
/// # use ratatui::{prelude::*, widgets::*};
/// BarChart::default()
/// .data(&[("foo", 1), ("bar", 2)])
/// .bar_gap(3);
@@ -254,8 +245,6 @@ 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();
@@ -273,8 +262,6 @@ 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();
@@ -294,8 +281,6 @@ 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();
@@ -634,14 +619,14 @@ impl<'a> Styled for BarChart<'a> {
#[cfg(test)]
mod tests {
use itertools::iproduct;
use ratatui_core::{
layout::Alignment,
style::{Color, Modifier},
text::Span,
};
use super::*;
use crate::{
layout::Alignment,
style::{Color, Modifier, Stylize},
text::Span,
widgets::BorderType,
};
use crate::BorderType;
#[test]
fn default() {

View File

@@ -1,7 +1,7 @@
use ratatui_core::prelude::{Buffer, Line, Rect, Style, Widget};
use unicode_width::UnicodeWidthStr;
use crate::{buffer::Buffer, layout::Rect, style::Style, text::Line, widgets::Widget};
/// A bar to be shown by the [`BarChart`](crate::widgets::BarChart) widget.
/// A bar to be shown by the [`BarChart`](crate::BarChart) widget.
///
/// Here is an explanation of a `Bar`'s components.
/// ```plain
@@ -16,16 +16,13 @@ use crate::{buffer::Buffer, layout::Rect, style::Style, text::Line, widgets::Wid
/// The following example creates a bar with the label "Bar 1", a value "10",
/// red background and a white value foreground.
/// ```
/// use ratatui::{
/// style::{Style, Stylize},
/// widgets::Bar,
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// Bar::default()
/// .label("Bar 1".into())
/// .value(10)
/// .style(Style::new().red())
/// .value_style(Style::new().red().on_white())
/// .style(Style::default().fg(Color::Red))
/// .value_style(Style::default().bg(Color::Red).fg(Color::White))
/// .text_value("10°C".to_string());
/// ```
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
@@ -63,7 +60,7 @@ impl<'a> Bar<'a> {
/// display the label **under** the bar.
/// For [`Horizontal`](crate::layout::Direction::Horizontal) bars,
/// display the label **in** the bar.
/// See [`BarChart::direction`](crate::widgets::BarChart::direction) to set the direction.
/// See [`BarChart::direction`](crate::BarChart::direction) to set the direction.
#[must_use = "method moves the value of self and returns the modified value"]
pub fn label(mut self, label: Line<'a>) -> Self {
self.label = Some(label);
@@ -76,8 +73,6 @@ 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();
@@ -92,8 +87,6 @@ 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();

View File

@@ -1,17 +1,13 @@
use crate::{
buffer::Buffer,
layout::{Alignment, Rect},
style::Style,
text::Line,
widgets::{barchart::Bar, Widget},
};
use ratatui_core::prelude::*;
use super::Bar;
/// A group of bars to be shown by the Barchart.
///
/// # Examples
///
/// ```
/// use ratatui::widgets::{Bar, BarGroup};
/// use ratatui::{prelude::*, widgets::*};
///
/// BarGroup::default()
/// .label("Group 1".into())

View File

@@ -6,22 +6,23 @@
//! [title](Block::title) and [padding](Block::padding).
use itertools::Itertools;
use ratatui_core::{
prelude::{Alignment, Buffer, Line, Rect, Style, Stylize, Widget},
style::Styled,
symbols::border,
widgets::WidgetRef,
};
use strum::{Display, EnumString};
use crate::{
buffer::Buffer,
layout::{Alignment, Rect},
style::{Style, Styled},
symbols::border,
text::Line,
widgets::{Borders, Widget, WidgetRef},
};
use self::title::Position;
mod padding;
pub mod title;
pub use padding::Padding;
pub use title::{Position, Title};
pub use title::Title;
use crate::Borders;
/// Base widget to be used to display a box border around all [upper level ones](crate::widgets).
///
@@ -74,10 +75,7 @@ pub use title::{Position, Title};
/// # Examples
///
/// ```
/// use ratatui::{
/// style::{Color, Style},
/// widgets::{Block, BorderType, Borders},
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// Block::new()
/// .border_type(BorderType::Rounded)
@@ -89,9 +87,12 @@ pub use title::{Position, Title};
///
/// You may also use multiple titles like in the following:
/// ```
/// use ratatui::widgets::{
/// block::{Position, Title},
/// Block,
/// use ratatui::{
/// prelude::*,
/// widgets::{
/// block::{Position, Title},
/// Block,
/// },
/// };
///
/// Block::new()
@@ -101,7 +102,10 @@ pub use title::{Position, Title};
///
/// You can also pass it as parameters of another widget so that the block surrounds them:
/// ```
/// use ratatui::widgets::{Block, Borders, List};
/// use ratatui::{
/// prelude::*,
/// widgets::{Block, Borders, List},
/// };
///
/// let surrounding_block = Block::default()
/// .borders(Borders::ALL)
@@ -224,8 +228,7 @@ 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 {
@@ -273,8 +276,8 @@ impl<'a> Block<'a> {
/// - Two titles with the same alignment (notice the left titles are separated)
/// ```
/// use ratatui::{
/// text::Line,
/// widgets::{Block, Borders},
/// prelude::*,
/// widgets::{block::*, *},
/// };
///
/// Block::new()
@@ -325,8 +328,7 @@ impl<'a> Block<'a> {
/// # Example
///
/// ```
/// use ratatui::{ widgets::Block, text::Line };
///
/// # use ratatui::{ prelude::*, widgets::* };
/// Block::bordered()
/// .title_top("Left1") // By default in the top left corner
/// .title_top(Line::from("Left2").left_aligned())
@@ -354,8 +356,7 @@ impl<'a> Block<'a> {
/// # Example
///
/// ```
/// use ratatui::{ widgets::Block, text::Line };
///
/// # use ratatui::{ prelude::*, widgets::* };
/// Block::bordered()
/// .title_bottom("Left1") // By default in the top left corner
/// .title_bottom(Line::from("Left2").left_aligned())
@@ -384,8 +385,6 @@ 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();
@@ -401,7 +400,10 @@ impl<'a> Block<'a> {
/// This example aligns all titles in the center except the "right" title which explicitly sets
/// [`Alignment::Right`].
/// ```
/// use ratatui::{layout::Alignment, text::Line, widgets::Block};
/// use ratatui::{
/// prelude::*,
/// widgets::{block::*, *},
/// };
///
/// Block::new()
/// .title_alignment(Alignment::Center)
@@ -425,7 +427,13 @@ impl<'a> Block<'a> {
/// This example positions all titles on the bottom except the "top" title which explicitly sets
/// [`Position::Top`].
/// ```
/// use ratatui::widgets::{block::Position, Block};
/// use ratatui::{
/// prelude::*,
/// widgets::{
/// block::{Position, Title},
/// Block,
/// },
/// };
///
/// Block::new()
/// .title_position(Position::Bottom)
@@ -454,14 +462,9 @@ impl<'a> Block<'a> {
///
/// This example shows a `Block` with blue borders.
/// ```
/// use ratatui::{
/// style::{Style, Stylize},
/// widgets::Block,
/// };
/// # use ratatui::{prelude::*, widgets::*};
/// 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();
@@ -484,11 +487,7 @@ impl<'a> Block<'a> {
/// # Example
///
/// ```
/// use ratatui::{
/// style::{Color, Style, Stylize},
/// widgets::{Block, Paragraph},
/// };
///
/// # use ratatui::{prelude::*, widgets::*};
/// 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.
@@ -504,8 +503,7 @@ impl<'a> Block<'a> {
/// .style(Style::new().white().not_bold()); // will be white, and italic
/// ```
///
/// [`Paragraph`]: crate::widgets::Paragraph
/// [`Color`]: crate::style::Color
/// [`Paragraph`]: crate::Paragraph
#[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();
@@ -520,7 +518,7 @@ impl<'a> Block<'a> {
///
/// Display left and right borders.
/// ```
/// use ratatui::widgets::{Block, Borders};
/// # use ratatui::{prelude::*, widgets::*};
/// Block::new().borders(Borders::LEFT | Borders::RIGHT);
/// ```
///
@@ -541,7 +539,7 @@ impl<'a> Block<'a> {
/// # Examples
///
/// ```
/// use ratatui::widgets::{Block, BorderType};
/// # use ratatui::{prelude::*, widgets::*};
/// Block::bordered()
/// .border_type(BorderType::Rounded)
/// .title("Block");
@@ -563,8 +561,7 @@ impl<'a> Block<'a> {
/// # Examples
///
/// ```
/// use ratatui::{widgets::Block, symbols};
///
/// # use ratatui::{prelude::*, widgets::*};
/// Block::bordered().border_set(symbols::border::DOUBLE).title("Block");
/// // Renders
/// // ╔Block╗
@@ -584,8 +581,7 @@ impl<'a> Block<'a> {
///
/// This renders a `Block` with no padding (the default).
/// ```
/// use ratatui::widgets::{Block, Padding};
///
/// # use ratatui::{prelude::*, widgets::*};
/// Block::bordered().padding(Padding::ZERO);
/// // Renders
/// // ┌───────┐
@@ -596,8 +592,7 @@ 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::widgets::{Block, Padding};
///
/// # use ratatui::{prelude::*, widgets::*};
/// Block::bordered().padding(Padding::horizontal(2));
/// // Renders
/// // ┌───────────┐
@@ -616,8 +611,7 @@ impl<'a> Block<'a> {
///
/// Draw a block nested within another block
/// ```
/// use ratatui::{widgets::Block, Frame};
///
/// # use ratatui::{prelude::*, widgets::*};
/// # fn render_nested_block(frame: &mut Frame) {
/// let outer_block = Block::bordered().title("Outer");
/// let inner_block = Block::bordered().title("Inner");
@@ -1000,11 +994,11 @@ impl<'a> Styled for Block<'a> {
#[cfg(test)]
mod tests {
use ratatui_core::style::{Color, Modifier};
use rstest::rstest;
use strum::ParseError;
use super::*;
use crate::style::{Color, Modifier, Stylize};
#[test]
fn create_with_all_borders() {
@@ -1259,7 +1253,7 @@ mod tests {
// .border_style(_DEFAULT_STYLE) // no longer const
// .title_style(_DEFAULT_STYLE) // no longer const
.title_alignment(Alignment::Left)
.title_position(Position::Top)
.title_position(crate::block::Position::Top)
.padding(_DEFAULT_PADDING);
}

View File

@@ -10,7 +10,7 @@
/// # Example
///
/// ```
/// use ratatui::widgets::Padding;
/// use ratatui::{prelude::*, widgets::*};
///
/// Padding::uniform(1);
/// Padding::horizontal(2);
@@ -19,8 +19,8 @@
/// Padding::symmetric(5, 6);
/// ```
///
/// [`Block`]: crate::widgets::Block
/// [`padding`]: crate::widgets::Block::padding
/// [`Block`]: crate::Block
/// [`padding`]: crate::Block::padding
/// [CSS padding]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Padding {

View File

@@ -1,11 +1,10 @@
//! This module holds the [`Title`] element and its related configuration types.
//! A title is a piece of [`Block`](crate::widgets::Block) configuration.
//! A title is a piece of [`Block`](crate::Block) configuration.
use ratatui_core::{layout::Alignment, text::Line};
use strum::{Display, EnumString};
use crate::{layout::Alignment, text::Line};
/// A [`Block`](crate::widgets::Block) title.
/// A [`Block`](crate::Block) title.
///
/// It can be aligned (see [`Alignment`]) and positioned (see [`Position`]).
///
@@ -17,8 +16,8 @@ use crate::{layout::Alignment, text::Line};
/// <https://github.com/ratatui/ratatui/issues/738>.
///
/// Use [`Line`] instead, when the position is not defined as part of the title. When a specific
/// position is needed, use [`Block::title_top`](crate::widgets::Block::title_top) or
/// [`Block::title_bottom`](crate::widgets::Block::title_bottom) instead.
/// position is needed, use [`Block::title_top`](crate::Block::title_top) or
/// [`Block::title_bottom`](crate::Block::title_bottom) instead.
///
/// # Example
///
@@ -31,14 +30,14 @@ use crate::{layout::Alignment, text::Line};
///
/// Blue title on a white background (via [`Stylize`](crate::style::Stylize) trait).
/// ```
/// use ratatui::{style::Stylize, widgets::block::Title};
/// use ratatui::{prelude::*, widgets::block::*};
///
/// Title::from("Title".blue().on_white());
/// ```
///
/// Title with multiple styles (see [`Line`] and [`Stylize`](crate::style::Stylize)).
/// ```
/// use ratatui::{style::Stylize, text::Line, widgets::block::Title};
/// use ratatui::{prelude::*, widgets::block::*};
///
/// Title::from(Line::from(vec!["Q".white().underlined(), "uit".gray()]));
/// ```
@@ -46,7 +45,7 @@ use crate::{layout::Alignment, text::Line};
/// Complete example
/// ```
/// use ratatui::{
/// layout::Alignment,
/// prelude::*,
/// widgets::{
/// block::{Position, Title},
/// Block,
@@ -64,19 +63,19 @@ pub struct Title<'a> {
/// Title alignment
///
/// If [`None`], defaults to the alignment defined with
/// [`Block::title_alignment`](crate::widgets::Block::title_alignment) in the associated
/// [`Block`](crate::widgets::Block).
/// [`Block::title_alignment`](crate::Block::title_alignment) in the associated
/// [`Block`](crate::Block).
pub alignment: Option<Alignment>,
/// Title position
///
/// If [`None`], defaults to the position defined with
/// [`Block::title_position`](crate::widgets::Block::title_position) in the associated
/// [`Block`](crate::widgets::Block).
/// [`Block::title_position`](crate::Block::title_position) in the associated
/// [`Block`](crate::Block).
pub position: Option<Position>,
}
/// Defines the [title](crate::widgets::block::Title) position.
/// Defines the [title](crate::block::Title) position.
///
/// The title can be positioned on top or at the bottom of the block.
/// Defaults to [`Position::Top`].
@@ -84,10 +83,7 @@ pub struct Title<'a> {
/// # Example
///
/// ```
/// use ratatui::widgets::{
/// block::{Position, Title},
/// Block,
/// };
/// use ratatui::widgets::{block::*, *};
///
/// Block::new().title(Title::from("title").position(Position::Bottom));
/// ```

View File

@@ -55,16 +55,12 @@ impl fmt::Debug for Borders {
/// and RIGHT.
///
/// When used with NONE you should consider omitting this completely. For ALL you should consider
/// [`Block::bordered()`](crate::widgets::Block::bordered) instead.
/// [`Block::bordered()`](crate::Block::bordered) instead.
///
/// ## Examples
///
/// ```
/// use ratatui::{
/// border,
/// widgets::{Block, Borders},
/// };
///
/// # use ratatui::{border, prelude::*, widgets::*};
/// Block::new()
/// .title("Construct Borders and use them in place")
/// .borders(border!(TOP, BOTTOM));
@@ -73,7 +69,7 @@ impl fmt::Debug for Borders {
/// `border!` can be called with any number of individual sides:
///
/// ```
/// use ratatui::{border, widgets::Borders};
/// # use ratatui::{border, prelude::*, widgets::*};
/// let right_open = border!(TOP, LEFT, BOTTOM);
/// assert_eq!(right_open, Borders::TOP | Borders::LEFT | Borders::BOTTOM);
/// ```
@@ -81,8 +77,7 @@ impl fmt::Debug for Borders {
/// Single borders work but using `Borders::` directly would be simpler.
///
/// ```
/// use ratatui::{border, widgets::Borders};
///
/// # use ratatui::{border, prelude::*, widgets::*};
/// assert_eq!(border!(TOP), Borders::TOP);
/// assert_eq!(border!(ALL), Borders::ALL);
/// assert_eq!(border!(), Borders::NONE);

View File

@@ -10,15 +10,13 @@
//! [`Monthly`] has several controls for what should be displayed
use std::collections::HashMap;
use ratatui_core::{
prelude::{Alignment, Buffer, Color, Constraint, Layout, Line, Rect, Span, Style, Widget},
widgets::WidgetRef,
};
use time::{Date, Duration, OffsetDateTime};
use crate::{
buffer::Buffer,
layout::{Alignment, Constraint, Layout, Rect},
style::Style,
text::{Line, Span},
widgets::{block::BlockExt, Block, Widget, WidgetRef},
};
use crate::{block::BlockExt, Block};
/// Display a month calendar for the month containing `display_date`
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
@@ -52,8 +50,6 @@ 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());
@@ -64,8 +60,6 @@ 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());
@@ -76,8 +70,6 @@ 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());
@@ -88,8 +80,6 @@ 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();
@@ -215,8 +205,6 @@ 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(
@@ -232,8 +220,6 @@ 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());
@@ -268,7 +254,6 @@ mod tests {
use time::Month;
use super::*;
use crate::style::Color;
#[test]
fn event_store() {

View File

@@ -22,6 +22,12 @@ mod world;
use std::{fmt, iter::zip};
use itertools::Itertools;
use ratatui_core::{
prelude::{Buffer, Color, Rect, Style, Widget},
symbols::{self, Marker},
text::Line as TextLine,
widgets::WidgetRef,
};
pub use self::{
circle::Circle,
@@ -30,14 +36,7 @@ pub use self::{
points::Points,
rectangle::Rectangle,
};
use crate::{
buffer::Buffer,
layout::Rect,
style::{Color, Style},
symbols::{self, Marker},
text::Line as TextLine,
widgets::{block::BlockExt, Block, Widget, WidgetRef},
};
use crate::block::{Block, BlockExt};
/// Something that can be drawn on a [`Canvas`].
///
@@ -363,10 +362,7 @@ impl<'a, 'b> Painter<'a, 'b> {
/// # Examples
///
/// ```
/// use ratatui::{
/// symbols,
/// widgets::canvas::{Context, Painter},
/// };
/// use ratatui::{prelude::*, widgets::canvas::*};
///
/// 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);
@@ -409,11 +405,7 @@ impl<'a, 'b> Painter<'a, 'b> {
/// # Example
///
/// ```
/// use ratatui::{
/// style::Color,
/// symbols,
/// widgets::canvas::{Context, Painter},
/// };
/// use ratatui::{prelude::*, widgets::canvas::*};
///
/// 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);
@@ -439,7 +431,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::Frame
/// [`Frame`]: ratatui_core::prelude::Frame
#[derive(Debug)]
pub struct Context<'a> {
x_bounds: [f64; 2],
@@ -463,7 +455,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::{symbols, widgets::canvas::Context};
/// use ratatui::{prelude::*, widgets::canvas::*};
///
/// let ctx = Context::new(
/// 100,
@@ -527,8 +519,6 @@ 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>>,
@@ -579,10 +569,7 @@ impl<'a> Context<'a> {
/// ```
/// use ratatui::{
/// style::Color,
/// widgets::{
/// canvas::{Canvas, Line, Map, MapResolution, Rectangle},
/// Block,
/// },
/// widgets::{canvas::*, *},
/// };
///
/// Canvas::default()
@@ -717,7 +704,7 @@ where
/// # Examples
///
/// ```
/// use ratatui::{symbols, widgets::canvas::Canvas};
/// use ratatui::{prelude::*, widgets::canvas::*};
///
/// Canvas::default()
/// .marker(symbols::Marker::Braille)
@@ -828,9 +815,9 @@ where
#[cfg(test)]
mod tests {
use indoc::indoc;
use ratatui_core::buffer::Cell;
use super::*;
use crate::buffer::Cell;
// helper to test the canvas checks that drawing a vertical and horizontal line
// results in the expected output

View File

@@ -1,7 +1,6 @@
use crate::{
style::Color,
widgets::canvas::{Painter, Shape},
};
use ratatui_core::style::Color;
use crate::canvas::{Painter, Shape};
/// A circle with a given center and radius and with a given color
#[derive(Debug, Default, Clone, PartialEq)]
@@ -31,17 +30,12 @@ impl Shape for Circle {
#[cfg(test)]
mod tests {
use crate::{
buffer::Buffer,
layout::Rect,
style::Color,
symbols::Marker,
widgets::{
canvas::{Canvas, Circle},
Widget,
},
use ratatui_core::{
buffer::Buffer, layout::Rect, style::Color, symbols::Marker, widgets::Widget,
};
use crate::canvas::{Canvas, Circle};
#[test]
fn test_it_draws_a_circle() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 5));

View File

@@ -1,7 +1,6 @@
use crate::{
style::Color,
widgets::canvas::{Painter, Shape},
};
use ratatui_core::style::Color;
use crate::canvas::{Painter, Shape};
/// A line from `(x1, y1)` to `(x2, y2)` with the given color
#[derive(Debug, Default, Clone, PartialEq)]
@@ -112,16 +111,18 @@ fn draw_line_high(painter: &mut Painter, x1: usize, y1: usize, x2: usize, y2: us
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
use crate::{
use ratatui_core::{
buffer::Buffer,
layout::Rect,
style::{Style, Stylize},
symbols::Marker,
widgets::{canvas::Canvas, Widget},
text,
widgets::Widget,
};
use rstest::rstest;
use super::*;
use crate::canvas::Canvas;
#[rstest]
#[case::off_grid(&Line::new(-1.0, -1.0, 10.0, 10.0, Color::Red), [" "; 10])]
@@ -207,7 +208,7 @@ mod tests {
fn tests<'expected_line, ExpectedLines>(#[case] line: &Line, #[case] expected: ExpectedLines)
where
ExpectedLines: IntoIterator,
ExpectedLines::Item: Into<crate::text::Line<'expected_line>>,
ExpectedLines::Item: Into<text::Line<'expected_line>>,
{
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
let canvas = Canvas::default()

View File

@@ -1,13 +1,10 @@
use ratatui_core::style::Color;
use strum::{Display, EnumString};
use crate::{
style::Color,
widgets::canvas::{
world::{WORLD_HIGH_RESOLUTION, WORLD_LOW_RESOLUTION},
Painter, Shape,
},
use crate::canvas::{
world::{WORLD_HIGH_RESOLUTION, WORLD_LOW_RESOLUTION},
Painter, Shape,
};
/// Defines how many points are going to be used to draw a [`Map`].
///
/// You generally want a [high](MapResolution::High) resolution map.
@@ -62,15 +59,14 @@ impl Shape for Map {
#[cfg(test)]
mod tests {
use ratatui_core::{
prelude::{Buffer, Color, Rect, Widget},
symbols::Marker,
};
use strum::ParseError;
use super::*;
use crate::{
buffer::Buffer,
layout::Rect,
symbols::Marker,
widgets::{canvas::Canvas, Widget},
};
use crate::canvas::Canvas;
#[test]
fn map_resolution_to_string() {

View File

@@ -1,7 +1,6 @@
use crate::{
style::Color,
widgets::canvas::{Painter, Shape},
};
use ratatui_core::style::Color;
use crate::canvas::{Painter, Shape};
/// A group of points with a given color
#[derive(Debug, Default, Clone, PartialEq)]

View File

@@ -1,9 +1,8 @@
use crate::{
style::Color,
widgets::canvas::{Line, Painter, Shape},
};
use ratatui_core::style::Color;
/// A rectangle to draw on a [`Canvas`](crate::widgets::canvas::Canvas)
use crate::canvas::{Line, Painter, Shape};
/// A rectangle to draw on a [`Canvas`](super::Canvas)
///
/// Sizes used here are **not** in terminal cell. This is much more similar to the
/// mathematic coordinate system.
@@ -65,14 +64,10 @@ impl Shape for Rectangle {
#[cfg(test)]
mod tests {
use ratatui_core::{prelude::*, symbols::Marker};
use super::*;
use crate::{
buffer::Buffer,
layout::{Margin, Rect},
style::{Style, Stylize},
symbols::Marker,
widgets::{canvas::Canvas, Widget},
};
use crate::canvas::Canvas;
#[test]
fn draw_block_lines() {

View File

@@ -1,20 +1,13 @@
use std::{cmp::max, ops::Not};
use ratatui_core::{layout::Flex, prelude::*, style::Styled, widgets::WidgetRef};
use strum::{Display, EnumString};
use crate::{
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, Widget, WidgetRef,
},
block::BlockExt,
canvas::{Canvas, Line as CanvasLine, Points},
Block,
};
/// An X or Y axis for the [`Chart`] widget
///
/// An axis can have a [title](Axis::title) which will be displayed at the end of the axis. For an
@@ -28,11 +21,7 @@ use crate::{
/// # Example
///
/// ```rust
/// use ratatui::{
/// style::{Style, Stylize},
/// widgets::Axis,
/// };
///
/// use ratatui::{prelude::*, widgets::*};
/// let axis = Axis::default()
/// .title("X Axis")
/// .style(Style::default().gray())
@@ -101,8 +90,7 @@ impl<'a> Axis<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{style::Stylize, widgets::Axis};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let axis = Axis::default()
/// .bounds([0.0, 50.0])
/// .labels(["0".bold(), "25".into(), "50".bold()]);
@@ -130,8 +118,7 @@ impl<'a> Axis<'a> {
/// like so
///
/// ```rust
/// use ratatui::{style::Stylize, widgets::Axis};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let axis = Axis::default().red();
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -306,11 +293,7 @@ impl LegendPosition {
/// This example draws a red line between two points.
///
/// ```rust
/// use ratatui::{
/// style::Stylize,
/// symbols::Marker,
/// widgets::{Dataset, GraphType},
/// };
/// use ratatui::{prelude::*, symbols::Marker, widgets::*};
///
/// let dataset = Dataset::default()
/// .name("dataset 1")
@@ -414,8 +397,7 @@ impl<'a> Dataset<'a> {
/// like so
///
/// ```rust
/// use ratatui::{style::Stylize, widgets::Dataset};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let dataset = Dataset::default().red();
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -466,11 +448,7 @@ struct ChartLayout {
/// # Examples
///
/// ```
/// use ratatui::{
/// style::{Style, Stylize},
/// symbols,
/// widgets::{Axis, Block, Chart, Dataset, GraphType},
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// // Create the datasets to fill the chart with
/// let datasets = vec![
@@ -539,19 +517,17 @@ impl<'a> Chart<'a> {
/// This creates a simple chart with one [`Dataset`]
///
/// ```rust
/// use ratatui::widgets::{Chart, Dataset};
///
/// let data_points = vec![];
/// # use ratatui::{prelude::*, widgets::*};
/// # 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::widgets::{Chart, Dataset};
///
/// let data_points = vec![];
/// let data_points2 = vec![];
/// # use ratatui::{prelude::*, widgets::*};
/// # let data_points = vec![];
/// # let data_points2 = vec![];
/// let chart = Chart::new(vec![
/// Dataset::default().data(&data_points),
/// Dataset::default().data(&data_points2),
@@ -601,8 +577,7 @@ impl<'a> Chart<'a> {
/// # Example
///
/// ```rust
/// use ratatui::widgets::{Axis, Chart};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let chart = Chart::new(vec![]).x_axis(
/// Axis::default()
/// .title("X Axis")
@@ -625,8 +600,7 @@ impl<'a> Chart<'a> {
/// # Example
///
/// ```rust
/// use ratatui::widgets::{Axis, Chart};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let chart = Chart::new(vec![]).y_axis(
/// Axis::default()
/// .title("Y Axis")
@@ -657,8 +631,7 @@ impl<'a> Chart<'a> {
/// its height is greater than 25% of the total widget height.
///
/// ```
/// use ratatui::{layout::Constraint, widgets::Chart};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let constraints = (Constraint::Ratio(1, 3), Constraint::Ratio(1, 4));
/// let chart = Chart::new(vec![]).hidden_legend_constraints(constraints);
/// ```
@@ -667,8 +640,7 @@ impl<'a> Chart<'a> {
/// first one is always true.
///
/// ```
/// use ratatui::{layout::Constraint, widgets::Chart};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let constraints = (Constraint::Min(0), Constraint::Ratio(1, 4));
/// let chart = Chart::new(vec![]).hidden_legend_constraints(constraints);
/// ```
@@ -677,8 +649,7 @@ impl<'a> Chart<'a> {
/// [`Chart::legend_position`].
///
/// ```
/// use ratatui::{layout::Constraint, widgets::Chart};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let constraints = (Constraint::Length(0), Constraint::Ratio(1, 4));
/// let chart = Chart::new(vec![]).hidden_legend_constraints(constraints);
/// ```
@@ -710,16 +681,14 @@ 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"]
@@ -1101,7 +1070,7 @@ impl WidgetRef for Chart<'_> {
for (i, (dataset_name, dataset_style)) in self
.datasets
.iter()
.filter_map(|ds| Some((ds.name.as_ref()?, ds.style())))
.filter_map(|ds| Some((ds.name.as_ref()?, ds.style)))
.enumerate()
{
let name = dataset_name.clone().patch_style(dataset_style);
@@ -1161,7 +1130,6 @@ mod tests {
use strum::ParseError;
use super::*;
use crate::style::{Modifier, Stylize};
struct LegendTestCase {
chart_area: Rect,

View File

@@ -1,8 +1,4 @@
use crate::{
buffer::Buffer,
layout::Rect,
widgets::{Widget, WidgetRef},
};
use ratatui_core::{prelude::*, widgets::WidgetRef};
/// A widget to clear/reset a certain area to allow overdrawing (e.g. for popups).
///
@@ -12,11 +8,7 @@ use crate::{
/// # Examples
///
/// ```
/// use ratatui::{
/// layout::Rect,
/// widgets::{Block, Clear},
/// Frame,
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// fn draw_on_clear(f: &mut Frame, area: Rect) {
/// let block = Block::bordered().title("Block");
@@ -51,7 +43,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]);

View File

@@ -1,11 +1,6 @@
use crate::{
buffer::Buffer,
layout::Rect,
style::{Color, Style, Styled},
symbols::{self},
text::{Line, Span},
widgets::{block::BlockExt, Block, Widget, WidgetRef},
};
use ratatui_core::{prelude::*, style::Styled, widgets::WidgetRef};
use crate::{block::BlockExt, Block};
/// A widget to display a progress bar.
///
@@ -23,14 +18,16 @@ use crate::{
/// # Example
///
/// ```
/// use ratatui::{
/// style::{Style, Stylize},
/// widgets::{Block, Gauge},
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// Gauge::default()
/// .block(Block::bordered().title("Progress"))
/// .gauge_style(Style::new().white().on_black().italic())
/// .gauge_style(
/// Style::default()
/// .fg(Color::White)
/// .bg(Color::Black)
/// .add_modifier(Modifier::ITALIC),
/// )
/// .percent(20);
/// ```
///
@@ -247,15 +244,16 @@ fn get_unicode_block<'a>(frac: f64) -> &'a str {
/// # Examples:
///
/// ```
/// use ratatui::{
/// style::{Style, Stylize},
/// symbols,
/// widgets::{Block, LineGauge},
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// LineGauge::default()
/// .block(Block::bordered().title("Progress"))
/// .filled_style(Style::new().white().on_black().bold())
/// .filled_style(
/// Style::default()
/// .fg(Color::White)
/// .bg(Color::Black)
/// .add_modifier(Modifier::BOLD),
/// )
/// .line_set(symbols::line::THICK)
/// .ratio(0.4);
/// ```
@@ -447,10 +445,7 @@ 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() {

View File

@@ -0,0 +1,31 @@
mod barchart;
pub mod block;
mod borders;
#[cfg(feature = "widget-calendar")]
pub mod calendar;
pub mod canvas;
mod chart;
mod clear;
mod gauge;
mod list;
mod paragraph;
mod reflow;
mod scrollbar;
mod sparkline;
mod table;
mod tabs;
pub use self::{
barchart::{Bar, BarChart, BarGroup},
block::{Block, BorderType, Padding},
borders::*,
chart::{Axis, Chart, Dataset, GraphType, LegendPosition},
clear::Clear,
gauge::{Gauge, LineGauge},
list::{List, ListDirection, ListItem, ListState},
paragraph::{Paragraph, Wrap},
scrollbar::{ScrollDirection, Scrollbar, ScrollbarOrientation, ScrollbarState},
sparkline::{RenderDirection, Sparkline},
table::{Cell, HighlightSpacing, Row, Table, TableState},
tabs::Tabs,
};

View File

@@ -1,4 +1,4 @@
use crate::{style::Style, text::Text};
use ratatui_core::prelude::*;
/// A single item in a [`List`]
///
@@ -19,15 +19,14 @@ use crate::{style::Style, text::Text};
/// You can create [`ListItem`]s from simple `&str`
///
/// ```rust
/// use ratatui::widgets::ListItem;
/// # use ratatui::{prelude::*, widgets::*};
/// let item = ListItem::new("Item 1");
/// ```
///
/// Anything that can be converted to [`Text`] can be a [`ListItem`].
///
/// ```rust
/// use ratatui::{text::Line, widgets::ListItem};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let item1: ListItem = "Item 1".into();
/// let item2: ListItem = Line::raw("Item 2").into();
/// ```
@@ -35,8 +34,7 @@ use crate::{style::Style, text::Text};
/// A [`ListItem`] styled with [`Stylize`]
///
/// ```rust
/// use ratatui::{style::Stylize, widgets::ListItem};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let item = ListItem::new("Item 1").red().on_white();
/// ```
///
@@ -44,12 +42,7 @@ use crate::{style::Style, text::Text};
/// [`Text`]
///
/// ```rust
/// use ratatui::{
/// style::Stylize,
/// text::{Span, Text},
/// widgets::ListItem,
/// };
///
/// # use ratatui::{prelude::*, widgets::*};
/// let mut text = Text::default();
/// text.extend(["Item".blue(), Span::raw(" "), "1".bold().red()]);
/// let item = ListItem::new(text);
@@ -58,15 +51,12 @@ use crate::{style::Style, text::Text};
/// A right-aligned `ListItem`
///
/// ```rust
/// use ratatui::{text::Text, widgets::ListItem};
///
/// ListItem::new(Text::from("foo").right_aligned());
/// # use ratatui::{prelude::*, widgets::*};
/// ListItem::new(Text::from("foo").alignment(Alignment::Right));
/// ```
///
/// [`List`]: crate::widgets::List
/// [`List`]: crate::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>,
@@ -83,32 +73,29 @@ impl<'a> ListItem<'a> {
/// You can create [`ListItem`]s from simple `&str`
///
/// ```rust
/// use ratatui::widgets::ListItem;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let item = ListItem::new("Item 1");
/// ```
///
/// Anything that can be converted to [`Text`] can be a [`ListItem`].
///
/// ```rust
/// use ratatui::{text::Line, widgets::ListItem};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let item1: ListItem = "Item 1".into();
/// let item2: ListItem = Line::raw("Item 2").into();
/// ```
///
/// You can also create multiline items
/// You can also create multilines item
///
/// ```rust
/// use ratatui::widgets::ListItem;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let item = ListItem::new("Multi-line\nitem");
/// ```
///
/// # See also
///
/// - [`List::new`](crate::widgets::List::new) to create a list of items that can be converted
/// to [`ListItem`]
/// - [`List::new`](crate::List::new) to create a list of items that can be converted to
/// [`ListItem`]
pub fn new<T>(content: T) -> Self
where
T: Into<Text<'a>>,
@@ -131,11 +118,7 @@ impl<'a> ListItem<'a> {
/// # Example
///
/// ```rust
/// use ratatui::{
/// style::{Style, Stylize},
/// widgets::ListItem,
/// };
///
/// # use ratatui::{prelude::*, widgets::*};
/// let item = ListItem::new("Item 1").style(Style::new().red().italic());
/// ```
///
@@ -144,14 +127,12 @@ impl<'a> ListItem<'a> {
/// concisely.
///
/// ```rust
/// use ratatui::{style::Stylize, widgets::ListItem};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let item = ListItem::new("Item 1").red().italic();
/// ```
///
/// [`Styled`]: crate::style::Styled
/// [`ListState`]: crate::widgets::list::ListState
/// [`Color`]: crate::style::Color
/// [`ListState`]: crate::list::ListState
#[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,8 +146,7 @@ impl<'a> ListItem<'a> {
/// One line item
///
/// ```rust
/// use ratatui::widgets::ListItem;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let item = ListItem::new("Item 1");
/// assert_eq!(item.height(), 1);
/// ```
@@ -174,8 +154,7 @@ impl<'a> ListItem<'a> {
/// Two lines item (note the `\n`)
///
/// ```rust
/// use ratatui::widgets::ListItem;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let item = ListItem::new("Multi-line\nitem");
/// assert_eq!(item.height(), 2);
/// ```
@@ -188,15 +167,13 @@ impl<'a> ListItem<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::ListItem;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let item = ListItem::new("12345");
/// assert_eq!(item.width(), 5);
/// ```
///
/// ```rust
/// use ratatui::widgets::ListItem;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let item = ListItem::new("12345\n1234567");
/// assert_eq!(item.width(), 7);
/// ```
@@ -221,10 +198,6 @@ mod tests {
use pretty_assertions::assert_eq;
use super::*;
use crate::{
style::{Color, Modifier, Stylize},
text::{Line, Span},
};
#[test]
fn new_from_str() {

View File

@@ -1,9 +1,8 @@
use ratatui_core::{prelude::Style, style::Styled};
use strum::{Display, EnumString};
use crate::{
style::{Style, Styled},
widgets::{Block, HighlightSpacing, ListItem},
};
use super::ListItem;
use crate::{Block, HighlightSpacing};
/// A widget to display several items among which one can be selected (optional)
///
@@ -13,7 +12,7 @@ use crate::{
/// the item's height is automatically determined. A `List` can also be put in reverse order (i.e.
/// *bottom to top*) whereas a [`Table`] cannot.
///
/// [`Table`]: crate::widgets::Table
/// [`Table`]: crate::Table
///
/// List items can be aligned using [`Text::alignment`], for more details see [`ListItem`].
///
@@ -39,20 +38,14 @@ use crate::{
/// # Examples
///
/// ```
/// use ratatui::{
/// layout::Rect,
/// style::{Style, Stylize},
/// widgets::{Block, List, ListDirection, ListItem},
/// Frame,
/// };
///
/// use ratatui::{prelude::*, widgets::*};
/// # 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::new().white())
/// .highlight_style(Style::new().italic())
/// .style(Style::default().fg(Color::White))
/// .highlight_style(Style::default().add_modifier(Modifier::ITALIC))
/// .highlight_symbol(">>")
/// .repeat_highlight_symbol(true)
/// .direction(ListDirection::BottomToTop);
@@ -64,13 +57,7 @@ use crate::{
/// # Stateful example
///
/// ```rust
/// use ratatui::{
/// layout::Rect,
/// style::{Style, Stylize},
/// widgets::{Block, List, ListState},
/// Frame,
/// };
///
/// # use ratatui::{prelude::*, widgets::*};
/// # fn ui(frame: &mut Frame) {
/// # let area = Rect::default();
/// // This should be stored outside of the function in your application state.
@@ -78,7 +65,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().reversed())
/// .highlight_style(Style::new().add_modifier(Modifier::REVERSED))
/// .highlight_symbol(">>")
/// .repeat_highlight_symbol(true);
///
@@ -95,12 +82,9 @@ use crate::{
/// (0..5).map(|i| format!("Item{i}")).collect::<List>();
/// ```
///
/// [`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
/// [`ListState`]: crate::list::ListState
/// [scroll]: crate::list::ListState::offset
/// [select]: crate::list::ListState::select
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub struct List<'a> {
/// An optional block to wrap the widget in
@@ -148,23 +132,17 @@ impl<'a> List<'a> {
/// From a slice of [`&str`]
///
/// ```
/// use ratatui::widgets::List;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let list = List::new(["Item 1", "Item 2"]);
/// ```
///
/// From [`Text`]
///
/// ```
/// use ratatui::{
/// style::{Style, Stylize},
/// text::Text,
/// widgets::List,
/// };
///
/// # use ratatui::{prelude::*, widgets::*};
/// let list = List::new([
/// Text::styled("Item 1", Style::new().red()),
/// Text::styled("Item 2", Style::new().red()),
/// Text::styled("Item 1", Style::default().red()),
/// Text::styled("Item 2", Style::default().red()),
/// ]);
/// ```
///
@@ -172,13 +150,10 @@ impl<'a> List<'a> {
/// [`List::items`] fluent setter.
///
/// ```rust
/// use ratatui::widgets::List;
///
/// # use ratatui::{prelude::*, widgets::*};
/// 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,
@@ -203,12 +178,9 @@ impl<'a> List<'a> {
/// # Example
///
/// ```rust
/// use ratatui::widgets::List;
///
/// # use ratatui::{prelude::*, widgets::*};
/// 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
@@ -228,9 +200,8 @@ impl<'a> List<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::{Block, List};
///
/// let items = ["Item 1"];
/// # use ratatui::{prelude::*, widgets::*};
/// # let items = ["Item 1"];
/// let block = Block::bordered().title("List");
/// let list = List::new(items).block(block);
/// ```
@@ -253,12 +224,8 @@ impl<'a> List<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{
/// style::{Style, Stylize},
/// widgets::List,
/// };
///
/// let items = ["Item 1"];
/// # use ratatui::{prelude::*, widgets::*};
/// # let items = ["Item 1"];
/// let list = List::new(items).style(Style::new().red().italic());
/// ```
///
@@ -268,13 +235,10 @@ impl<'a> List<'a> {
/// [`Stylize`]: crate::style::Stylize
///
/// ```rust
/// use ratatui::{style::Stylize, widgets::List};
///
/// let items = ["Item 1"];
/// # use ratatui::{prelude::*, widgets::*};
/// # 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();
@@ -290,9 +254,8 @@ impl<'a> List<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::List;
///
/// let items = ["Item 1", "Item 2"];
/// # use ratatui::{prelude::*, widgets::*};
/// # 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"]
@@ -315,16 +278,10 @@ impl<'a> List<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{
/// style::{Style, Stylize},
/// widgets::List,
/// };
///
/// let items = ["Item 1", "Item 2"];
/// # use ratatui::{prelude::*, widgets::*};
/// # 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();
@@ -363,9 +320,8 @@ impl<'a> List<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::{HighlightSpacing, List};
///
/// let items = ["Item 1"];
/// # use ratatui::{prelude::*, widgets::*};
/// # 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"]
@@ -386,9 +342,8 @@ impl<'a> List<'a> {
/// Bottom to top
///
/// ```rust
/// use ratatui::widgets::{List, ListDirection};
///
/// let items = ["Item 1"];
/// # use ratatui::{prelude::*, widgets::*};
/// # 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"]
@@ -406,9 +361,8 @@ impl<'a> List<'a> {
/// A padding value of 1 will keep 1 item above and 1 item bellow visible if possible
///
/// ```rust
/// use ratatui::widgets::List;
///
/// let items = ["Item 1"];
/// # use ratatui::{prelude::*, widgets::*};
/// # 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"]
@@ -464,9 +418,9 @@ where
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use ratatui_core::style::{Color, Modifier, Stylize};
use super::*;
use crate::style::{Color, Modifier, Stylize};
#[test]
fn collect_list_from_iterator() {

View File

@@ -1,13 +1,10 @@
use ratatui_core::{
prelude::{Buffer, Rect, StatefulWidget, Widget},
widgets::{StatefulWidgetRef, WidgetRef},
};
use unicode_width::UnicodeWidthStr;
use crate::{
buffer::Buffer,
layout::Rect,
widgets::{
block::BlockExt, List, ListDirection, ListState, StatefulWidget, StatefulWidgetRef, Widget,
WidgetRef,
},
};
use crate::{block::BlockExt, List, ListDirection, ListState};
impl Widget for List<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
@@ -273,17 +270,11 @@ impl List<'_> {
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use ratatui_core::prelude::*;
use rstest::{fixture, rstest};
use super::*;
use crate::{
backend,
layout::{Alignment, Rect},
style::{Color, Modifier, Style, Stylize},
text::Line,
widgets::{Block, HighlightSpacing, ListItem, StatefulWidget, Widget},
Terminal,
};
use crate::{Block, HighlightSpacing, ListItem};
#[fixture]
fn single_line_buf() -> Buffer {

View File

@@ -20,15 +20,10 @@
/// # Example
///
/// ```rust
/// use ratatui::{
/// layout::Rect,
/// widgets::{List, ListState},
/// Frame,
/// };
///
/// # use ratatui::{prelude::*, widgets::*};
/// # 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.
@@ -41,7 +36,7 @@
/// # }
/// ```
///
/// [`List`]: crate::widgets::List
/// [`List`]: crate::List
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ListState {
@@ -57,8 +52,7 @@ impl ListState {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::ListState;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let state = ListState::default().with_offset(1);
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -74,8 +68,7 @@ impl ListState {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::ListState;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let state = ListState::default().with_selected(Some(1));
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -89,8 +82,7 @@ impl ListState {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::ListState;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let state = ListState::default();
/// assert_eq!(state.offset(), 0);
/// ```
@@ -103,8 +95,7 @@ impl ListState {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::ListState;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = ListState::default();
/// *state.offset_mut() = 1;
/// ```
@@ -119,9 +110,8 @@ impl ListState {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::ListState;
///
/// let state = ListState::default();
/// # use ratatui::{prelude::*, widgets::*};
/// let state = TableState::default();
/// assert_eq!(state.selected(), None);
/// ```
pub const fn selected(&self) -> Option<usize> {
@@ -135,8 +125,7 @@ impl ListState {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::ListState;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = ListState::default();
/// *state.selected_mut() = Some(1);
/// ```
@@ -151,8 +140,7 @@ impl ListState {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::ListState;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = ListState::default();
/// state.select(Some(1));
/// ```
@@ -171,8 +159,7 @@ impl ListState {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::ListState;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = ListState::default();
/// state.select_next();
/// ```
@@ -189,8 +176,7 @@ impl ListState {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::ListState;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = ListState::default();
/// state.select_previous();
/// ```
@@ -207,8 +193,7 @@ impl ListState {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::ListState;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = ListState::default();
/// state.select_first();
/// ```
@@ -224,8 +209,7 @@ impl ListState {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::ListState;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = ListState::default();
/// state.select_last();
/// ```
@@ -242,8 +226,7 @@ impl ListState {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::ListState;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = ListState::default();
/// state.scroll_down_by(4);
/// ```
@@ -261,8 +244,7 @@ impl ListState {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::ListState;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = ListState::default();
/// state.scroll_up_by(4);
/// ```
@@ -276,7 +258,7 @@ impl ListState {
mod tests {
use pretty_assertions::assert_eq;
use crate::widgets::ListState;
use crate::ListState;
#[test]
fn selected() {

View File

@@ -1,15 +1,10 @@
use ratatui_core::{prelude::*, style::Styled, text::StyledGrapheme, widgets::WidgetRef};
use unicode_width::UnicodeWidthStr;
use crate::{
buffer::Buffer,
layout::{Alignment, Position, Rect},
style::{Style, Styled},
text::{Line, StyledGrapheme, Text},
widgets::{
block::BlockExt,
reflow::{LineComposer, LineTruncator, WordWrapper, WrappedLine},
Block, Widget, WidgetRef,
},
block::BlockExt,
reflow::{LineComposer, LineTruncator, WordWrapper, WrappedLine},
Block,
};
const fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment) -> u16 {
@@ -61,12 +56,7 @@ const fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Align
/// # Example
///
/// ```
/// use ratatui::{
/// layout::Alignment,
/// style::{Style, Stylize},
/// text::{Line, Span},
/// widgets::{Block, Paragraph, Wrap},
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// let text = vec![
/// Line::from(vec![
@@ -83,8 +73,6 @@ 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
@@ -106,10 +94,7 @@ pub struct Paragraph<'a> {
/// ## Examples
///
/// ```
/// use ratatui::{
/// text::Text,
/// widgets::{Paragraph, Wrap},
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// let bullet_points = Text::from(
/// r#"Some indented points:
@@ -151,12 +136,7 @@ impl<'a> Paragraph<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{
/// style::{Style, Stylize},
/// text::{Line, Text},
/// widgets::Paragraph,
/// };
///
/// # use ratatui::{prelude::*, widgets::*};
/// let paragraph = Paragraph::new("Hello, world!");
/// let paragraph = Paragraph::new(String::from("Hello, world!"));
/// let paragraph = Paragraph::new(Text::raw("Hello, world!"));
@@ -182,8 +162,7 @@ impl<'a> Paragraph<'a> {
/// # Example
///
/// ```rust
/// use ratatui::widgets::{Block, Paragraph};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let paragraph = Paragraph::new("Hello, world!").block(Block::bordered().title("Paragraph"));
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -203,15 +182,9 @@ impl<'a> Paragraph<'a> {
/// # Example
///
/// ```rust
/// use ratatui::{
/// style::{Style, Stylize},
/// widgets::Paragraph,
/// };
///
/// # use ratatui::{prelude::*, widgets::*};
/// 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();
@@ -225,8 +198,7 @@ impl<'a> Paragraph<'a> {
/// # Example
///
/// ```rust
/// use ratatui::widgets::{Paragraph, Wrap};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let paragraph = Paragraph::new("Hello, world!").wrap(Wrap { trim: true });
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -263,8 +235,7 @@ impl<'a> Paragraph<'a> {
/// # Example
///
/// ```rust
/// use ratatui::{layout::Alignment, widgets::Paragraph};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let paragraph = Paragraph::new("Hello World").alignment(Alignment::Center);
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -280,8 +251,7 @@ impl<'a> Paragraph<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::Paragraph;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let paragraph = Paragraph::new("Hello World").left_aligned();
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -296,8 +266,7 @@ impl<'a> Paragraph<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::Paragraph;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let paragraph = Paragraph::new("Hello World").centered();
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -312,8 +281,7 @@ impl<'a> Paragraph<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::Paragraph;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let paragraph = Paragraph::new("Hello World").right_aligned();
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
@@ -334,8 +302,7 @@ impl<'a> Paragraph<'a> {
/// # Example
///
/// ```ignore
/// use ratatui::{widgets::{Paragraph, Wrap}};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let paragraph = Paragraph::new("Hello World")
/// .wrap(Wrap { trim: false });
/// assert_eq!(paragraph.line_count(20), 1);
@@ -389,8 +356,7 @@ impl<'a> Paragraph<'a> {
/// # Example
///
/// ```ignore
/// use ratatui::{widgets::Paragraph};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let paragraph = Paragraph::new("Hello World");
/// assert_eq!(paragraph.line_width(), 11);
///
@@ -501,16 +467,10 @@ impl<'a> Styled for Paragraph<'a> {
#[cfg(test)]
mod test {
use ratatui_core::backend::TestBackend;
use super::*;
use crate::{
backend::TestBackend,
buffer::Buffer,
layout::{Alignment, Rect},
style::{Color, Modifier, Style, Stylize},
text::{Line, Span, Text},
widgets::{block::Position, Borders, Widget},
Terminal,
};
use crate::{block::title::Position, Borders};
/// Tests the [`Paragraph`] widget against the expected [`Buffer`] by rendering it onto an equal
/// area and comparing the rendered and expected content.

View File

@@ -1,10 +1,9 @@
use std::{collections::VecDeque, mem};
use ratatui_core::{layout::Alignment, text::StyledGrapheme};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use crate::{layout::Alignment, text::StyledGrapheme};
/// A state machine to pack styled symbols into lines.
/// Cannot implement it as Iterator since it yields slices of the internal buffer (need streaming
/// iterators for that).
@@ -344,12 +343,13 @@ fn trim_offset(src: &str, mut offset: usize) -> &str {
#[cfg(test)]
mod test {
use super::*;
use crate::{
use ratatui_core::{
style::Style,
text::{Line, Text},
};
use super::*;
#[derive(Clone, Copy)]
enum Composer {
WordWrapper { trim: bool },

View File

@@ -8,17 +8,13 @@
use std::iter;
use ratatui_core::{
prelude::*,
symbols::scrollbar::{Set, DOUBLE_HORIZONTAL, DOUBLE_VERTICAL},
};
use strum::{Display, EnumString};
use unicode_width::UnicodeWidthStr;
use crate::{
buffer::Buffer,
layout::Rect,
style::Style,
symbols::scrollbar::{Set, DOUBLE_HORIZONTAL, DOUBLE_VERTICAL},
widgets::StatefulWidget,
};
/// A widget to display a scrollbar
///
/// The following components of the scrollbar are customizable in symbol and style. Note the
@@ -42,15 +38,7 @@ use crate::{
/// # Examples
///
/// ```rust
/// use ratatui::{
/// layout::{Margin, Rect},
/// text::Line,
/// widgets::{
/// Block, Borders, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState,
/// StatefulWidget,
/// },
/// Frame,
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// # fn render_paragraph_with_scrollbar(frame: &mut Frame, area: Rect) {
/// let vertical_scroll = 0; // from app state
@@ -265,8 +253,6 @@ 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();
@@ -292,8 +278,6 @@ 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();
@@ -319,8 +303,6 @@ 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();
@@ -346,8 +328,6 @@ 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();
@@ -401,8 +381,6 @@ 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();
@@ -642,7 +620,6 @@ mod tests {
use strum::ParseError;
use super::*;
use crate::{text::Text, widgets::Widget};
#[test]
fn scroll_direction_to_string() {

View File

@@ -1,14 +1,9 @@
use std::cmp::min;
use ratatui_core::{prelude::*, style::Styled, widgets::WidgetRef};
use strum::{Display, EnumString};
use crate::{
buffer::Buffer,
layout::Rect,
style::{Style, Styled},
symbols::{self},
widgets::{block::BlockExt, Block, Widget, WidgetRef},
};
use crate::{block::BlockExt, Block};
/// Widget to render a sparkline over one or more lines.
///
@@ -27,10 +22,7 @@ use crate::{
/// # Examples
///
/// ```
/// use ratatui::{
/// style::{Style, Stylize},
/// widgets::{Block, RenderDirection, Sparkline},
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// Sparkline::default()
/// .block(Block::bordered().title("Sparkline"))
@@ -82,8 +74,6 @@ 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();
@@ -95,8 +85,7 @@ impl<'a> Sparkline<'a> {
/// # Example
///
/// ```
/// use ratatui::{layout::Rect, widgets::Sparkline, Frame};
///
/// # use ratatui::{prelude::*, widgets::*};
/// # fn ui(frame: &mut Frame) {
/// # let area = Rect::default();
/// let sparkline = Sparkline::default().data(&[1, 2, 3]);
@@ -220,13 +209,10 @@ impl Sparkline<'_> {
#[cfg(test)]
mod tests {
use ratatui_core::buffer::Cell;
use strum::ParseError;
use super::*;
use crate::{
buffer::Cell,
style::{Color, Modifier, Stylize},
};
#[test]
fn render_direction_to_string() {

View File

@@ -1,10 +1,4 @@
use crate::{
buffer::Buffer,
layout::Rect,
style::{Style, Styled},
text::Text,
widgets::WidgetRef,
};
use ratatui_core::{prelude::*, style::Styled, widgets::WidgetRef};
/// A [`Cell`] contains the [`Text`] to be displayed in a [`Row`] of a [`Table`].
///
@@ -22,17 +16,13 @@ use crate::{
/// ```rust
/// use std::borrow::Cow;
///
/// use ratatui::{
/// style::Stylize,
/// text::{Line, Span, Text},
/// widgets::Cell,
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// Cell::from("simple string");
/// Cell::from(Span::from("span"));
/// Cell::from(Line::from(vec![
/// Span::from("a vec of "),
/// Span::from("spans").bold(),
/// Span::raw("a vec of "),
/// Span::styled("spans", Style::default().add_modifier(Modifier::BOLD)),
/// ]));
/// Cell::from(Text::from("a text"));
/// Cell::from(Text::from(Cow::Borrowed("hello")));
@@ -42,14 +32,12 @@ use crate::{
/// to set the style of the cell concisely.
///
/// ```rust
/// use ratatui::{style::Stylize, widgets::Cell};
///
/// use ratatui::{prelude::*, widgets::*};
/// Cell::new("Cell 1").red().italic();
/// ```
///
/// [`Row`]: crate::widgets::Row
/// [`Table`]: crate::widgets::Table
/// [`Stylize`]: crate::style::Stylize
/// [`Row`]: super::Row
/// [`Table`]: super::Table
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct Cell<'a> {
content: Text<'a>,
@@ -64,17 +52,12 @@ impl<'a> Cell<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{
/// style::Stylize,
/// text::{Line, Span, Text},
/// widgets::Cell,
/// };
///
/// # use ratatui::{prelude::*, widgets::*};
/// Cell::new("simple string");
/// Cell::new(Span::from("span"));
/// Cell::new(Line::from(vec![
/// Span::raw("a vec of "),
/// Span::from("spans").bold(),
/// Span::styled("spans", Style::default().add_modifier(Modifier::BOLD)),
/// ]));
/// Cell::new(Text::from("a text"));
/// ```
@@ -97,17 +80,12 @@ impl<'a> Cell<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{
/// style::Stylize,
/// text::{Line, Span, Text},
/// widgets::Cell,
/// };
///
/// # use ratatui::{prelude::*, widgets::*};
/// Cell::default().content("simple string");
/// Cell::default().content(Span::from("span"));
/// Cell::default().content(Line::from(vec![
/// Span::raw("a vec of "),
/// Span::from("spans").bold(),
/// Span::styled("spans", Style::new().bold()),
/// ]));
/// Cell::default().content(Text::from("a text"));
/// ```
@@ -133,11 +111,7 @@ impl<'a> Cell<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{
/// style::{Style, Stylize},
/// widgets::Cell,
/// };
///
/// # use ratatui::{prelude::*, widgets::*};
/// Cell::new("Cell 1").style(Style::new().red().italic());
/// ```
///
@@ -145,14 +119,11 @@ impl<'a> Cell<'a> {
/// the [`Stylize`] trait to set the style of the widget more concisely.
///
/// ```rust
/// use ratatui::{style::Stylize, widgets::Cell};
///
/// # use ratatui::{prelude::*, widgets::*};
/// Cell::new("Cell 1").red().italic();
/// ```
///
/// [`Row`]: crate::widgets::Row
/// [`Color`]: crate::style::Color
/// [`Stylize`]: crate::style::Stylize
/// [`Row`]: super::Row
#[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();
@@ -194,7 +165,6 @@ impl<'a> Styled for Cell<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::style::{Color, Modifier, Stylize};
#[test]
fn new() {

View File

@@ -1,7 +1,6 @@
use crate::{
style::{Style, Styled},
widgets::table::Cell,
};
use ratatui_core::{prelude::*, style::Styled};
use super::Cell;
/// A single row of data to be displayed in a [`Table`] widget.
///
@@ -18,7 +17,7 @@ use crate::{
/// You can create `Row`s from simple strings.
///
/// ```rust
/// use ratatui::widgets::Row;
/// use ratatui::{prelude::*, widgets::*};
///
/// Row::new(vec!["Cell1", "Cell2", "Cell3"]);
/// ```
@@ -26,14 +25,11 @@ use crate::{
/// If you need a bit more control over individual cells, you can explicitly create [`Cell`]s:
///
/// ```rust
/// use ratatui::{
/// style::Stylize,
/// widgets::{Cell, Row},
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// Row::new(vec![
/// Cell::from("Cell1"),
/// Cell::from("Cell2").red().italic(),
/// Cell::from("Cell2").style(Style::default().fg(Color::Yellow)),
/// ]);
/// ```
///
@@ -42,7 +38,7 @@ use crate::{
/// ```rust
/// use std::borrow::Cow;
///
/// use ratatui::widgets::{Cell, Row};
/// use ratatui::{prelude::*, widgets::*};
///
/// Row::new(vec![
/// Cow::Borrowed("hello"),
@@ -62,15 +58,12 @@ use crate::{
/// to set the style of the row concisely.
///
/// ```rust
/// use ratatui::{style::Stylize, widgets::Row};
///
/// use ratatui::{prelude::*, widgets::*};
/// let cells = vec!["Cell1", "Cell2", "Cell3"];
/// Row::new(cells).red().italic();
/// ```
///
/// [`Table`]: crate::widgets::Table
/// [`Text`]: crate::text::Text
/// [`Stylize`]: crate::style::Stylize
/// [`Table`]: super::Table
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct Row<'a> {
pub(crate) cells: Vec<Cell<'a>>,
@@ -89,8 +82,7 @@ impl<'a> Row<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::{Cell, Row};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let row = Row::new(vec!["Cell 1", "Cell 2", "Cell 3"]);
/// let row = Row::new(vec![
/// Cell::new("Cell 1"),
@@ -120,8 +112,7 @@ impl<'a> Row<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::{Cell, Row};
///
/// # use ratatui::{prelude::*, widgets::*};
/// let row = Row::default().cells(vec!["Cell 1", "Cell 2", "Cell 3"]);
/// let row = Row::default().cells(vec![
/// Cell::new("Cell 1"),
@@ -150,8 +141,7 @@ impl<'a> Row<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::Row;
///
/// # use ratatui::{prelude::*, widgets::*};
/// let cells = vec!["Cell 1\nline 2", "Cell 2", "Cell 3"];
/// let row = Row::new(cells).height(2);
/// ```
@@ -170,9 +160,8 @@ impl<'a> Row<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::Row;
/// let cells = vec!["Cell 1", "Cell 2", "Cell 3"];
///
/// # use ratatui::{prelude::*, widgets::*};
/// # 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"]
@@ -190,9 +179,8 @@ impl<'a> Row<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::widgets::Row;
///
/// let cells = vec!["Cell 1", "Cell 2", "Cell 3"];
/// # use ratatui::{prelude::*, widgets::*};
/// # 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"]
@@ -214,10 +202,7 @@ impl<'a> Row<'a> {
/// # Examples
///
/// ```rust
/// use ratatui::{
/// style::{Style, Stylize},
/// widgets::Row,
/// };
/// # use ratatui::{prelude::*, widgets::*};
/// let cells = vec!["Cell 1", "Cell 2", "Cell 3"];
/// let row = Row::new(cells).style(Style::new().red().italic());
/// ```
@@ -226,15 +211,10 @@ impl<'a> Row<'a> {
/// the [`Stylize`] trait to set the style of the widget more concisely.
///
/// ```rust
/// use ratatui::{style::Stylize, widgets::Row};
///
/// # use ratatui::{prelude::*, widgets::*};
/// 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();
@@ -278,7 +258,6 @@ mod tests {
use std::vec;
use super::*;
use crate::style::{Color, Modifier, Stylize};
#[test]
fn new() {

View File

@@ -0,0 +1,396 @@
/// 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.
///
/// 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
///
/// [`offset`]: TableState::offset()
/// [`selected`]: TableState::selected()
///
/// 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
/// how to handle state.
///
/// [Examples]: https://github.com/ratatui/ratatui/blob/master/examples/README.md
///
/// # Example
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// # 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 table = Table::new(rows, widths).widths(widths);
///
/// // Note: TableState should be stored in your application state (not constructed in your render
/// // method) so that the selected row is preserved across renders
/// 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)
///
/// frame.render_stateful_widget(table, area, &mut table_state);
/// # }
/// ```
///
/// Note that if [`Table::widths`] is not called before rendering, the rendered columns will have
/// equal width.
///
/// [`Table`]: crate::Table
/// [`Table::widths`]: crate::Table::widths
/// [`Frame::render_stateful_widget`]: crate::Frame::render_stateful_widget
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TableState {
pub(crate) offset: usize,
pub(crate) selected: Option<usize>,
}
impl TableState {
/// Creates a new [`TableState`]
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// let state = TableState::new();
/// ```
pub const fn new() -> Self {
Self {
offset: 0,
selected: None,
}
}
/// Sets the index of the first row to be displayed
///
/// 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_offset(1);
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn with_offset(mut self, offset: usize) -> Self {
self.offset = offset;
self
}
/// Sets the index of the selected row
///
/// This is a fluent setter method which must be chained or used as it consumes self
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// let state = TableState::new().with_selected(Some(1));
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
pub fn with_selected<T>(mut self, selected: T) -> Self
where
T: Into<Option<usize>>,
{
self.selected = selected.into();
self
}
/// Index of the first row to be displayed
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// let state = TableState::new();
/// assert_eq!(state.offset(), 0);
/// ```
pub const fn offset(&self) -> usize {
self.offset
}
/// Mutable reference to the index of the first row to be displayed
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = TableState::default();
/// *state.offset_mut() = 1;
/// ```
pub fn offset_mut(&mut self) -> &mut usize {
&mut self.offset
}
/// Index of the selected row
///
/// Returns `None` if no row is selected
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// let state = TableState::new();
/// assert_eq!(state.selected(), None);
/// ```
pub const fn selected(&self) -> Option<usize> {
self.selected
}
/// Mutable reference to the index of the selected row
///
/// Returns `None` if no row is selected
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = TableState::default();
/// *state.selected_mut() = Some(1);
/// ```
pub fn selected_mut(&mut self) -> &mut Option<usize> {
&mut self.selected
}
/// Sets the index of the selected row
///
/// Set to `None` if no row is selected. This will also reset the offset to `0`.
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = TableState::default();
/// state.select(Some(1));
/// ```
pub fn select(&mut self, index: Option<usize>) {
self.selected = index;
if index.is_none() {
self.offset = 0;
}
}
/// 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
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = TableState::default();
/// state.select_next();
/// ```
pub fn select_next(&mut self) {
let next = self.selected.map_or(0, |i| i.saturating_add(1));
self.select(Some(next));
}
/// Selects the previous item or the last 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
/// `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();
/// ```
pub fn select_previous(&mut self) {
let previous = self.selected.map_or(usize::MAX, |i| i.saturating_sub(1));
self.select(Some(previous));
}
/// Selects the first item
///
/// 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
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = TableState::default();
/// state.select_first();
/// ```
pub fn select_first(&mut self) {
self.select(Some(0));
}
/// Selects the last item
///
/// 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
///
/// # 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));
}
/// 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.
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = TableState::default();
/// state.scroll_down_by(4);
/// ```
pub fn scroll_down_by(&mut self, amount: u16) {
let selected = self.selected.unwrap_or_default();
self.select(Some(selected.saturating_add(amount as usize)));
}
/// Scrolls up by a specified `amount` in the table.
///
/// 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 item in the table will be selected.
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// 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)));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new() {
let state = TableState::new();
assert_eq!(state.offset, 0);
assert_eq!(state.selected, None);
}
#[test]
fn with_offset() {
let state = TableState::new().with_offset(1);
assert_eq!(state.offset, 1);
}
#[test]
fn with_selected() {
let state = TableState::new().with_selected(Some(1));
assert_eq!(state.selected, Some(1));
}
#[test]
fn offset() {
let state = TableState::new();
assert_eq!(state.offset(), 0);
}
#[test]
fn offset_mut() {
let mut state = TableState::new();
*state.offset_mut() = 1;
assert_eq!(state.offset, 1);
}
#[test]
fn selected() {
let state = TableState::new();
assert_eq!(state.selected(), None);
}
#[test]
fn selected_mut() {
let mut state = TableState::new();
*state.selected_mut() = Some(1);
assert_eq!(state.selected, Some(1));
}
#[test]
fn select() {
let mut state = TableState::new();
state.select(Some(1));
assert_eq!(state.selected, Some(1));
}
#[test]
fn select_none() {
let mut state = TableState::new().with_selected(Some(1));
state.select(None);
assert_eq!(state.selected, None);
}
#[test]
fn test_table_state_navigation() {
let mut state = TableState::default();
state.select_first();
assert_eq!(state.selected, Some(0));
state.select_previous(); // should not go below 0
assert_eq!(state.selected, Some(0));
state.select_next();
assert_eq!(state.selected, Some(1));
state.select_previous();
assert_eq!(state.selected, Some(0));
state.select_last();
assert_eq!(state.selected, Some(usize::MAX));
state.select_next(); // should not go above usize::MAX
assert_eq!(state.selected, Some(usize::MAX));
state.select_previous();
assert_eq!(state.selected, Some(usize::MAX - 1));
state.select_next();
assert_eq!(state.selected, Some(usize::MAX));
let mut state = TableState::default();
state.select_next();
assert_eq!(state.selected, Some(0));
let mut state = TableState::default();
state.select_previous();
assert_eq!(state.selected, Some(usize::MAX));
let mut state = TableState::default();
state.select(Some(2));
state.scroll_down_by(4);
assert_eq!(state.selected, Some(6));
let mut state = TableState::default();
state.scroll_up_by(3);
assert_eq!(state.selected, Some(0));
state.select(Some(6));
state.scroll_up_by(4);
assert_eq!(state.selected, Some(2));
state.scroll_up_by(4);
assert_eq!(state.selected, Some(0));
}
}

View File

@@ -1,13 +1,6 @@
use itertools::Itertools;
use ratatui_core::{prelude::*, style::Styled, widgets::WidgetRef};
use crate::{
buffer::Buffer,
layout::Rect,
style::{Modifier, Style, Styled},
symbols::{self},
text::{Line, Span},
widgets::{block::BlockExt, Block, Widget, WidgetRef},
};
use crate::{block::BlockExt, Block};
const DEFAULT_HIGHLIGHT_STYLE: Style = Style::new().add_modifier(Modifier::REVERSED);
@@ -23,11 +16,7 @@ const DEFAULT_HIGHLIGHT_STYLE: Style = Style::new().add_modifier(Modifier::REVER
/// # Example
///
/// ```
/// use ratatui::{
/// style::{Style, Stylize},
/// symbols,
/// widgets::{Block, Tabs},
/// };
/// use ratatui::{prelude::*, widgets::*};
///
/// Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"])
/// .block(Block::bordered().title("Tabs"))
@@ -46,14 +35,14 @@ const DEFAULT_HIGHLIGHT_STYLE: Style = Style::new().add_modifier(Modifier::REVER
///
/// (0..5).map(|i| format!("Tab{i}")).collect::<Tabs>();
/// ```
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[derive(Debug, Default, 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: Option<usize>,
selected: usize,
/// The style used to draw the text
style: Style,
/// Style to apply to the selected item
@@ -66,30 +55,6 @@ 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.
///
@@ -112,15 +77,13 @@ impl<'a> Tabs<'a> {
///
/// Basic titles.
/// ```
/// use ratatui::widgets::Tabs;
///
/// # use ratatui::{prelude::*, widgets::Tabs};
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]);
/// ```
///
/// Styled titles
/// ```
/// use ratatui::{style::Stylize, widgets::Tabs};
///
/// # use ratatui::{prelude::*, widgets::Tabs};
/// let tabs = Tabs::new(vec!["Tab 1".red(), "Tab 2".blue()]);
/// ```
pub fn new<Iter>(titles: Iter) -> Self
@@ -128,12 +91,10 @@ 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,
selected,
titles: titles.into_iter().map(Into::into).collect(),
selected: 0,
style: Style::default(),
highlight_style: DEFAULT_HIGHLIGHT_STYLE,
divider: Span::raw(symbols::line::VERTICAL),
@@ -142,48 +103,6 @@ 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 {
@@ -195,27 +114,9 @@ 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 fn select<T: Into<Option<usize>>>(mut self, selected: T) -> Self {
self.selected = selected.into();
pub const fn select(mut self, selected: usize) -> Self {
self.selected = selected;
self
}
@@ -227,8 +128,6 @@ 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();
@@ -242,8 +141,6 @@ 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
@@ -257,14 +154,12 @@ impl<'a> Tabs<'a> {
///
/// Use a dot (`•`) as separator.
/// ```
/// use ratatui::{symbols, widgets::Tabs};
///
/// # use ratatui::{prelude::*, widgets::Tabs, symbols};
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).divider(symbols::DOT);
/// ```
/// Use dash (`-`) as separator.
/// ```
/// use ratatui::widgets::Tabs;
///
/// # use ratatui::{prelude::*, 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"]
@@ -284,14 +179,12 @@ impl<'a> Tabs<'a> {
///
/// A space on either side of the tabs.
/// ```
/// use ratatui::widgets::Tabs;
///
/// # use ratatui::{prelude::*, widgets::Tabs};
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).padding(" ", " ");
/// ```
/// Nothing on either side of the tabs.
/// ```
/// use ratatui::widgets::Tabs;
///
/// # use ratatui::{prelude::*, 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"]
@@ -313,8 +206,7 @@ impl<'a> Tabs<'a> {
///
/// An arrow on the left of tabs.
/// ```
/// use ratatui::widgets::Tabs;
///
/// # use ratatui::{prelude::*, 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"]
@@ -334,8 +226,7 @@ impl<'a> Tabs<'a> {
///
/// An arrow on the right of tabs.
/// ```
/// use ratatui::widgets::Tabs;
///
/// # use ratatui::{prelude::*, 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"]
@@ -401,7 +292,7 @@ impl Tabs<'_> {
// Title
let pos = buf.set_line(x, tabs_area.top(), title, remaining_width);
if Some(i) == self.selected {
if i == self.selected {
buf.set_style(
Rect {
x,
@@ -444,7 +335,6 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::style::{Color, Stylize};
#[test]
fn new() {
@@ -460,7 +350,7 @@ mod tests {
Line::from("Tab3"),
Line::from("Tab4"),
],
selected: Some(0),
selected: 0,
style: Style::default(),
highlight_style: DEFAULT_HIGHLIGHT_STYLE,
divider: Span::raw(symbols::line::VERTICAL),
@@ -470,37 +360,6 @@ 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"]);
@@ -529,7 +388,7 @@ mod tests {
}
#[test]
fn render_new() {
fn render_default() {
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]);
let mut expected = Buffer::with_lines([" Tab1 │ Tab2 │ Tab3 │ Tab4 "]);
// first tab selected
@@ -609,10 +468,6 @@ 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]

386
ratatui/Cargo.toml Normal file
View File

@@ -0,0 +1,386 @@
[package]
name = "ratatui"
version = "0.28.1" # crate version
authors = ["Florian Dehau <work@fdehau.com>", "The Ratatui Developers"]
description = "A library that's all about cooking up terminal user interfaces"
documentation = "https://docs.rs/ratatui/latest/ratatui/"
repository = "https://github.com/ratatui/ratatui"
homepage = "https://ratatui.rs"
keywords = ["tui", "terminal", "dashboard"]
categories = ["command-line-interface"]
readme = "README.md"
license = "MIT"
exclude = [
"assets/*",
".github",
"Makefile.toml",
"CONTRIBUTING.md",
"*.log",
"tags",
]
edition = "2021"
rust-version = "1.74.0"
[features]
#! The crate provides a set of optional features that can be enabled in your `cargo.toml` file.
#!
## By default, we enable the crossterm backend as this is a reasonable choice for most applications
## as it is supported on Linux/Mac/Windows systems. We also enable the `underline-color` feature
## which allows you to set the underline color of text.
default = ["crossterm", "underline-color"]
#! Generally an application will only use one backend, so you should only enable one of the following features:
## enables the [`CrosstermBackend`](backend::CrosstermBackend) backend and adds a dependency on [`crossterm`].
crossterm = ["dep:ratatui-crossterm", "dep:crossterm"]
## enables the [`TermionBackend`](backend::TermionBackend) backend and adds a dependency on [`termion`].
# termion = ["dep:ratatui-termion", "dep:termion"]
# ## enables the [`TermwizBackend`](backend::TermwizBackend) backend and adds a dependency on [`termwiz`].
# termwiz = ["dep:ratatui-termwiz"]
#! The following optional features are available for all backends:
## enables serialization and deserialization of style and color types using the [`serde`] crate.
## This is useful if you want to save themes to a file.
serde = ["dep:serde", "bitflags/serde", "compact_str/serde"]
## enables the [`border!`] macro.
macros = []
## enables conversions from colors in the [`palette`] crate to [`Color`](crate::style::Color).
palette = ["dep:palette"]
## enables all widgets.
all-widgets = ["widget-calendar"]
#! Widgets that add dependencies are gated behind feature flags to prevent unused transitive
#! dependencies. The available features are:
## enables the [`calendar`](widgets::calendar) widget module and adds a dependency on [`time`].
widget-calendar = ["ratatui-widgets/widget-calendar", "dep:time"]
#! The following optional features are only available for some backends:
## enables the backend code that sets the underline color.
## Underline color is only supported by the [`CrosstermBackend`](backend::CrosstermBackend) backend,
## and is not supported on Windows 7.
# underline-color = ["ratatui-crossterm/underline-color"]
underline-color = ["ratatui-core/underline-color"]
#! The following features are unstable and may change in the future:
## Enable all unstable features.
unstable = [
"unstable-rendered-line-info",
"unstable-widget-ref",
"unstable-backend-writer",
]
## Enables the [`Paragraph::line_count`](widgets::Paragraph::line_count)
## [`Paragraph::line_width`](widgets::Paragraph::line_width) methods
## which are experimental and may change in the future.
## See [Issue 293](https://github.com/ratatui/ratatui/issues/293) for more details.
unstable-rendered-line-info = []
## Enables the [`WidgetRef`](widgets::WidgetRef) and [`StatefulWidgetRef`](widgets::StatefulWidgetRef) traits which are experimental and may change in
## the future.
unstable-widget-ref = []
## Enables getting access to backends' writers.
unstable-backend-writer = []
[dependencies]
ratatui-widgets = { workspace = true }
ratatui-core = { workspace = true }
ratatui-crossterm = { workspace = true, optional = true }
# ratatui-termwiz = { workspace = true, optional = true }
bitflags = "2.3"
cassowary = "0.3"
crossterm = { workspace = true, optional = true }
compact_str = "0.8.0"
document-features = { version = "0.2.7", optional = true }
instability = "0.3.1"
itertools = "0.13"
lru = "0.12.0"
paste = "1.0.2"
palette = { version = "0.7.6", optional = true }
serde = { version = "1", optional = true, features = ["derive"] }
strum = { version = "0.26.3", features = ["derive"] }
unicode-segmentation = "1.10"
unicode-truncate = "1"
unicode-width = "=0.1.13"
time = { workspace = true, optional = true }
# [target.'cfg(not(windows))'.dependencies]
# # termion is not supported on Windows
# ratatui-termion = { workspace = true, optional = true }
# termion = { workspace = true, optional = true }
[dev-dependencies]
argh = "0.1.12"
color-eyre = "0.6.2"
criterion = { version = "0.5.1", features = ["html_reports"] }
fakeit = "1.1"
font8x8 = "0.3.1"
futures = "0.3.30"
indoc = "2"
octocrab = "0.40.0"
pretty_assertions = "1.4.0"
rand = "0.8.5"
rand_chacha = "0.3.1"
rstest = "0.22.0"
serde_json = "1.0.109"
tokio = { version = "1.39.2", features = [
"rt",
"macros",
"time",
"rt-multi-thread",
] }
tracing = "0.1.40"
tracing-appender = "0.2.3"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
cargo = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
cast_possible_truncation = "allow"
cast_possible_wrap = "allow"
cast_precision_loss = "allow"
cast_sign_loss = "allow"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
module_name_repetitions = "allow"
must_use_candidate = "allow"
# we often split up a module into multiple files with the main type in a file named after the
# module, so we want to allow this pattern
module_inception = "allow"
# nursery or restricted
as_underscore = "warn"
deref_by_slicing = "warn"
else_if_without_else = "warn"
empty_line_after_doc_comments = "warn"
equatable_if_let = "warn"
fn_to_numeric_cast_any = "warn"
format_push_string = "warn"
map_err_ignore = "warn"
missing_const_for_fn = "warn"
mixed_read_write_in_expression = "warn"
mod_module_files = "warn"
needless_pass_by_ref_mut = "warn"
needless_raw_strings = "warn"
or_fun_call = "warn"
redundant_type_annotations = "warn"
rest_pat_in_fully_bound_structs = "warn"
string_lit_chars_any = "warn"
string_slice = "warn"
string_to_string = "warn"
unnecessary_self_imports = "warn"
use_self = "warn"
[package.metadata.docs.rs]
all-features = true
# see https://doc.rust-lang.org/nightly/rustdoc/scraped-examples.html
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
rustdoc-args = ["--cfg", "docsrs"]
# Improve benchmark consistency
[profile.bench]
codegen-units = 1
lto = true
[lib]
bench = false
[[bench]]
name = "main"
harness = false
[[example]]
name = "async"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "barchart"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "barchart-grouped"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "block"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "calendar"
required-features = ["crossterm", "widget-calendar"]
doc-scrape-examples = true
[[example]]
name = "canvas"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "chart"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "colors"
required-features = ["crossterm"]
# this example is a bit verbose, so we don't want to include it in the docs
doc-scrape-examples = false
[[example]]
name = "colors_rgb"
required-features = ["crossterm", "palette"]
doc-scrape-examples = true
[[example]]
name = "constraint-explorer"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "constraints"
required-features = ["crossterm"]
doc-scrape-examples = false
[[example]]
name = "custom_widget"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "demo"
# this runs for all of the terminal backends, so it can't be built using --all-features or scraped
doc-scrape-examples = false
[[example]]
name = "demo2"
required-features = ["crossterm", "palette", "widget-calendar"]
doc-scrape-examples = true
[[example]]
name = "docsrs"
required-features = ["crossterm"]
doc-scrape-examples = false
[[example]]
name = "flex"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "gauge"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "hello_world"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "inline"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "layout"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "line_gauge"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "hyperlink"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "list"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "minimal"
required-features = ["crossterm"]
# prefer to show the more featureful examples in the docs
doc-scrape-examples = false
[[example]]
name = "modifiers"
required-features = ["crossterm"]
# this example is a bit verbose, so we don't want to include it in the docs
doc-scrape-examples = false
[[example]]
name = "panic"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "paragraph"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "popup"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "ratatui-logo"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "scrollbar"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "sparkline"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "table"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "tabs"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "tracing"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "user_input"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "widget_impl"
required-features = ["crossterm", "unstable-widget-ref"]
doc-scrape-examples = true
[[test]]
name = "state_serde"
required-features = ["serde"]

View File

@@ -7,7 +7,6 @@ pub mod main {
pub mod paragraph;
pub mod rect;
pub mod sparkline;
pub mod table;
}
pub use main::*;
@@ -19,6 +18,5 @@ criterion::criterion_main!(
list::benches,
paragraph::benches,
rect::benches,
sparkline::benches,
table::benches,
sparkline::benches
);

View File

@@ -2,7 +2,8 @@ use criterion::{criterion_group, Bencher, BenchmarkId, Criterion};
use rand::Rng;
use ratatui::{
buffer::Buffer,
layout::{Direction, Rect},
layout::Rect,
prelude::Direction,
widgets::{Bar, BarChart, BarGroup, Widget},
};

View File

@@ -1,17 +1,19 @@
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, diff);
criterion::criterion_group!(benches, empty, filled, with_lines);
const fn rect(size: u16) -> Rect {
Rect::new(0, 0, size, size)
Rect {
x: 0,
y: 0,
width: size,
height: size,
}
}
fn empty(c: &mut Criterion) {
@@ -61,37 +63,3 @@ 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
}

Some files were not shown because too many files have changed in this diff Show More