Compare commits
1 Commits
jm/buffer-
...
jm/modular
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26163effdf |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: bnjbvr/cargo-machete@v0.7.0
|
||||
- uses: bnjbvr/cargo-machete@v0.6.2
|
||||
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
376
Cargo.toml
376
Cargo.toml
@@ -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"]
|
||||
|
||||
@@ -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);
|
||||
@@ -11,7 +11,6 @@ allow = [
|
||||
"MIT",
|
||||
"Unicode-DFS-2016",
|
||||
"WTFPL",
|
||||
"Zlib",
|
||||
]
|
||||
|
||||
[advisories]
|
||||
|
||||
@@ -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
50
ratatui-core/Cargo.toml
Normal 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"] }
|
||||
@@ -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)]
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
@@ -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() {
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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.
|
||||
///
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
143
ratatui-core/src/layout/rect/iter.rs
Normal file
143
ratatui-core/src/layout/rect/iter.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#![warn(missing_docs)]
|
||||
use std::fmt;
|
||||
|
||||
use crate::layout::Rect;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A simple size struct
|
||||
///
|
||||
41
ratatui-core/src/prelude.rs
Normal file
41
ratatui-core/src/prelude.rs
Normal 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,
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
@@ -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
|
||||
///
|
||||
@@ -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,
|
||||
@@ -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`].
|
||||
///
|
||||
@@ -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![
|
||||
@@ -155,7 +155,7 @@ pub enum Marker {
|
||||
}
|
||||
|
||||
pub mod scrollbar {
|
||||
use crate::symbols::{block, line};
|
||||
use super::{block, line};
|
||||
|
||||
/// Scrollbar Set
|
||||
/// ```text
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::symbols::{block, line};
|
||||
use super::{block, line};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub struct Set {
|
||||
@@ -13,7 +13,7 @@
|
||||
//! ```rust,no_run
|
||||
//! use std::io::stdout;
|
||||
//!
|
||||
//! use ratatui::{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;
|
||||
@@ -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();
|
||||
@@ -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(
|
||||
@@ -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
|
||||
@@ -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![
|
||||
@@ -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() {
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
///
|
||||
20
ratatui-crossterm/Cargo.toml
Normal file
20
ratatui-crossterm/Cargo.toml
Normal 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
|
||||
681
ratatui-crossterm/src/lib.rs
Normal file
681
ratatui-crossterm/src/lib.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
9
ratatui-termion/Cargo.toml
Normal file
9
ratatui-termion/Cargo.toml
Normal 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
|
||||
@@ -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::*;
|
||||
7
ratatui-termwiz/Cargo.toml
Normal file
7
ratatui-termwiz/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "ratatui-termwiz"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ratatui-core = { workspace = true }
|
||||
@@ -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 {
|
||||
40
ratatui-widgets/Cargo.toml
Normal file
40
ratatui-widgets/Cargo.toml
Normal 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
|
||||
@@ -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() {
|
||||
@@ -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();
|
||||
@@ -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())
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -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));
|
||||
/// ```
|
||||
@@ -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);
|
||||
@@ -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() {
|
||||
@@ -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
|
||||
@@ -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));
|
||||
@@ -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()
|
||||
@@ -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() {
|
||||
@@ -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)]
|
||||
@@ -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() {
|
||||
@@ -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,
|
||||
@@ -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]);
|
||||
@@ -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() {
|
||||
31
ratatui-widgets/src/lib.rs
Normal file
31
ratatui-widgets/src/lib.rs
Normal 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,
|
||||
};
|
||||
@@ -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() {
|
||||
@@ -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() {
|
||||
@@ -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 {
|
||||
@@ -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() {
|
||||
@@ -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.
|
||||
@@ -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 },
|
||||
@@ -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() {
|
||||
@@ -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() {
|
||||
@@ -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() {
|
||||
@@ -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() {
|
||||
File diff suppressed because it is too large
Load Diff
396
ratatui-widgets/src/table/table_state.rs
Normal file
396
ratatui-widgets/src/table/table_state.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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
386
ratatui/Cargo.toml
Normal 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"]
|
||||
@@ -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
|
||||
);
|
||||
@@ -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},
|
||||
};
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user