Compare commits
63 Commits
v0.26.2-al
...
rect-offse
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ea840d87f | ||
|
|
26af65043e | ||
|
|
07da90a718 | ||
|
|
125ee929ee | ||
|
|
742a5ead06 | ||
|
|
8719608bda | ||
|
|
f6c4e447e6 | ||
|
|
c56f49b9fb | ||
|
|
078e97e4ff | ||
|
|
8e68db9e2f | ||
|
|
541f0f9953 | ||
|
|
88bfb5a430 | ||
|
|
c4ce7e8ff6 | ||
|
|
3be189e3c6 | ||
|
|
5c4efacd1d | ||
|
|
bbb6d65e06 | ||
|
|
fdb14dc7cd | ||
|
|
9b3b23ac14 | ||
|
|
58b6e0be0f | ||
|
|
6e6ba27a12 | ||
|
|
c870a41057 | ||
|
|
a6036ad789 | ||
|
|
060d26b6dc | ||
|
|
a4e84a6a7f | ||
|
|
fcbea9ee68 | ||
|
|
14b24e7585 | ||
|
|
5ed1f43c62 | ||
|
|
c8c7924e0c | ||
|
|
e3afe7c8a1 | ||
|
|
a1f54de7d6 | ||
|
|
b8ea190bf2 | ||
|
|
0de5238ed3 | ||
|
|
df5dddfbc9 | ||
|
|
f1398ae6cb | ||
|
|
525848ff4e | ||
|
|
660c7183c7 | ||
|
|
ab951fae81 | ||
|
|
3cd4369176 | ||
|
|
9bc014d7f1 | ||
|
|
36a0cd56e5 | ||
|
|
b831c5688c | ||
|
|
8195f526cb | ||
|
|
f7f66928a8 | ||
|
|
01418eb7c2 | ||
|
|
8536760e78 | ||
|
|
a558b19c9a | ||
|
|
183c07ef43 | ||
|
|
a13867ffce | ||
|
|
5b00e3aae9 | ||
|
|
38c17e091c | ||
|
|
3834374652 | ||
|
|
27680c05ce | ||
|
|
6fd5f631bb | ||
|
|
37b957c7e1 | ||
|
|
e02f4768ce | ||
|
|
c12bcfefa2 | ||
|
|
94f4547dcf | ||
|
|
3a6b8808ed | ||
|
|
1cff511934 | ||
|
|
b5bdde079e | ||
|
|
654949bb00 | ||
|
|
943c0431d9 | ||
|
|
65e7923753 |
@@ -2,6 +2,12 @@
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.rs]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -5,4 +5,4 @@
|
||||
# https://git-scm.com/docs/gitignore#_pattern_format
|
||||
|
||||
# Maintainers
|
||||
* @orhun @mindoodoo @sayanarijit @joshka @kdheepak @Valentin271
|
||||
* @orhun @mindoodoo @sayanarijit @joshka @kdheepak @Valentin271 @EdJoPaTo
|
||||
|
||||
2
.github/workflows/cd.yml
vendored
2
.github/workflows/cd.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
args: --allow-dirty --token ${{ secrets.CARGO_TOKEN }}
|
||||
|
||||
- name: Generate a changelog
|
||||
uses: orhun/git-cliff-action@v2
|
||||
uses: orhun/git-cliff-action@v3
|
||||
with:
|
||||
config: cliff.toml
|
||||
args: --unreleased --tag ${{ env.NEXT_TAG }} --strip header
|
||||
|
||||
1
.github/workflows/check-pr.yml
vendored
1
.github/workflows/check-pr.yml
vendored
@@ -84,4 +84,3 @@ jobs:
|
||||
echo "Pull request is labeled as 'do not merge'"
|
||||
echo "This workflow fails so that the pull request cannot be merged"
|
||||
exit 1
|
||||
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -91,7 +91,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
toolchain: ["1.70.0", "stable"]
|
||||
toolchain: ["1.74.0", "stable"]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
toolchain: ["1.70.0", "stable"]
|
||||
toolchain: ["1.74.0", "stable"]
|
||||
backend: [crossterm, termion, termwiz]
|
||||
exclude:
|
||||
# termion is not supported on windows
|
||||
|
||||
@@ -53,15 +53,15 @@ This is a quick summary of the sections below:
|
||||
|
||||
[#881]: https://github.com/ratatui-org/ratatui/pull/881
|
||||
|
||||
Previously, constraints would stretch to fill all available space, violating constraints if
|
||||
Previously, constraints would stretch to fill all available space, violating constraints if
|
||||
necessary.
|
||||
|
||||
With v0.26.0, `Flex` modes are introduced and the default is `Flex::Start`, which will align
|
||||
areas associated with constraints to be beginning of the area. With v0.26.0, additionally,
|
||||
`Min` constraints grow to fill excess space. These changes will allow users to build layouts
|
||||
With v0.26.0, `Flex` modes are introduced and the default is `Flex::Start`, which will align
|
||||
areas associated with constraints to be beginning of the area. With v0.26.0, additionally,
|
||||
`Min` constraints grow to fill excess space. These changes will allow users to build layouts
|
||||
more easily.
|
||||
|
||||
With v0.26.0, users will most likely not need to change what constraints they use to create
|
||||
With v0.26.0, users will most likely not need to change what constraints they use to create
|
||||
existing layouts with `Flex::Start`. However, to get old behavior, use `Flex::Legacy`.
|
||||
|
||||
```diff
|
||||
|
||||
@@ -112,7 +112,8 @@ exist to show coverage directly in your editor. E.g.:
|
||||
|
||||
### Documentation
|
||||
|
||||
Here are some guidelines for writing documentation in Ratatui.
|
||||
Here are some guidelines for writing documentation in Ratatui.
|
||||
|
||||
Every public API **must** be documented.
|
||||
|
||||
Keep in mind that Ratatui tends to attract beginner Rust users that may not be familiar with Rust
|
||||
@@ -125,10 +126,9 @@ the concepts pointing to the various methods. Focus on interaction with various
|
||||
enough information that helps understand why you might want something.
|
||||
|
||||
Examples should help users understand a particular usage, not test a feature. They should be as
|
||||
simple as possible.
|
||||
Prefer hiding imports and using wildcards to keep things concise. Some imports may still be shown
|
||||
to demonstrate a particular non-obvious import (e.g. `Stylize` trait to use style methods).
|
||||
Speaking of `Stylize`, you should use it over the more verbose style setters:
|
||||
simple as possible. Prefer hiding imports and using wildcards to keep things concise. Some imports
|
||||
may still be shown to demonstrate a particular non-obvious import (e.g. `Stylize` trait to use style
|
||||
methods). Speaking of `Stylize`, you should use it over the more verbose style setters:
|
||||
|
||||
```rust
|
||||
let style = Style::new().red().bold();
|
||||
@@ -138,7 +138,7 @@ let style = Style::default().fg(Color::Red).add_modifier(Modifiers::BOLD);
|
||||
|
||||
#### Format
|
||||
|
||||
- First line is summary, second is blank, third onward is more detail
|
||||
- First line is summary, second is blank, third onward is more detail
|
||||
|
||||
```rust
|
||||
/// Summary
|
||||
@@ -148,10 +148,10 @@ let style = Style::default().fg(Color::Red).add_modifier(Modifiers::BOLD);
|
||||
fn foo() {}
|
||||
```
|
||||
|
||||
- Max line length is 100 characters
|
||||
- Max line length is 100 characters
|
||||
See [vscode rewrap extension](https://marketplace.visualstudio.com/items?itemName=stkb.rewrap)
|
||||
|
||||
- Doc comments are above macros
|
||||
- Doc comments are above macros
|
||||
i.e.
|
||||
|
||||
```rust
|
||||
@@ -160,7 +160,7 @@ i.e.
|
||||
struct Foo {}
|
||||
```
|
||||
|
||||
- Code items should be between backticks
|
||||
- Code items should be between backticks
|
||||
i.e. ``[`Block`]``, **NOT** ``[Block]``
|
||||
|
||||
### Deprecation notice
|
||||
|
||||
37
Cargo.toml
37
Cargo.toml
@@ -18,7 +18,7 @@ exclude = [
|
||||
]
|
||||
autoexamples = true
|
||||
edition = "2021"
|
||||
rust-version = "1.70.0"
|
||||
rust-version = "1.74.0"
|
||||
|
||||
[badges]
|
||||
|
||||
@@ -51,7 +51,7 @@ cargo-husky = { version = "1.5.0", default-features = false, features = [
|
||||
] }
|
||||
color-eyre = "0.6.2"
|
||||
criterion = { version = "0.5.1", features = ["html_reports"] }
|
||||
derive_builder = "0.13.0"
|
||||
derive_builder = "0.20.0"
|
||||
fakeit = "1.1"
|
||||
font8x8 = "0.3.1"
|
||||
palette = "0.7.3"
|
||||
@@ -61,6 +61,39 @@ rand_chacha = "0.3.1"
|
||||
rstest = "0.18.2"
|
||||
serde_json = "1.0.109"
|
||||
|
||||
[lints.rust]
|
||||
unsafe_code = "forbid"
|
||||
[lints.clippy]
|
||||
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"
|
||||
wildcard_imports = "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_raw_strings = "warn"
|
||||
redundant_type_annotations = "warn"
|
||||
rest_pat_in_fully_bound_structs = "warn"
|
||||
string_lit_chars_any = "warn"
|
||||
string_to_string = "warn"
|
||||
use_self = "warn"
|
||||
|
||||
[features]
|
||||
#! The crate provides a set of optional features that can be enabled in your `cargo.toml` file.
|
||||
#!
|
||||
|
||||
7
FUNDING.json
Normal file
7
FUNDING.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"drips": {
|
||||
"ethereum": {
|
||||
"ownedBy": "0x6053C8984f4F214Ad12c4653F28514E1E09213B5"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ on_success = "job:doc" # so that we don't open the browser at each change
|
||||
command = [
|
||||
"cargo", "llvm-cov",
|
||||
"--lcov", "--output-path", "target/lcov.info",
|
||||
"--all-features",
|
||||
"--all-features",
|
||||
"--color", "always",
|
||||
]
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use ratatui::{
|
||||
};
|
||||
|
||||
/// Benchmark for rendering a barchart.
|
||||
pub fn barchart(c: &mut Criterion) {
|
||||
fn barchart(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("barchart");
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
@@ -66,7 +66,7 @@ fn render(bencher: &mut Bencher, barchart: &BarChart) {
|
||||
bench_barchart.render(buffer.area, &mut buffer);
|
||||
},
|
||||
criterion::BatchSize::LargeInput,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
criterion_group!(benches, barchart);
|
||||
|
||||
@@ -10,10 +10,10 @@ use ratatui::{
|
||||
};
|
||||
|
||||
/// Benchmark for rendering a block.
|
||||
pub fn block(c: &mut Criterion) {
|
||||
fn block(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("block");
|
||||
|
||||
for buffer_size in &[
|
||||
for buffer_size in [
|
||||
Rect::new(0, 0, 100, 50), // vertically split screen
|
||||
Rect::new(0, 0, 200, 50), // 1080p fullscreen with medium font
|
||||
Rect::new(0, 0, 256, 256), // Max sized area
|
||||
@@ -47,8 +47,8 @@ pub fn block(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
/// render the block into a buffer of the given `size`
|
||||
fn render(bencher: &mut Bencher, block: &Block, size: &Rect) {
|
||||
let mut buffer = Buffer::empty(*size);
|
||||
fn render(bencher: &mut Bencher, block: &Block, size: Rect) {
|
||||
let mut buffer = Buffer::empty(size);
|
||||
// We use `iter_batched` to clone the value in the setup function.
|
||||
// See https://github.com/ratatui-org/ratatui/pull/377.
|
||||
bencher.iter_batched(
|
||||
@@ -57,7 +57,7 @@ fn render(bencher: &mut Bencher, block: &Block, size: &Rect) {
|
||||
bench_block.render(buffer.area, &mut buffer);
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
criterion_group!(benches, block);
|
||||
|
||||
@@ -7,7 +7,7 @@ use ratatui::{
|
||||
|
||||
/// Benchmark for rendering a list.
|
||||
/// It only benchmarks the render with a different amount of items.
|
||||
pub fn list(c: &mut Criterion) {
|
||||
fn list(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("list");
|
||||
|
||||
for line_count in [64, 2048, 16384] {
|
||||
@@ -33,7 +33,7 @@ pub fn list(c: &mut Criterion) {
|
||||
ListState::default()
|
||||
.with_offset(line_count / 2)
|
||||
.with_selected(Some(line_count / 2)),
|
||||
)
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -52,7 +52,7 @@ fn render(bencher: &mut Bencher, list: &List) {
|
||||
Widget::render(bench_list, buffer.area, &mut buffer);
|
||||
},
|
||||
BatchSize::LargeInput,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// render the list into a common size buffer with a state
|
||||
@@ -66,7 +66,7 @@ fn render_stateful(bencher: &mut Bencher, list: &List, mut state: ListState) {
|
||||
StatefulWidget::render(bench_list, buffer.area, &mut buffer, &mut state);
|
||||
},
|
||||
BatchSize::LargeInput,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
criterion_group!(benches, list);
|
||||
|
||||
@@ -17,15 +17,15 @@ const WRAP_WIDTH: u16 = 100;
|
||||
/// Benchmark for rendering a paragraph with a given number of lines. The design of this benchmark
|
||||
/// allows comparison of the performance of rendering a paragraph with different numbers of lines.
|
||||
/// as well as comparing with the various settings on the scroll and wrap features.
|
||||
pub fn paragraph(c: &mut Criterion) {
|
||||
fn paragraph(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("paragraph");
|
||||
for &line_count in [64, 2048, MAX_SCROLL_OFFSET].iter() {
|
||||
for line_count in [64, 2048, MAX_SCROLL_OFFSET] {
|
||||
let lines = random_lines(line_count);
|
||||
let lines = lines.as_str();
|
||||
|
||||
// benchmark that measures the overhead of creating a paragraph separately from rendering
|
||||
group.bench_with_input(BenchmarkId::new("new", line_count), lines, |b, lines| {
|
||||
b.iter(|| Paragraph::new(black_box(lines)))
|
||||
b.iter(|| Paragraph::new(black_box(lines)));
|
||||
});
|
||||
|
||||
// render the paragraph with no scroll
|
||||
@@ -38,14 +38,14 @@ pub fn paragraph(c: &mut Criterion) {
|
||||
// scroll the paragraph by half the number of lines and render
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("render_scroll_half", line_count),
|
||||
&Paragraph::new(lines).scroll((0u16, line_count / 2)),
|
||||
&Paragraph::new(lines).scroll((0, line_count / 2)),
|
||||
|bencher, paragraph| render(bencher, paragraph, NO_WRAP_WIDTH),
|
||||
);
|
||||
|
||||
// scroll the paragraph by the full number of lines and render
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("render_scroll_full", line_count),
|
||||
&Paragraph::new(lines).scroll((0u16, line_count)),
|
||||
&Paragraph::new(lines).scroll((0, line_count)),
|
||||
|bencher, paragraph| render(bencher, paragraph, NO_WRAP_WIDTH),
|
||||
);
|
||||
|
||||
@@ -61,7 +61,7 @@ pub fn paragraph(c: &mut Criterion) {
|
||||
BenchmarkId::new("render_wrap_scroll_full", line_count),
|
||||
&Paragraph::new(lines)
|
||||
.wrap(Wrap { trim: false })
|
||||
.scroll((0u16, line_count)),
|
||||
.scroll((0, line_count)),
|
||||
|bencher, paragraph| render(bencher, paragraph, WRAP_WIDTH),
|
||||
);
|
||||
}
|
||||
@@ -79,7 +79,7 @@ fn render(bencher: &mut Bencher, paragraph: &Paragraph, width: u16) {
|
||||
bench_paragraph.render(buffer.area, &mut buffer);
|
||||
},
|
||||
BatchSize::LargeInput,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a string with the given number of lines filled with nonsense words
|
||||
@@ -87,7 +87,7 @@ fn render(bencher: &mut Bencher, paragraph: &Paragraph, width: u16) {
|
||||
/// English language has about 5.1 average characters per word so including the space between words
|
||||
/// this should emit around 200 characters per paragraph on average.
|
||||
fn random_lines(count: u16) -> String {
|
||||
let count = count as i64;
|
||||
let count = i64::from(count);
|
||||
let sentence_count = 3;
|
||||
let word_count = 11;
|
||||
fakeit::words::paragraph(count, sentence_count, word_count, "\n".into())
|
||||
|
||||
@@ -7,7 +7,7 @@ use ratatui::{
|
||||
};
|
||||
|
||||
/// Benchmark for rendering a sparkline.
|
||||
pub fn sparkline(c: &mut Criterion) {
|
||||
fn sparkline(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("sparkline");
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
@@ -38,7 +38,7 @@ fn render(bencher: &mut Bencher, sparkline: &Sparkline) {
|
||||
bench_sparkline.render(buffer.area, &mut buffer);
|
||||
},
|
||||
criterion::BatchSize::LargeInput,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
criterion_group!(benches, sparkline);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! # [Ratatui] BarChart example
|
||||
//! # [Ratatui] `BarChart` example
|
||||
//!
|
||||
//! The latest version of this example is available in the [examples] folder in the repository.
|
||||
//!
|
||||
@@ -24,7 +24,10 @@ use crossterm::{
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Bar, BarChart, BarGroup, Block, Borders, Paragraph},
|
||||
};
|
||||
|
||||
struct Company<'a> {
|
||||
revenue: [u64; 4],
|
||||
@@ -41,7 +44,7 @@ struct App<'a> {
|
||||
const TOTAL_REVENUE: &str = "Total Revenue";
|
||||
|
||||
impl<'a> App<'a> {
|
||||
fn new() -> App<'a> {
|
||||
fn new() -> Self {
|
||||
App {
|
||||
data: vec![
|
||||
("B1", 9),
|
||||
@@ -137,7 +140,7 @@ fn run_app<B: Backend>(
|
||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||
if crossterm::event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if let KeyCode::Char('q') = key.code {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -167,6 +170,7 @@ fn ui(frame: &mut Frame, app: &App) {
|
||||
draw_horizontal_bars(frame, app, right);
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
fn create_groups<'a>(app: &'a App, combine_values_and_labels: bool) -> Vec<BarGroup<'a>> {
|
||||
app.months
|
||||
.iter()
|
||||
@@ -206,7 +210,10 @@ fn create_groups<'a>(app: &'a App, combine_values_and_labels: bool) -> Vec<BarGr
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn draw_bar_with_group_labels(f: &mut Frame, app: &App, area: Rect) {
|
||||
const LEGEND_HEIGHT: u16 = 6;
|
||||
|
||||
let groups = create_groups(app, false);
|
||||
|
||||
let mut barchart = BarChart::default()
|
||||
@@ -215,12 +222,11 @@ fn draw_bar_with_group_labels(f: &mut Frame, app: &App, area: Rect) {
|
||||
.group_gap(3);
|
||||
|
||||
for group in groups {
|
||||
barchart = barchart.data(group)
|
||||
barchart = barchart.data(group);
|
||||
}
|
||||
|
||||
f.render_widget(barchart, area);
|
||||
|
||||
const LEGEND_HEIGHT: u16 = 6;
|
||||
if area.height >= LEGEND_HEIGHT && area.width >= TOTAL_REVENUE.len() as u16 + 2 {
|
||||
let legend_width = TOTAL_REVENUE.len() as u16 + 2;
|
||||
let legend_area = Rect {
|
||||
@@ -233,7 +239,10 @@ fn draw_bar_with_group_labels(f: &mut Frame, app: &App, area: Rect) {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn draw_horizontal_bars(f: &mut Frame, app: &App, area: Rect) {
|
||||
const LEGEND_HEIGHT: u16 = 6;
|
||||
|
||||
let groups = create_groups(app, true);
|
||||
|
||||
let mut barchart = BarChart::default()
|
||||
@@ -244,12 +253,11 @@ fn draw_horizontal_bars(f: &mut Frame, app: &App, area: Rect) {
|
||||
.direction(Direction::Horizontal);
|
||||
|
||||
for group in groups {
|
||||
barchart = barchart.data(group)
|
||||
barchart = barchart.data(group);
|
||||
}
|
||||
|
||||
f.render_widget(barchart, area);
|
||||
|
||||
const LEGEND_HEIGHT: u16 = 6;
|
||||
if area.height >= LEGEND_HEIGHT && area.width >= TOTAL_REVENUE.len() as u16 + 2 {
|
||||
let legend_width = TOTAL_REVENUE.len() as u16 + 2;
|
||||
let legend_area = Rect {
|
||||
|
||||
@@ -77,7 +77,7 @@ fn run(terminal: &mut Terminal) -> Result<()> {
|
||||
fn handle_events() -> Result<ControlFlow<()>> {
|
||||
if event::poll(Duration::from_millis(100))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if let KeyCode::Char('q') = key.code {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
return Ok(ControlFlow::Break(()));
|
||||
}
|
||||
}
|
||||
@@ -150,7 +150,7 @@ fn placeholder_paragraph() -> Paragraph<'static> {
|
||||
fn render_borders(paragraph: &Paragraph, border: Borders, frame: &mut Frame, area: Rect) {
|
||||
let block = Block::new()
|
||||
.borders(border)
|
||||
.title(format!("Borders::{border:#?}", border = border));
|
||||
.title(format!("Borders::{border:#?}"));
|
||||
frame.render_widget(paragraph.clone().block(block), area);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
#![allow(clippy::wildcard_imports)]
|
||||
|
||||
use std::{error::Error, io};
|
||||
|
||||
use crossterm::{
|
||||
@@ -166,15 +168,13 @@ fn make_dates(current_year: i32) -> CalendarEventStore {
|
||||
mod cals {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn get_cal<'a, DS: DateStyler>(m: Month, y: i32, es: DS) -> Monthly<'a, DS> {
|
||||
use Month::*;
|
||||
pub fn get_cal<'a, DS: DateStyler>(m: Month, y: i32, es: DS) -> Monthly<'a, DS> {
|
||||
match m {
|
||||
May => example1(m, y, es),
|
||||
June => example2(m, y, es),
|
||||
July => example3(m, y, es),
|
||||
December => example3(m, y, es),
|
||||
February => example4(m, y, es),
|
||||
November => example5(m, y, es),
|
||||
Month::May => example1(m, y, es),
|
||||
Month::June => example2(m, y, es),
|
||||
Month::July | Month::December => example3(m, y, es),
|
||||
Month::February => example4(m, y, es),
|
||||
Month::November => example5(m, y, es),
|
||||
_ => default(m, y, es),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
#![allow(clippy::wildcard_imports)]
|
||||
|
||||
use std::{
|
||||
io::{self, stdout, Stdout},
|
||||
time::{Duration, Instant},
|
||||
@@ -44,8 +46,8 @@ struct App {
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn new() -> App {
|
||||
App {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
ball: Circle {
|
||||
@@ -64,7 +66,7 @@ impl App {
|
||||
|
||||
pub fn run() -> io::Result<()> {
|
||||
let mut terminal = init_terminal()?;
|
||||
let mut app = App::new();
|
||||
let mut app = Self::new();
|
||||
let mut last_tick = Instant::now();
|
||||
let tick_rate = Duration::from_millis(16);
|
||||
loop {
|
||||
@@ -106,13 +108,13 @@ impl App {
|
||||
// bounce the ball by flipping the velocity vector
|
||||
let ball = &self.ball;
|
||||
let playground = self.playground;
|
||||
if ball.x - ball.radius < playground.left() as f64
|
||||
|| ball.x + ball.radius > playground.right() as f64
|
||||
if ball.x - ball.radius < f64::from(playground.left())
|
||||
|| ball.x + ball.radius > f64::from(playground.right())
|
||||
{
|
||||
self.vx = -self.vx;
|
||||
}
|
||||
if ball.y - ball.radius < playground.top() as f64
|
||||
|| ball.y + ball.radius > playground.bottom() as f64
|
||||
if ball.y - ball.radius < f64::from(playground.top())
|
||||
|| ball.y + ball.radius > f64::from(playground.bottom())
|
||||
{
|
||||
self.vy = -self.vy;
|
||||
}
|
||||
@@ -160,8 +162,10 @@ impl App {
|
||||
}
|
||||
|
||||
fn boxes_canvas(&self, area: Rect) -> impl Widget {
|
||||
let (left, right, bottom, top) =
|
||||
(0.0, area.width as f64, 0.0, area.height as f64 * 2.0 - 4.0);
|
||||
let left = 0.0;
|
||||
let right = f64::from(area.width);
|
||||
let bottom = 0.0;
|
||||
let top = f64::from(area.height).mul_add(2.0, -4.0);
|
||||
Canvas::default()
|
||||
.block(Block::default().borders(Borders::ALL).title("Rects"))
|
||||
.marker(self.marker)
|
||||
@@ -170,26 +174,26 @@ impl App {
|
||||
.paint(|ctx| {
|
||||
for i in 0..=11 {
|
||||
ctx.draw(&Rectangle {
|
||||
x: (i * i + 3 * i) as f64 / 2.0 + 2.0,
|
||||
x: f64::from(i * i + 3 * i) / 2.0 + 2.0,
|
||||
y: 2.0,
|
||||
width: i as f64,
|
||||
height: i as f64,
|
||||
width: f64::from(i),
|
||||
height: f64::from(i),
|
||||
color: Color::Red,
|
||||
});
|
||||
ctx.draw(&Rectangle {
|
||||
x: (i * i + 3 * i) as f64 / 2.0 + 2.0,
|
||||
x: f64::from(i * i + 3 * i) / 2.0 + 2.0,
|
||||
y: 21.0,
|
||||
width: i as f64,
|
||||
height: i as f64,
|
||||
width: f64::from(i),
|
||||
height: f64::from(i),
|
||||
color: Color::Blue,
|
||||
});
|
||||
}
|
||||
for i in 0..100 {
|
||||
if i % 10 != 0 {
|
||||
ctx.print(i as f64 + 1.0, 0.0, format!("{i}", i = i % 10));
|
||||
ctx.print(f64::from(i) + 1.0, 0.0, format!("{i}", i = i % 10));
|
||||
}
|
||||
if i % 2 == 0 && i % 10 != 0 {
|
||||
ctx.print(0.0, i as f64, format!("{i}", i = i % 10));
|
||||
ctx.print(0.0, f64::from(i), format!("{i}", i = i % 10));
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -26,11 +26,11 @@ use crossterm::{
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{block::Title, *},
|
||||
widgets::{block::Title, Axis, Block, Borders, Chart, Dataset, GraphType, LegendPosition},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SinSignal {
|
||||
struct SinSignal {
|
||||
x: f64,
|
||||
interval: f64,
|
||||
period: f64,
|
||||
@@ -38,8 +38,8 @@ pub struct SinSignal {
|
||||
}
|
||||
|
||||
impl SinSignal {
|
||||
pub fn new(interval: f64, period: f64, scale: f64) -> SinSignal {
|
||||
SinSignal {
|
||||
const fn new(interval: f64, period: f64, scale: f64) -> Self {
|
||||
Self {
|
||||
x: 0.0,
|
||||
interval,
|
||||
period,
|
||||
@@ -66,12 +66,12 @@ struct App {
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn new() -> App {
|
||||
fn new() -> Self {
|
||||
let mut signal1 = SinSignal::new(0.2, 3.0, 18.0);
|
||||
let mut signal2 = SinSignal::new(0.1, 2.0, 10.0);
|
||||
let data1 = signal1.by_ref().take(200).collect::<Vec<(f64, f64)>>();
|
||||
let data2 = signal2.by_ref().take(200).collect::<Vec<(f64, f64)>>();
|
||||
App {
|
||||
Self {
|
||||
signal1,
|
||||
data1,
|
||||
signal2,
|
||||
@@ -133,7 +133,7 @@ fn run_app<B: Backend>(
|
||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||
if crossterm::event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if let KeyCode::Char('q') = key.code {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -242,7 +242,7 @@ fn render_line_chart(f: &mut Frame, area: Rect) {
|
||||
.legend_position(Some(LegendPosition::TopLeft))
|
||||
.hidden_legend_constraints((Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)));
|
||||
|
||||
f.render_widget(chart, area)
|
||||
f.render_widget(chart, area);
|
||||
}
|
||||
|
||||
fn render_scatter(f: &mut Frame, area: Rect) {
|
||||
@@ -310,7 +310,7 @@ const HEAVY_PAYLOAD_DATA: [(f64, f64); 9] = [
|
||||
const MEDIUM_PAYLOAD_DATA: [(f64, f64); 29] = [
|
||||
(1963., 29500.),
|
||||
(1964., 30600.),
|
||||
(1965., 177900.),
|
||||
(1965., 177_900.),
|
||||
(1965., 21000.),
|
||||
(1966., 17900.),
|
||||
(1966., 8400.),
|
||||
@@ -340,7 +340,7 @@ const MEDIUM_PAYLOAD_DATA: [(f64, f64); 29] = [
|
||||
];
|
||||
|
||||
const SMALL_PAYLOAD_DATA: [(f64, f64); 23] = [
|
||||
(1961., 118500.),
|
||||
(1961., 118_500.),
|
||||
(1962., 14900.),
|
||||
(1975., 21400.),
|
||||
(1980., 32800.),
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
/// This example shows all the colors supported by ratatui. It will render a grid of foreground
|
||||
/// and background colors with their names and indexes.
|
||||
// This example shows all the colors supported by ratatui. It will render a grid of foreground
|
||||
// and background colors with their names and indexes.
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
io::{self, Stdout},
|
||||
@@ -28,7 +29,10 @@ use crossterm::{
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
};
|
||||
|
||||
type Result<T> = result::Result<T, Box<dyn Error>>;
|
||||
|
||||
@@ -48,7 +52,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||
|
||||
if event::poll(Duration::from_millis(250))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if let KeyCode::Char('q') = key.code {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! # [Ratatui] Colors_RGB example
|
||||
//! # [Ratatui] `Colors_RGB` example
|
||||
//!
|
||||
//! The latest version of this example is available in the [examples] folder in the repository.
|
||||
//!
|
||||
@@ -13,18 +13,19 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
/// This example shows the full range of RGB colors that can be displayed in the terminal.
|
||||
///
|
||||
/// Requires a terminal that supports 24-bit color (true color) and unicode.
|
||||
///
|
||||
/// This example also demonstrates how implementing the Widget trait on a mutable reference
|
||||
/// allows the widget to update its state while it is being rendered. This allows the fps
|
||||
/// widget to update the fps calculation and the colors widget to update a cached version of
|
||||
/// the colors to render instead of recalculating them every frame.
|
||||
///
|
||||
/// This is an alternative to using the StatefulWidget trait and a separate state struct. It is
|
||||
/// useful when the state is only used by the widget and doesn't need to be shared with other
|
||||
/// widgets.
|
||||
// This example shows the full range of RGB colors that can be displayed in the terminal.
|
||||
//
|
||||
// Requires a terminal that supports 24-bit color (true color) and unicode.
|
||||
//
|
||||
// This example also demonstrates how implementing the Widget trait on a mutable reference
|
||||
// allows the widget to update its state while it is being rendered. This allows the fps
|
||||
// widget to update the fps calculation and the colors widget to update a cached version of
|
||||
// the colors to render instead of recalculating them every frame.
|
||||
//
|
||||
// This is an alternative to using the `StatefulWidget` trait and a separate state struct. It
|
||||
// is useful when the state is only used by the widget and doesn't need to be shared with
|
||||
// other widgets.
|
||||
|
||||
use std::{
|
||||
io::stdout,
|
||||
panic,
|
||||
@@ -110,7 +111,7 @@ impl App {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
const fn is_running(&self) -> bool {
|
||||
matches!(self.state, AppState::Running)
|
||||
}
|
||||
|
||||
@@ -140,6 +141,7 @@ impl App {
|
||||
/// to update the colors to render.
|
||||
impl Widget for &mut App {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use Constraint::*;
|
||||
let [top, colors] = Layout::vertical([Length(1), Min(0)]).areas(area);
|
||||
let [title, fps] = Layout::horizontal([Min(0), Length(8)]).areas(top);
|
||||
@@ -151,9 +153,9 @@ impl Widget for &mut App {
|
||||
}
|
||||
}
|
||||
|
||||
/// Default impl for FpsWidget
|
||||
/// Default impl for `FpsWidget`
|
||||
///
|
||||
/// Manual impl is required because we need to initialize the last_instant field to the current
|
||||
/// Manual impl is required because we need to initialize the `last_instant` field to the current
|
||||
/// instant.
|
||||
impl Default for FpsWidget {
|
||||
fn default() -> Self {
|
||||
@@ -165,7 +167,7 @@ impl Default for FpsWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Widget impl for FpsWidget
|
||||
/// Widget impl for `FpsWidget`
|
||||
///
|
||||
/// This is implemented on a mutable reference so that we can update the frame count and fps
|
||||
/// calculation while rendering.
|
||||
@@ -173,7 +175,7 @@ impl Widget for &mut FpsWidget {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.calculate_fps();
|
||||
if let Some(fps) = self.fps {
|
||||
let text = format!("{:.1} fps", fps);
|
||||
let text = format!("{fps:.1} fps");
|
||||
Text::from(text).render(area, buf);
|
||||
}
|
||||
}
|
||||
@@ -185,6 +187,7 @@ impl FpsWidget {
|
||||
/// This updates the fps once a second, but only if the widget has rendered at least 2 frames
|
||||
/// since the last calculation. This avoids noise in the fps calculation when rendering on slow
|
||||
/// machines that can't render at least 2 frames per second.
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
fn calculate_fps(&mut self) {
|
||||
self.frame_count += 1;
|
||||
let elapsed = self.last_instant.elapsed();
|
||||
@@ -196,7 +199,7 @@ impl FpsWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Widget impl for ColorsWidget
|
||||
/// Widget impl for `ColorsWidget`
|
||||
///
|
||||
/// This is implemented on a mutable reference so that we can update the frame count and store a
|
||||
/// cached version of the colors to render instead of recalculating them every frame.
|
||||
@@ -226,6 +229,7 @@ impl ColorsWidget {
|
||||
///
|
||||
/// This is called once per frame to setup the colors to render. It caches the colors so that
|
||||
/// they don't need to be recalculated every frame.
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
fn setup_colors(&mut self, size: Rect) {
|
||||
let Rect { width, height, .. } = size;
|
||||
// double the height because each screen row has two rows of half block pixels
|
||||
@@ -253,7 +257,7 @@ impl ColorsWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Install color_eyre panic and error hooks
|
||||
/// Install `color_eyre` panic and error hooks
|
||||
///
|
||||
/// The hooks restore the terminal to a usable state before printing the error message.
|
||||
fn install_error_hooks() -> Result<()> {
|
||||
@@ -266,7 +270,7 @@ fn install_error_hooks() -> Result<()> {
|
||||
}))?;
|
||||
panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info)
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
#![allow(clippy::enum_glob_use, clippy::wildcard_imports)]
|
||||
|
||||
use std::io::{self, stdout};
|
||||
|
||||
use color_eyre::{config::HookBuilder, Result};
|
||||
@@ -27,7 +29,7 @@ use ratatui::{
|
||||
prelude::*,
|
||||
style::palette::tailwind::*,
|
||||
symbols::line,
|
||||
widgets::*,
|
||||
widgets::{Block, Paragraph, Wrap},
|
||||
};
|
||||
use strum::{Display, EnumIter, FromRepr};
|
||||
|
||||
@@ -67,9 +69,9 @@ enum ConstraintName {
|
||||
/// └──────────────┘
|
||||
/// ```
|
||||
struct ConstraintBlock {
|
||||
selected: bool,
|
||||
legend: bool,
|
||||
constraint: Constraint,
|
||||
legend: bool,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
/// A widget that renders a spacer with a label indicating the width of the spacer. E.g.:
|
||||
@@ -146,32 +148,31 @@ impl App {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// select the next block with wrap around
|
||||
fn increment_value(&mut self) {
|
||||
if self.constraints.is_empty() {
|
||||
let Some(constraint) = self.constraints.get_mut(self.selected_index) else {
|
||||
return;
|
||||
}
|
||||
self.constraints[self.selected_index] = match self.constraints[self.selected_index] {
|
||||
Constraint::Length(v) => Constraint::Length(v.saturating_add(1)),
|
||||
Constraint::Min(v) => Constraint::Min(v.saturating_add(1)),
|
||||
Constraint::Max(v) => Constraint::Max(v.saturating_add(1)),
|
||||
Constraint::Fill(v) => Constraint::Fill(v.saturating_add(1)),
|
||||
Constraint::Percentage(v) => Constraint::Percentage(v.saturating_add(1)),
|
||||
Constraint::Ratio(n, d) => Constraint::Ratio(n, d.saturating_add(1)),
|
||||
};
|
||||
match constraint {
|
||||
Constraint::Length(v)
|
||||
| Constraint::Min(v)
|
||||
| Constraint::Max(v)
|
||||
| Constraint::Fill(v)
|
||||
| Constraint::Percentage(v) => *v = v.saturating_add(1),
|
||||
Constraint::Ratio(_n, d) => *d = d.saturating_add(1),
|
||||
};
|
||||
}
|
||||
|
||||
fn decrement_value(&mut self) {
|
||||
if self.constraints.is_empty() {
|
||||
let Some(constraint) = self.constraints.get_mut(self.selected_index) else {
|
||||
return;
|
||||
}
|
||||
self.constraints[self.selected_index] = match self.constraints[self.selected_index] {
|
||||
Constraint::Length(v) => Constraint::Length(v.saturating_sub(1)),
|
||||
Constraint::Min(v) => Constraint::Min(v.saturating_sub(1)),
|
||||
Constraint::Max(v) => Constraint::Max(v.saturating_sub(1)),
|
||||
Constraint::Fill(v) => Constraint::Fill(v.saturating_sub(1)),
|
||||
Constraint::Percentage(v) => Constraint::Percentage(v.saturating_sub(1)),
|
||||
Constraint::Ratio(n, d) => Constraint::Ratio(n, d.saturating_sub(1)),
|
||||
};
|
||||
match constraint {
|
||||
Constraint::Length(v)
|
||||
| Constraint::Min(v)
|
||||
| Constraint::Max(v)
|
||||
| Constraint::Fill(v)
|
||||
| Constraint::Percentage(v) => *v = v.saturating_sub(1),
|
||||
Constraint::Ratio(_n, d) => *d = d.saturating_sub(1),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -222,7 +223,7 @@ impl App {
|
||||
}
|
||||
|
||||
fn exit(&mut self) {
|
||||
self.mode = AppMode::Quit
|
||||
self.mode = AppMode::Quit;
|
||||
}
|
||||
|
||||
fn swap_constraint(&mut self, name: ConstraintName) {
|
||||
@@ -235,7 +236,7 @@ impl App {
|
||||
ConstraintName::Min => Min(self.value),
|
||||
ConstraintName::Max => Max(self.value),
|
||||
ConstraintName::Fill => Fill(self.value),
|
||||
ConstraintName::Ratio => Ratio(1, self.value as u32 / 4), // for balance
|
||||
ConstraintName::Ratio => Ratio(1, u32::from(self.value) / 4), // for balance
|
||||
};
|
||||
self.constraints[self.selected_index] = constraint;
|
||||
}
|
||||
@@ -243,14 +244,13 @@ impl App {
|
||||
|
||||
impl From<Constraint> for ConstraintName {
|
||||
fn from(constraint: Constraint) -> Self {
|
||||
use Constraint::*;
|
||||
match constraint {
|
||||
Length(_) => ConstraintName::Length,
|
||||
Percentage(_) => ConstraintName::Percentage,
|
||||
Ratio(_, _) => ConstraintName::Ratio,
|
||||
Min(_) => ConstraintName::Min,
|
||||
Max(_) => ConstraintName::Max,
|
||||
Fill(_) => ConstraintName::Fill,
|
||||
Length(_) => Self::Length,
|
||||
Percentage(_) => Self::Percentage,
|
||||
Ratio(_, _) => Self::Ratio,
|
||||
Min(_) => Self::Min,
|
||||
Max(_) => Self::Max,
|
||||
Fill(_) => Self::Fill,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,9 +267,9 @@ impl Widget for &App {
|
||||
])
|
||||
.areas(area);
|
||||
|
||||
self.header().render(header_area, buf);
|
||||
self.instructions().render(instructions_area, buf);
|
||||
self.swap_legend().render(swap_legend_area, buf);
|
||||
App::header().render(header_area, buf);
|
||||
App::instructions().render(instructions_area, buf);
|
||||
App::swap_legend().render(swap_legend_area, buf);
|
||||
self.render_layout_blocks(blocks_area, buf);
|
||||
}
|
||||
}
|
||||
@@ -280,12 +280,12 @@ impl App {
|
||||
const TEXT_COLOR: Color = SLATE.c400;
|
||||
const AXIS_COLOR: Color = SLATE.c500;
|
||||
|
||||
fn header(&self) -> impl Widget {
|
||||
fn header() -> impl Widget {
|
||||
let text = "Constraint Explorer";
|
||||
text.bold().fg(Self::HEADER_COLOR).to_centered_line()
|
||||
text.bold().fg(Self::HEADER_COLOR).into_centered_line()
|
||||
}
|
||||
|
||||
fn instructions(&self) -> impl Widget {
|
||||
fn instructions() -> impl Widget {
|
||||
let text = "◄ ►: select, ▲ ▼: edit, 1-6: swap, a: add, x: delete, q: quit, + -: spacing";
|
||||
Paragraph::new(text)
|
||||
.fg(Self::TEXT_COLOR)
|
||||
@@ -293,7 +293,7 @@ impl App {
|
||||
.wrap(Wrap { trim: false })
|
||||
}
|
||||
|
||||
fn swap_legend(&self) -> impl Widget {
|
||||
fn swap_legend() -> impl Widget {
|
||||
#[allow(unstable_name_collisions)]
|
||||
Paragraph::new(
|
||||
Line::from(
|
||||
@@ -327,7 +327,7 @@ impl App {
|
||||
let label = if self.spacing != 0 {
|
||||
format!("{} px (gap: {} px)", width, self.spacing)
|
||||
} else {
|
||||
format!("{} px", width)
|
||||
format!("{width} px")
|
||||
};
|
||||
let bar_width = width.saturating_sub(2) as usize; // we want to `<` and `>` at the ends
|
||||
let width_bar = format!("<{label:-^bar_width$}>");
|
||||
@@ -348,7 +348,7 @@ impl App {
|
||||
self.render_layout_block(Flex::Center, center, buf);
|
||||
self.render_layout_block(Flex::End, end, buf);
|
||||
self.render_layout_block(Flex::SpaceAround, space_around, buf);
|
||||
self.render_layout_block(Flex::SpaceBetween, space_between, buf)
|
||||
self.render_layout_block(Flex::SpaceBetween, space_between, buf);
|
||||
}
|
||||
|
||||
fn render_user_constraints_legend(&self, area: Rect, buf: &mut Buffer) {
|
||||
@@ -371,7 +371,7 @@ impl App {
|
||||
Layout::vertical([Length(1), Max(1), Length(4)]).areas(area);
|
||||
|
||||
if label_area.height > 0 {
|
||||
format!("Flex::{:?}", flex).bold().render(label_area, buf);
|
||||
format!("Flex::{flex:?}").bold().render(label_area, buf);
|
||||
}
|
||||
|
||||
self.axis(area.width).render(axis_area, buf);
|
||||
@@ -405,17 +405,17 @@ impl Widget for ConstraintBlock {
|
||||
impl ConstraintBlock {
|
||||
const TEXT_COLOR: Color = SLATE.c200;
|
||||
|
||||
fn new(constraint: Constraint, selected: bool, legend: bool) -> Self {
|
||||
const fn new(constraint: Constraint, selected: bool, legend: bool) -> Self {
|
||||
Self {
|
||||
constraint,
|
||||
selected,
|
||||
legend,
|
||||
selected,
|
||||
}
|
||||
}
|
||||
|
||||
fn label(&self, width: u16) -> String {
|
||||
let long_width = format!("{} px", width);
|
||||
let short_width = format!("{}", width);
|
||||
let long_width = format!("{width} px");
|
||||
let short_width = format!("{width}");
|
||||
// border takes up 2 columns
|
||||
let available_space = width.saturating_sub(2) as usize;
|
||||
let width_label = if long_width.len() < available_space {
|
||||
@@ -423,7 +423,7 @@ impl ConstraintBlock {
|
||||
} else if short_width.len() < available_space {
|
||||
short_width
|
||||
} else {
|
||||
"".to_string()
|
||||
String::new()
|
||||
};
|
||||
format!("{}\n{}", self.constraint, width_label)
|
||||
}
|
||||
@@ -499,9 +499,9 @@ impl Widget for SpacerBlock {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
match area.height {
|
||||
1 => (),
|
||||
2 => self.render_2px(area, buf),
|
||||
3 => self.render_3px(area, buf),
|
||||
_ => self.render_4px(area, buf),
|
||||
2 => Self::render_2px(area, buf),
|
||||
3 => Self::render_3px(area, buf),
|
||||
_ => Self::render_4px(area, buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -541,7 +541,7 @@ impl SpacerBlock {
|
||||
/// A label that says "Spacer" if there is enough space
|
||||
fn spacer_label(width: u16) -> impl Widget {
|
||||
let label = if width >= 6 { "Spacer" } else { "" };
|
||||
label.fg(SpacerBlock::TEXT_COLOR).to_centered_line()
|
||||
label.fg(Self::TEXT_COLOR).into_centered_line()
|
||||
}
|
||||
|
||||
/// A label that says "8 px" if there is enough space
|
||||
@@ -553,12 +553,12 @@ impl SpacerBlock {
|
||||
} else if short_label.len() < width as usize {
|
||||
short_label
|
||||
} else {
|
||||
"".to_string()
|
||||
String::new()
|
||||
};
|
||||
Line::styled(label, Self::TEXT_COLOR).centered()
|
||||
}
|
||||
|
||||
fn render_2px(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_2px(area: Rect, buf: &mut Buffer) {
|
||||
if area.width > 1 {
|
||||
Self::block().render(area, buf);
|
||||
} else {
|
||||
@@ -566,7 +566,7 @@ impl SpacerBlock {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_3px(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_3px(area: Rect, buf: &mut Buffer) {
|
||||
if area.width > 1 {
|
||||
Self::block().render(area, buf);
|
||||
} else {
|
||||
@@ -577,7 +577,7 @@ impl SpacerBlock {
|
||||
Self::spacer_label(area.width).render(row, buf);
|
||||
}
|
||||
|
||||
fn render_4px(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_4px(area: Rect, buf: &mut Buffer) {
|
||||
if area.width > 1 {
|
||||
Self::block().render(area, buf);
|
||||
} else {
|
||||
@@ -593,7 +593,7 @@ impl SpacerBlock {
|
||||
}
|
||||
|
||||
impl ConstraintName {
|
||||
fn color(&self) -> Color {
|
||||
const fn color(self) -> Color {
|
||||
match self {
|
||||
Self::Length => SLATE.c700,
|
||||
Self::Percentage => SLATE.c800,
|
||||
@@ -604,7 +604,7 @@ impl ConstraintName {
|
||||
}
|
||||
}
|
||||
|
||||
fn lighter_color(&self) -> Color {
|
||||
const fn lighter_color(self) -> Color {
|
||||
match self {
|
||||
Self::Length => STONE.c500,
|
||||
Self::Percentage => STONE.c600,
|
||||
@@ -626,7 +626,7 @@ fn init_error_hooks() -> Result<()> {
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info)
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
#![allow(clippy::enum_glob_use, clippy::wildcard_imports)]
|
||||
|
||||
use std::io::{self, stdout};
|
||||
|
||||
use color_eyre::{config::HookBuilder, Result};
|
||||
@@ -95,7 +97,7 @@ impl App {
|
||||
self.max_scroll_offset = (self.selected_tab.get_example_count() - 1) * EXAMPLE_HEIGHT;
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
fn is_running(self) -> bool {
|
||||
self.state == AppState::Running
|
||||
}
|
||||
|
||||
@@ -138,14 +140,14 @@ impl App {
|
||||
}
|
||||
|
||||
fn up(&mut self) {
|
||||
self.scroll_offset = self.scroll_offset.saturating_sub(1)
|
||||
self.scroll_offset = self.scroll_offset.saturating_sub(1);
|
||||
}
|
||||
|
||||
fn down(&mut self) {
|
||||
self.scroll_offset = self
|
||||
.scroll_offset
|
||||
.saturating_add(1)
|
||||
.min(self.max_scroll_offset)
|
||||
.min(self.max_scroll_offset);
|
||||
}
|
||||
|
||||
fn top(&mut self) {
|
||||
@@ -159,17 +161,16 @@ impl App {
|
||||
|
||||
impl Widget for App {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let [tabs, axis, demo] =
|
||||
Layout::vertical([Constraint::Length(3), Constraint::Length(3), Fill(0)]).areas(area);
|
||||
let [tabs, axis, demo] = Layout::vertical([Length(3), Length(3), Fill(0)]).areas(area);
|
||||
|
||||
self.render_tabs(tabs, buf);
|
||||
self.render_axis(axis, buf);
|
||||
Self::render_axis(axis, buf);
|
||||
self.render_demo(demo, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn render_tabs(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_tabs(self, area: Rect, buf: &mut Buffer) {
|
||||
let titles = SelectedTab::iter().map(SelectedTab::to_tab_title);
|
||||
let block = Block::new()
|
||||
.title("Constraints ".bold())
|
||||
@@ -183,10 +184,10 @@ impl App {
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn render_axis(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_axis(area: Rect, buf: &mut Buffer) {
|
||||
let width = area.width as usize;
|
||||
// a bar like `<----- 80 px ----->`
|
||||
let width_label = format!("{} px", width);
|
||||
let width_label = format!("{width} px");
|
||||
let width_bar = format!(
|
||||
"<{width_label:-^width$}>",
|
||||
width = width - width_label.len() / 2
|
||||
@@ -206,7 +207,8 @@ impl App {
|
||||
///
|
||||
/// This function renders the demo content into a separate buffer and then splices the buffer
|
||||
/// into the main buffer. This is done to make it possible to handle scrolling easily.
|
||||
fn render_demo(&self, area: Rect, buf: &mut Buffer) {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn render_demo(self, area: Rect, buf: &mut Buffer) {
|
||||
// render demo content into a separate buffer so all examples fit we add an extra
|
||||
// area.height to make sure the last example is fully visible even when the scroll offset is
|
||||
// at the max
|
||||
@@ -246,41 +248,40 @@ impl App {
|
||||
|
||||
impl SelectedTab {
|
||||
/// Get the previous tab, if there is no previous tab return the current tab.
|
||||
fn previous(&self) -> Self {
|
||||
let current_index: usize = *self as usize;
|
||||
fn previous(self) -> Self {
|
||||
let current_index: usize = self as usize;
|
||||
let previous_index = current_index.saturating_sub(1);
|
||||
Self::from_repr(previous_index).unwrap_or(*self)
|
||||
Self::from_repr(previous_index).unwrap_or(self)
|
||||
}
|
||||
|
||||
/// Get the next tab, if there is no next tab return the current tab.
|
||||
fn next(&self) -> Self {
|
||||
let current_index = *self as usize;
|
||||
fn next(self) -> Self {
|
||||
let current_index = self as usize;
|
||||
let next_index = current_index.saturating_add(1);
|
||||
Self::from_repr(next_index).unwrap_or(*self)
|
||||
Self::from_repr(next_index).unwrap_or(self)
|
||||
}
|
||||
|
||||
fn get_example_count(&self) -> u16 {
|
||||
use SelectedTab::*;
|
||||
const fn get_example_count(self) -> u16 {
|
||||
#[allow(clippy::match_same_arms)]
|
||||
match self {
|
||||
Length => 4,
|
||||
Percentage => 5,
|
||||
Ratio => 4,
|
||||
Fill => 2,
|
||||
Min => 5,
|
||||
Max => 5,
|
||||
Self::Length => 4,
|
||||
Self::Percentage => 5,
|
||||
Self::Ratio => 4,
|
||||
Self::Fill => 2,
|
||||
Self::Min => 5,
|
||||
Self::Max => 5,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_tab_title(value: SelectedTab) -> Line<'static> {
|
||||
use SelectedTab::*;
|
||||
fn to_tab_title(value: Self) -> Line<'static> {
|
||||
let text = format!(" {value} ");
|
||||
let color = match value {
|
||||
Length => LENGTH_COLOR,
|
||||
Percentage => PERCENTAGE_COLOR,
|
||||
Ratio => RATIO_COLOR,
|
||||
Fill => FILL_COLOR,
|
||||
Min => MIN_COLOR,
|
||||
Max => MAX_COLOR,
|
||||
Self::Length => LENGTH_COLOR,
|
||||
Self::Percentage => PERCENTAGE_COLOR,
|
||||
Self::Ratio => RATIO_COLOR,
|
||||
Self::Fill => FILL_COLOR,
|
||||
Self::Min => MIN_COLOR,
|
||||
Self::Max => MAX_COLOR,
|
||||
};
|
||||
text.fg(tailwind::SLATE.c200).bg(color).into()
|
||||
}
|
||||
@@ -289,18 +290,18 @@ impl SelectedTab {
|
||||
impl Widget for SelectedTab {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
match self {
|
||||
SelectedTab::Length => self.render_length_example(area, buf),
|
||||
SelectedTab::Percentage => self.render_percentage_example(area, buf),
|
||||
SelectedTab::Ratio => self.render_ratio_example(area, buf),
|
||||
SelectedTab::Fill => self.render_fill_example(area, buf),
|
||||
SelectedTab::Min => self.render_min_example(area, buf),
|
||||
SelectedTab::Max => self.render_max_example(area, buf),
|
||||
Self::Length => Self::render_length_example(area, buf),
|
||||
Self::Percentage => Self::render_percentage_example(area, buf),
|
||||
Self::Ratio => Self::render_ratio_example(area, buf),
|
||||
Self::Fill => Self::render_fill_example(area, buf),
|
||||
Self::Min => Self::render_min_example(area, buf),
|
||||
Self::Max => Self::render_max_example(area, buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectedTab {
|
||||
fn render_length_example(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_length_example(area: Rect, buf: &mut Buffer) {
|
||||
let [example1, example2, example3, _] =
|
||||
Layout::vertical([Length(EXAMPLE_HEIGHT); 4]).areas(area);
|
||||
|
||||
@@ -309,7 +310,7 @@ impl SelectedTab {
|
||||
Example::new(&[Length(20), Max(20)]).render(example3, buf);
|
||||
}
|
||||
|
||||
fn render_percentage_example(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_percentage_example(area: Rect, buf: &mut Buffer) {
|
||||
let [example1, example2, example3, example4, example5, _] =
|
||||
Layout::vertical([Length(EXAMPLE_HEIGHT); 6]).areas(area);
|
||||
|
||||
@@ -320,7 +321,7 @@ impl SelectedTab {
|
||||
Example::new(&[Percentage(0), Fill(0)]).render(example5, buf);
|
||||
}
|
||||
|
||||
fn render_ratio_example(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_ratio_example(area: Rect, buf: &mut Buffer) {
|
||||
let [example1, example2, example3, example4, _] =
|
||||
Layout::vertical([Length(EXAMPLE_HEIGHT); 5]).areas(area);
|
||||
|
||||
@@ -330,14 +331,14 @@ impl SelectedTab {
|
||||
Example::new(&[Ratio(1, 2), Percentage(25), Length(10)]).render(example4, buf);
|
||||
}
|
||||
|
||||
fn render_fill_example(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_fill_example(area: Rect, buf: &mut Buffer) {
|
||||
let [example1, example2, _] = Layout::vertical([Length(EXAMPLE_HEIGHT); 3]).areas(area);
|
||||
|
||||
Example::new(&[Fill(1), Fill(2), Fill(3)]).render(example1, buf);
|
||||
Example::new(&[Fill(1), Percentage(50), Fill(1)]).render(example2, buf);
|
||||
}
|
||||
|
||||
fn render_min_example(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_min_example(area: Rect, buf: &mut Buffer) {
|
||||
let [example1, example2, example3, example4, example5, _] =
|
||||
Layout::vertical([Length(EXAMPLE_HEIGHT); 6]).areas(area);
|
||||
|
||||
@@ -348,7 +349,7 @@ impl SelectedTab {
|
||||
Example::new(&[Percentage(100), Min(80)]).render(example5, buf);
|
||||
}
|
||||
|
||||
fn render_max_example(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_max_example(area: Rect, buf: &mut Buffer) {
|
||||
let [example1, example2, example3, example4, example5, _] =
|
||||
Layout::vertical([Length(EXAMPLE_HEIGHT); 6]).areas(area);
|
||||
|
||||
@@ -379,14 +380,13 @@ impl Widget for Example {
|
||||
let blocks = Layout::horizontal(&self.constraints).split(area);
|
||||
|
||||
for (block, constraint) in blocks.iter().zip(&self.constraints) {
|
||||
self.illustration(*constraint, block.width)
|
||||
.render(*block, buf);
|
||||
Self::illustration(*constraint, block.width).render(*block, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Example {
|
||||
fn illustration(&self, constraint: Constraint, width: u16) -> Paragraph {
|
||||
fn illustration(constraint: Constraint, width: u16) -> impl Widget {
|
||||
let color = match constraint {
|
||||
Constraint::Length(_) => LENGTH_COLOR,
|
||||
Constraint::Percentage(_) => PERCENTAGE_COLOR,
|
||||
@@ -417,7 +417,7 @@ fn init_error_hooks() -> Result<()> {
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info)
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ use crossterm::{
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{prelude::*, widgets::Paragraph};
|
||||
|
||||
/// A custom widget that renders a button with a label, theme and state.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -71,7 +71,7 @@ const GREEN: Theme = Theme {
|
||||
|
||||
/// A button with a label that can be themed.
|
||||
impl<'a> Button<'a> {
|
||||
pub fn new<T: Into<Line<'a>>>(label: T) -> Button<'a> {
|
||||
pub fn new<T: Into<Line<'a>>>(label: T) -> Self {
|
||||
Button {
|
||||
label: label.into(),
|
||||
theme: BLUE,
|
||||
@@ -79,18 +79,19 @@ impl<'a> Button<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn theme(mut self, theme: Theme) -> Button<'a> {
|
||||
pub const fn theme(mut self, theme: Theme) -> Self {
|
||||
self.theme = theme;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn state(mut self, state: State) -> Button<'a> {
|
||||
pub const fn state(mut self, state: State) -> Self {
|
||||
self.state = state;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Button<'a> {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let (background, text, shadow, highlight) = self.colors();
|
||||
buf.set_style(area, Style::new().bg(background).fg(text));
|
||||
@@ -124,7 +125,7 @@ impl<'a> Widget for Button<'a> {
|
||||
}
|
||||
|
||||
impl Button<'_> {
|
||||
fn colors(&self) -> (Color, Color, Color, Color) {
|
||||
const fn colors(&self) -> (Color, Color, Color, Color) {
|
||||
let theme = self.theme;
|
||||
match self.state {
|
||||
State::Normal => (theme.background, theme.text, theme.shadow, theme.highlight),
|
||||
@@ -163,7 +164,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||
let mut selected_button: usize = 0;
|
||||
let button_states = &mut [State::Selected, State::Normal, State::Normal];
|
||||
let mut button_states = [State::Selected, State::Normal, State::Normal];
|
||||
loop {
|
||||
terminal.draw(|frame| ui(frame, button_states))?;
|
||||
if !event::poll(Duration::from_millis(100))? {
|
||||
@@ -174,18 +175,20 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||
if key.kind != event::KeyEventKind::Press {
|
||||
continue;
|
||||
}
|
||||
if handle_key_event(key, button_states, &mut selected_button).is_break() {
|
||||
if handle_key_event(key, &mut button_states, &mut selected_button).is_break() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse) => handle_mouse_event(mouse, button_states, &mut selected_button),
|
||||
Event::Mouse(mouse) => {
|
||||
handle_mouse_event(mouse, &mut button_states, &mut selected_button);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ui(frame: &mut Frame, states: &[State; 3]) {
|
||||
fn ui(frame: &mut Frame, states: [State; 3]) {
|
||||
let vertical = Layout::vertical([
|
||||
Constraint::Length(1),
|
||||
Constraint::Max(3),
|
||||
@@ -202,7 +205,7 @@ fn ui(frame: &mut Frame, states: &[State; 3]) {
|
||||
frame.render_widget(Paragraph::new("←/→: select, Space: toggle, q: quit"), help);
|
||||
}
|
||||
|
||||
fn render_buttons(frame: &mut Frame<'_>, area: Rect, states: &[State; 3]) {
|
||||
fn render_buttons(frame: &mut Frame<'_>, area: Rect, states: [State; 3]) {
|
||||
let horizontal = Layout::horizontal([
|
||||
Constraint::Length(15),
|
||||
Constraint::Length(15),
|
||||
|
||||
@@ -2,7 +2,7 @@ use rand::{
|
||||
distributions::{Distribution, Uniform},
|
||||
rngs::ThreadRng,
|
||||
};
|
||||
use ratatui::widgets::*;
|
||||
use ratatui::widgets::ListState;
|
||||
|
||||
const TASKS: [&str; 24] = [
|
||||
"Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8", "Item9", "Item10",
|
||||
@@ -73,8 +73,8 @@ pub struct RandomSignal {
|
||||
}
|
||||
|
||||
impl RandomSignal {
|
||||
pub fn new(lower: u64, upper: u64) -> RandomSignal {
|
||||
RandomSignal {
|
||||
pub fn new(lower: u64, upper: u64) -> Self {
|
||||
Self {
|
||||
distribution: Uniform::new(lower, upper),
|
||||
rng: rand::thread_rng(),
|
||||
}
|
||||
@@ -97,8 +97,8 @@ pub struct SinSignal {
|
||||
}
|
||||
|
||||
impl SinSignal {
|
||||
pub fn new(interval: f64, period: f64, scale: f64) -> SinSignal {
|
||||
SinSignal {
|
||||
pub const fn new(interval: f64, period: f64, scale: f64) -> Self {
|
||||
Self {
|
||||
x: 0.0,
|
||||
interval,
|
||||
period,
|
||||
@@ -144,8 +144,8 @@ pub struct StatefulList<T> {
|
||||
}
|
||||
|
||||
impl<T> StatefulList<T> {
|
||||
pub fn with_items(items: Vec<T>) -> StatefulList<T> {
|
||||
StatefulList {
|
||||
pub fn with_items(items: Vec<T>) -> Self {
|
||||
Self {
|
||||
state: ListState::default(),
|
||||
items,
|
||||
}
|
||||
@@ -235,7 +235,7 @@ pub struct App<'a> {
|
||||
}
|
||||
|
||||
impl<'a> App<'a> {
|
||||
pub fn new(title: &'a str, enhanced_graphics: bool) -> App<'a> {
|
||||
pub fn new(title: &'a str, enhanced_graphics: bool) -> Self {
|
||||
let mut rand_signal = RandomSignal::new(0, 100);
|
||||
let sparkline_points = rand_signal.by_ref().take(300).collect();
|
||||
let mut sin_signal = SinSignal::new(0.2, 3.0, 18.0);
|
||||
|
||||
@@ -4,7 +4,10 @@ use std::{
|
||||
};
|
||||
|
||||
use ratatui::prelude::*;
|
||||
use termwiz::{input::*, terminal::Terminal as TermwizTerminal};
|
||||
use termwiz::{
|
||||
input::{InputEvent, KeyCode},
|
||||
terminal::Terminal as TermwizTerminal,
|
||||
};
|
||||
|
||||
use crate::{app::App, ui};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{canvas::*, *},
|
||||
@@ -85,6 +86,7 @@ fn draw_gauges(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
f.render_widget(line_gauge, chunks[2]);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn draw_charts(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
let constraints = if app.show_chart {
|
||||
vec![Constraint::Percentage(50), Constraint::Percentage(50)]
|
||||
|
||||
@@ -80,11 +80,12 @@ impl App {
|
||||
let timeout = Duration::from_secs_f64(1.0 / 50.0);
|
||||
match term::next_event(timeout)? {
|
||||
Some(Event::Key(key)) if key.kind == KeyEventKind::Press => self.handle_key_press(key),
|
||||
_ => Ok(()),
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_key_press(&mut self, key: KeyEvent) -> Result<()> {
|
||||
fn handle_key_press(&mut self, key: KeyEvent) {
|
||||
use KeyCode::*;
|
||||
match key.code {
|
||||
Char('q') | Esc => self.mode = Mode::Quit,
|
||||
@@ -93,10 +94,8 @@ impl App {
|
||||
Char('k') | Up => self.prev(),
|
||||
Char('j') | Down => self.next(),
|
||||
Char('d') | Delete => self.destroy(),
|
||||
|
||||
_ => {}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prev(&mut self) {
|
||||
@@ -124,11 +123,11 @@ impl App {
|
||||
}
|
||||
|
||||
fn next_tab(&mut self) {
|
||||
self.tab = self.tab.next()
|
||||
self.tab = self.tab.next();
|
||||
}
|
||||
|
||||
fn destroy(&mut self) {
|
||||
self.mode = Mode::Destroy
|
||||
self.mode = Mode::Destroy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +146,7 @@ impl Widget for &App {
|
||||
Block::new().style(THEME.root).render(area, buf);
|
||||
self.render_title_bar(title_bar, buf);
|
||||
self.render_selected_tab(tab, buf);
|
||||
self.render_bottom_bar(bottom_bar, buf);
|
||||
App::render_bottom_bar(bottom_bar, buf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +156,7 @@ impl App {
|
||||
let [title, tabs] = layout.areas(area);
|
||||
|
||||
Span::styled("Ratatui", THEME.app_title).render(title, buf);
|
||||
let titles = Tab::iter().map(|tab| tab.title());
|
||||
let titles = Tab::iter().map(Tab::title);
|
||||
Tabs::new(titles)
|
||||
.style(THEME.tabs)
|
||||
.highlight_style(THEME.tabs_selected)
|
||||
@@ -177,7 +176,7 @@ impl App {
|
||||
};
|
||||
}
|
||||
|
||||
fn render_bottom_bar(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_bottom_bar(area: Rect, buf: &mut Buffer) {
|
||||
let keys = [
|
||||
("H/←", "Left"),
|
||||
("L/→", "Right"),
|
||||
@@ -189,8 +188,8 @@ impl App {
|
||||
let spans = keys
|
||||
.iter()
|
||||
.flat_map(|(key, desc)| {
|
||||
let key = Span::styled(format!(" {} ", key), THEME.key_binding.key);
|
||||
let desc = Span::styled(format!(" {} ", desc), THEME.key_binding.description);
|
||||
let key = Span::styled(format!(" {key} "), THEME.key_binding.key);
|
||||
let desc = Span::styled(format!(" {desc} "), THEME.key_binding.description);
|
||||
[key, desc]
|
||||
})
|
||||
.collect_vec();
|
||||
@@ -202,22 +201,22 @@ impl App {
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
fn next(&self) -> Self {
|
||||
let current_index = *self as usize;
|
||||
fn next(self) -> Self {
|
||||
let current_index = self as usize;
|
||||
let next_index = current_index.saturating_add(1);
|
||||
Self::from_repr(next_index).unwrap_or(*self)
|
||||
Self::from_repr(next_index).unwrap_or(self)
|
||||
}
|
||||
|
||||
fn prev(&self) -> Self {
|
||||
let current_index = *self as usize;
|
||||
fn prev(self) -> Self {
|
||||
let current_index = self as usize;
|
||||
let prev_index = current_index.saturating_sub(1);
|
||||
Self::from_repr(prev_index).unwrap_or(*self)
|
||||
Self::from_repr(prev_index).unwrap_or(self)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
fn title(self) -> String {
|
||||
match self {
|
||||
Tab::About => "".to_string(),
|
||||
tab => format!(" {} ", tab),
|
||||
Self::About => String::new(),
|
||||
tab => format!(" {tab} "),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ use std::cmp::min;
|
||||
|
||||
use derive_builder::Builder;
|
||||
use font8x8::UnicodeFonts;
|
||||
use ratatui::{prelude::*, text::StyledGrapheme, widgets::Widget};
|
||||
use ratatui::{prelude::*, text::StyledGrapheme};
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
|
||||
@@ -129,24 +129,24 @@ pub struct BigText<'a> {
|
||||
|
||||
/// The size of single glyphs
|
||||
///
|
||||
/// Defaults to `BigTextSize::default()` (=> BigTextSize::Full)
|
||||
/// Defaults to `BigTextSize::default()` (=> `BigTextSize::Full`)
|
||||
#[builder(default)]
|
||||
pixel_size: PixelSize,
|
||||
}
|
||||
|
||||
impl Widget for BigText<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let layout = layout(area, &self.pixel_size);
|
||||
let layout = layout(area, self.pixel_size);
|
||||
for (line, line_layout) in self.lines.iter().zip(layout) {
|
||||
for (g, cell) in line.styled_graphemes(self.style).zip(line_layout) {
|
||||
render_symbol(g, cell, buf, &self.pixel_size);
|
||||
render_symbol(&g, cell, buf, self.pixel_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns how many cells are needed to display a full 8x8 glyphe using the given font size
|
||||
fn cells_per_glyph(size: &PixelSize) -> (u16, u16) {
|
||||
const fn cells_per_glyph(size: PixelSize) -> (u16, u16) {
|
||||
match size {
|
||||
PixelSize::Full => (8, 8),
|
||||
PixelSize::HalfHeight => (8, 4),
|
||||
@@ -160,7 +160,7 @@ fn cells_per_glyph(size: &PixelSize) -> (u16, u16) {
|
||||
/// The size of each cell depends on given font size
|
||||
fn layout(
|
||||
area: Rect,
|
||||
pixel_size: &PixelSize,
|
||||
pixel_size: PixelSize,
|
||||
) -> impl IntoIterator<Item = impl IntoIterator<Item = Rect>> {
|
||||
let (width, height) = cells_per_glyph(pixel_size);
|
||||
(area.top()..area.bottom())
|
||||
@@ -178,7 +178,7 @@ fn layout(
|
||||
|
||||
/// Render a single grapheme into a cell by looking up the corresponding 8x8 bitmap in the
|
||||
/// `BITMAPS` array and setting the corresponding cells in the buffer.
|
||||
fn render_symbol(grapheme: StyledGrapheme, area: Rect, buf: &mut Buffer, pixel_size: &PixelSize) {
|
||||
fn render_symbol(grapheme: &StyledGrapheme, area: Rect, buf: &mut Buffer, pixel_size: PixelSize) {
|
||||
buf.set_style(area, grapheme.style);
|
||||
let c = grapheme.symbol.chars().next().unwrap(); // TODO: handle multi-char graphemes
|
||||
if let Some(glyph) = font8x8::BASIC_FONTS.get(c) {
|
||||
@@ -187,7 +187,7 @@ fn render_symbol(grapheme: StyledGrapheme, area: Rect, buf: &mut Buffer, pixel_s
|
||||
}
|
||||
|
||||
/// Get the correct unicode symbol for two vertical "pixels"
|
||||
fn get_symbol_half_height(top: u8, bottom: u8) -> char {
|
||||
const fn get_symbol_half_height(top: u8, bottom: u8) -> char {
|
||||
match top {
|
||||
0 => match bottom {
|
||||
0 => ' ',
|
||||
@@ -201,7 +201,7 @@ fn get_symbol_half_height(top: u8, bottom: u8) -> char {
|
||||
}
|
||||
|
||||
/// Get the correct unicode symbol for two horizontal "pixels"
|
||||
fn get_symbol_half_width(left: u8, right: u8) -> char {
|
||||
const fn get_symbol_half_width(left: u8, right: u8) -> char {
|
||||
match left {
|
||||
0 => match right {
|
||||
0 => ' ',
|
||||
@@ -215,20 +215,26 @@ fn get_symbol_half_width(left: u8, right: u8) -> char {
|
||||
}
|
||||
|
||||
/// Get the correct unicode symbol for 2x2 "pixels"
|
||||
fn get_symbol_half_size(top_left: u8, top_right: u8, bottom_left: u8, bottom_right: u8) -> char {
|
||||
let top_left = if top_left > 0 { 1 } else { 0 };
|
||||
let top_right = if top_right > 0 { 1 } else { 0 };
|
||||
let bottom_left = if bottom_left > 0 { 1 } else { 0 };
|
||||
let bottom_right = if bottom_right > 0 { 1 } else { 0 };
|
||||
|
||||
const fn get_symbol_half_size(
|
||||
top_left: u8,
|
||||
top_right: u8,
|
||||
bottom_left: u8,
|
||||
bottom_right: u8,
|
||||
) -> char {
|
||||
const QUADRANT_SYMBOLS: [char; 16] = [
|
||||
' ', '▘', '▝', '▀', '▖', '▌', '▞', '▛', '▗', '▚', '▐', '▜', '▄', '▙', '▟', '█',
|
||||
];
|
||||
QUADRANT_SYMBOLS[top_left + (top_right << 1) + (bottom_left << 2) + (bottom_right << 3)]
|
||||
|
||||
let top_left = if top_left > 0 { 1 } else { 0 };
|
||||
let top_right = if top_right > 0 { 1 << 1 } else { 0 };
|
||||
let bottom_left = if bottom_left > 0 { 1 << 2 } else { 0 };
|
||||
let bottom_right = if bottom_right > 0 { 1 << 3 } else { 0 };
|
||||
|
||||
QUADRANT_SYMBOLS[top_left + top_right + bottom_left + bottom_right]
|
||||
}
|
||||
|
||||
/// Render a single 8x8 glyph into a cell by setting the corresponding cells in the buffer.
|
||||
fn render_glyph(glyph: [u8; 8], area: Rect, buf: &mut Buffer, pixel_size: &PixelSize) {
|
||||
fn render_glyph(glyph: [u8; 8], area: Rect, buf: &mut Buffer, pixel_size: PixelSize) {
|
||||
let (width, height) = cells_per_glyph(pixel_size);
|
||||
|
||||
let glyph_vertical_index = (0..glyph.len()).step_by(8 / height as usize);
|
||||
|
||||
@@ -9,13 +9,14 @@ use ratatui::prelude::*;
|
||||
pub struct RgbSwatch;
|
||||
|
||||
impl Widget for RgbSwatch {
|
||||
#[allow(clippy::cast_precision_loss, clippy::similar_names)]
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
for (yi, y) in (area.top()..area.bottom()).enumerate() {
|
||||
let value = area.height as f32 - yi as f32;
|
||||
let value_fg = value / (area.height as f32);
|
||||
let value_bg = (value - 0.5) / (area.height as f32);
|
||||
let value = f32::from(area.height) - yi as f32;
|
||||
let value_fg = value / f32::from(area.height);
|
||||
let value_bg = (value - 0.5) / f32::from(area.height);
|
||||
for (xi, x) in (area.left()..area.right()).enumerate() {
|
||||
let hue = xi as f32 * 360.0 / area.width as f32;
|
||||
let hue = xi as f32 * 360.0 / f32::from(area.width);
|
||||
let fg = color_from_oklab(hue, Okhsv::max_saturation(), value_fg);
|
||||
let bg = color_from_oklab(hue, Okhsv::max_saturation(), value_bg);
|
||||
buf.get_mut(x, y).set_char('▀').set_fg(fg).set_bg(bg);
|
||||
@@ -24,7 +25,7 @@ impl Widget for RgbSwatch {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a hue and value into an RGB color via the OkLab color space.
|
||||
/// Convert a hue and value into an RGB color via the Oklab color space.
|
||||
///
|
||||
/// See <https://bottosson.github.io/posts/oklab/> for more details.
|
||||
pub fn color_from_oklab(hue: f32, saturation: f32, value: f32) -> Color {
|
||||
|
||||
@@ -30,11 +30,16 @@ pub fn destroy(frame: &mut Frame<'_>) {
|
||||
///
|
||||
/// Each pick some random pixels and move them each down one row. This is a very inefficient way to
|
||||
/// do this, but it works well enough for this demo.
|
||||
#[allow(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_precision_loss,
|
||||
clippy::cast_sign_loss
|
||||
)]
|
||||
fn drip(frame_count: usize, area: Rect, buf: &mut Buffer) {
|
||||
// a seeded rng as we have to move the same random pixels each frame
|
||||
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(10);
|
||||
let ramp_frames = 450;
|
||||
let fractional_speed = frame_count as f64 / ramp_frames as f64;
|
||||
let fractional_speed = frame_count as f64 / f64::from(ramp_frames);
|
||||
let variable_speed = DRIP_SPEED as f64 * fractional_speed * fractional_speed * fractional_speed;
|
||||
let pixel_count = (frame_count as f64 * variable_speed).floor() as usize;
|
||||
for _ in 0..pixel_count {
|
||||
@@ -68,6 +73,7 @@ fn drip(frame_count: usize, area: Rect, buf: &mut Buffer) {
|
||||
}
|
||||
|
||||
/// draw some text fading in and out from black to red and back
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
|
||||
fn text(frame_count: usize, area: Rect, buf: &mut Buffer) {
|
||||
let sub_frame = frame_count.saturating_sub(TEXT_DELAY);
|
||||
if sub_frame == 0 {
|
||||
@@ -114,10 +120,13 @@ fn blend(mask_color: Color, cell_color: Color, percentage: f64) -> Color {
|
||||
return mask_color;
|
||||
};
|
||||
|
||||
let red = mask_red as f64 * percentage + cell_red as f64 * (1.0 - percentage);
|
||||
let green = mask_green as f64 * percentage + cell_green as f64 * (1.0 - percentage);
|
||||
let blue = mask_blue as f64 * percentage + cell_blue as f64 * (1.0 - percentage);
|
||||
let remain = 1.0 - percentage;
|
||||
|
||||
let red = f64::from(mask_red).mul_add(percentage, f64::from(cell_red) * remain);
|
||||
let green = f64::from(mask_green).mul_add(percentage, f64::from(cell_green) * remain);
|
||||
let blue = f64::from(mask_blue).mul_add(percentage, f64::from(cell_blue) * remain);
|
||||
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||
Color::Rgb(red as u8, green as u8, blue as u8)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ pub fn init_hooks() -> Result<()> {
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = term::restore();
|
||||
panic(info)
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,6 +13,14 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
#![allow(
|
||||
clippy::enum_glob_use,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::must_use_candidate,
|
||||
clippy::wildcard_imports
|
||||
)]
|
||||
|
||||
mod app;
|
||||
mod big_text;
|
||||
mod colors;
|
||||
|
||||
@@ -97,10 +97,11 @@ fn render_crate_description(area: Rect, buf: &mut Buffer) {
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
/// Use half block characters to render a logo based on the RATATUI_LOGO const.
|
||||
/// Use half block characters to render a logo based on the `RATATUI_LOGO` const.
|
||||
///
|
||||
/// The logo is rendered in three colors, one for the rat, one for the terminal, and one for the
|
||||
/// rat's eye. The eye color alternates between two colors based on the selected row.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
pub fn render_logo(selected_row: usize, area: Rect, buf: &mut Buffer) {
|
||||
let eye_color = if selected_row % 2 == 0 {
|
||||
THEME.logo.rat_eye
|
||||
|
||||
@@ -10,6 +10,7 @@ struct Ingredient {
|
||||
}
|
||||
|
||||
impl Ingredient {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn height(&self) -> u16 {
|
||||
self.name.lines().count() as u16
|
||||
}
|
||||
@@ -148,7 +149,7 @@ fn render_recipe(area: Rect, buf: &mut Buffer) {
|
||||
|
||||
fn render_ingredients(selected_row: usize, area: Rect, buf: &mut Buffer) {
|
||||
let mut state = TableState::default().with_selected(Some(selected_row));
|
||||
let rows = INGREDIENTS.iter().cloned();
|
||||
let rows = INGREDIENTS.iter().copied();
|
||||
let theme = THEME.recipe;
|
||||
StatefulWidget::render(
|
||||
Table::new(rows, [Constraint::Length(7), Constraint::Length(30)])
|
||||
@@ -171,5 +172,5 @@ fn render_scrollbar(position: usize, area: Rect, buf: &mut Buffer) {
|
||||
.end_symbol(None)
|
||||
.track_symbol(None)
|
||||
.thumb_symbol("▐")
|
||||
.render(area, buf, &mut state)
|
||||
.render(area, buf, &mut state);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ fn render_simple_barchart(area: Rect, buf: &mut Buffer) {
|
||||
// This doesn't actually render correctly as the text is too wide for the bar
|
||||
// See https://github.com/ratatui-org/ratatui/issues/513 for more info
|
||||
// (the demo GIFs hack around this by hacking the calculation in bars.rs)
|
||||
.text_value(format!("{}°", value))
|
||||
.text_value(format!("{value}°"))
|
||||
.style(if value > 70 {
|
||||
Style::new().fg(Color::Red)
|
||||
} else {
|
||||
@@ -128,12 +128,14 @@ fn render_horizontal_barchart(area: Rect, buf: &mut Buffer) {
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
pub fn render_gauge(progress: usize, area: Rect, buf: &mut Buffer) {
|
||||
let percent = (progress * 3).min(100) as f64;
|
||||
|
||||
render_line_gauge(percent, area, buf);
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn render_line_gauge(percent: f64, area: Rect, buf: &mut Buffer) {
|
||||
// cycle color hue based on the percent for a neat effect yellow -> red
|
||||
let hue = 90.0 - (percent as f32 * 0.6);
|
||||
@@ -141,7 +143,7 @@ fn render_line_gauge(percent: f64, area: Rect, buf: &mut Buffer) {
|
||||
let fg = color_from_oklab(hue, Okhsv::max_saturation(), value);
|
||||
let bg = color_from_oklab(hue, Okhsv::max_saturation(), value * 0.5);
|
||||
let label = if percent < 100.0 {
|
||||
format!("Downloading: {}%", percent)
|
||||
format!("Downloading: {percent}%")
|
||||
} else {
|
||||
"Download Complete!".into()
|
||||
};
|
||||
|
||||
@@ -19,7 +19,10 @@ use crossterm::{
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
};
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
};
|
||||
|
||||
/// Example code for lib.rs
|
||||
///
|
||||
@@ -34,7 +37,6 @@ fn main() -> io::Result<()> {
|
||||
let mut should_quit = false;
|
||||
while !should_quit {
|
||||
terminal.draw(match arg.as_str() {
|
||||
"hello_world" => hello_world,
|
||||
"layout" => layout,
|
||||
"styling" => styling,
|
||||
_ => hello_world,
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
#![allow(clippy::enum_glob_use, clippy::wildcard_imports)]
|
||||
|
||||
use std::io::{self, stdout};
|
||||
|
||||
use color_eyre::{config::HookBuilder, Result};
|
||||
@@ -165,7 +167,7 @@ impl App {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
fn is_running(self) -> bool {
|
||||
self.state == AppState::Running
|
||||
}
|
||||
|
||||
@@ -203,14 +205,14 @@ impl App {
|
||||
}
|
||||
|
||||
fn up(&mut self) {
|
||||
self.scroll_offset = self.scroll_offset.saturating_sub(1)
|
||||
self.scroll_offset = self.scroll_offset.saturating_sub(1);
|
||||
}
|
||||
|
||||
fn down(&mut self) {
|
||||
self.scroll_offset = self
|
||||
.scroll_offset
|
||||
.saturating_add(1)
|
||||
.min(max_scroll_offset())
|
||||
.min(max_scroll_offset());
|
||||
}
|
||||
|
||||
fn top(&mut self) {
|
||||
@@ -239,8 +241,7 @@ fn max_scroll_offset() -> u16 {
|
||||
example_height()
|
||||
- EXAMPLE_DATA
|
||||
.last()
|
||||
.map(|(desc, _)| get_description_height(desc) + 4)
|
||||
.unwrap_or(0)
|
||||
.map_or(0, |(desc, _)| get_description_height(desc) + 4)
|
||||
}
|
||||
|
||||
/// The height of all examples combined
|
||||
@@ -264,12 +265,12 @@ impl Widget for App {
|
||||
} else {
|
||||
axis.width
|
||||
};
|
||||
self.axis(axis_width, self.spacing).render(axis, buf);
|
||||
Self::axis(axis_width, self.spacing).render(axis, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn tabs(&self) -> impl Widget {
|
||||
fn tabs(self) -> impl Widget {
|
||||
let tab_titles = SelectedTab::iter().map(SelectedTab::to_tab_title);
|
||||
let block = Block::new()
|
||||
.title(Title::from("Flex Layouts ".bold()))
|
||||
@@ -283,13 +284,13 @@ impl App {
|
||||
}
|
||||
|
||||
/// a bar like `<----- 80 px (gap: 2 px)? ----->`
|
||||
fn axis(&self, width: u16, spacing: u16) -> impl Widget {
|
||||
fn axis(width: u16, spacing: u16) -> impl Widget {
|
||||
let width = width as usize;
|
||||
// only show gap when spacing is not zero
|
||||
let label = if spacing != 0 {
|
||||
format!("{} px (gap: {} px)", width, spacing)
|
||||
format!("{width} px (gap: {spacing} px)")
|
||||
} else {
|
||||
format!("{} px", width)
|
||||
format!("{width} px")
|
||||
};
|
||||
let bar_width = width.saturating_sub(2); // we want to `<` and `>` at the ends
|
||||
let width_bar = format!("<{label:-^bar_width$}>");
|
||||
@@ -302,6 +303,7 @@ impl App {
|
||||
/// into the main buffer. This is done to make it possible to handle scrolling easily.
|
||||
///
|
||||
/// Returns bool indicating whether scroll was needed
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn render_demo(self, area: Rect, buf: &mut Buffer) -> bool {
|
||||
// render demo content into a separate buffer so all examples fit we add an extra
|
||||
// area.height to make sure the last example is fully visible even when the scroll offset is
|
||||
@@ -347,31 +349,30 @@ impl App {
|
||||
|
||||
impl SelectedTab {
|
||||
/// Get the previous tab, if there is no previous tab return the current tab.
|
||||
fn previous(&self) -> Self {
|
||||
let current_index: usize = *self as usize;
|
||||
fn previous(self) -> Self {
|
||||
let current_index: usize = self as usize;
|
||||
let previous_index = current_index.saturating_sub(1);
|
||||
Self::from_repr(previous_index).unwrap_or(*self)
|
||||
Self::from_repr(previous_index).unwrap_or(self)
|
||||
}
|
||||
|
||||
/// Get the next tab, if there is no next tab return the current tab.
|
||||
fn next(&self) -> Self {
|
||||
let current_index = *self as usize;
|
||||
fn next(self) -> Self {
|
||||
let current_index = self as usize;
|
||||
let next_index = current_index.saturating_add(1);
|
||||
Self::from_repr(next_index).unwrap_or(*self)
|
||||
Self::from_repr(next_index).unwrap_or(self)
|
||||
}
|
||||
|
||||
/// Convert a `SelectedTab` into a `Line` to display it by the `Tabs` widget.
|
||||
fn to_tab_title(value: SelectedTab) -> Line<'static> {
|
||||
fn to_tab_title(value: Self) -> Line<'static> {
|
||||
use tailwind::*;
|
||||
use SelectedTab::*;
|
||||
let text = value.to_string();
|
||||
let color = match value {
|
||||
Legacy => ORANGE.c400,
|
||||
Start => SKY.c400,
|
||||
Center => SKY.c300,
|
||||
End => SKY.c200,
|
||||
SpaceAround => INDIGO.c400,
|
||||
SpaceBetween => INDIGO.c300,
|
||||
Self::Legacy => ORANGE.c400,
|
||||
Self::Start => SKY.c400,
|
||||
Self::Center => SKY.c300,
|
||||
Self::End => SKY.c200,
|
||||
Self::SpaceAround => INDIGO.c400,
|
||||
Self::SpaceBetween => INDIGO.c300,
|
||||
};
|
||||
format!(" {text} ").fg(color).bg(Color::Black).into()
|
||||
}
|
||||
@@ -382,20 +383,18 @@ impl StatefulWidget for SelectedTab {
|
||||
fn render(self, area: Rect, buf: &mut Buffer, spacing: &mut Self::State) {
|
||||
let spacing = *spacing;
|
||||
match self {
|
||||
SelectedTab::Legacy => self.render_examples(area, buf, Flex::Legacy, spacing),
|
||||
SelectedTab::Start => self.render_examples(area, buf, Flex::Start, spacing),
|
||||
SelectedTab::Center => self.render_examples(area, buf, Flex::Center, spacing),
|
||||
SelectedTab::End => self.render_examples(area, buf, Flex::End, spacing),
|
||||
SelectedTab::SpaceAround => self.render_examples(area, buf, Flex::SpaceAround, spacing),
|
||||
SelectedTab::SpaceBetween => {
|
||||
self.render_examples(area, buf, Flex::SpaceBetween, spacing)
|
||||
}
|
||||
Self::Legacy => Self::render_examples(area, buf, Flex::Legacy, spacing),
|
||||
Self::Start => Self::render_examples(area, buf, Flex::Start, spacing),
|
||||
Self::Center => Self::render_examples(area, buf, Flex::Center, spacing),
|
||||
Self::End => Self::render_examples(area, buf, Flex::End, spacing),
|
||||
Self::SpaceAround => Self::render_examples(area, buf, Flex::SpaceAround, spacing),
|
||||
Self::SpaceBetween => Self::render_examples(area, buf, Flex::SpaceBetween, spacing),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectedTab {
|
||||
fn render_examples(&self, area: Rect, buf: &mut Buffer, flex: Flex, spacing: u16) {
|
||||
fn render_examples(area: Rect, buf: &mut Buffer, flex: Flex, spacing: u16) {
|
||||
let heights = EXAMPLE_DATA
|
||||
.iter()
|
||||
.map(|(desc, _)| get_description_height(desc) + 4);
|
||||
@@ -432,7 +431,7 @@ impl Widget for Example {
|
||||
Paragraph::new(
|
||||
self.description
|
||||
.split('\n')
|
||||
.map(|s| format!("// {}", s).italic().fg(tailwind::SLATE.c400))
|
||||
.map(|s| format!("// {s}").italic().fg(tailwind::SLATE.c400))
|
||||
.map(Line::from)
|
||||
.collect::<Vec<Line>>(),
|
||||
)
|
||||
@@ -440,18 +439,17 @@ impl Widget for Example {
|
||||
}
|
||||
|
||||
for (block, constraint) in blocks.iter().zip(&self.constraints) {
|
||||
self.illustration(*constraint, block.width)
|
||||
.render(*block, buf);
|
||||
Self::illustration(*constraint, block.width).render(*block, buf);
|
||||
}
|
||||
|
||||
for spacer in spacers.iter() {
|
||||
self.render_spacer(*spacer, buf);
|
||||
Self::render_spacer(*spacer, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Example {
|
||||
fn render_spacer(&self, spacer: Rect, buf: &mut Buffer) {
|
||||
fn render_spacer(spacer: Rect, buf: &mut Buffer) {
|
||||
if spacer.width > 1 {
|
||||
let corners_only = symbols::border::Set {
|
||||
top_left: line::NORMAL.top_left,
|
||||
@@ -483,7 +481,7 @@ impl Example {
|
||||
} else if width > 2 {
|
||||
format!("{width}")
|
||||
} else {
|
||||
"".to_string()
|
||||
String::new()
|
||||
};
|
||||
let text = Text::from(vec![
|
||||
Line::raw(""),
|
||||
@@ -496,7 +494,7 @@ impl Example {
|
||||
.render(spacer, buf);
|
||||
}
|
||||
|
||||
fn illustration(&self, constraint: Constraint, width: u16) -> Paragraph {
|
||||
fn illustration(constraint: Constraint, width: u16) -> impl Widget {
|
||||
let main_color = color_for_constraint(constraint);
|
||||
let fg_color = Color::White;
|
||||
let title = format!("{constraint}");
|
||||
@@ -510,7 +508,7 @@ impl Example {
|
||||
}
|
||||
}
|
||||
|
||||
fn color_for_constraint(constraint: Constraint) -> Color {
|
||||
const fn color_for_constraint(constraint: Constraint) -> Color {
|
||||
use tailwind::*;
|
||||
match constraint {
|
||||
Constraint::Min(_) => BLUE.c900,
|
||||
@@ -532,7 +530,7 @@ fn init_error_hooks() -> Result<()> {
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info)
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
@@ -551,6 +549,7 @@ fn restore_terminal() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn get_description_height(s: &str) -> u16 {
|
||||
if s.is_empty() {
|
||||
0
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
#![allow(clippy::enum_glob_use)]
|
||||
|
||||
use std::{io::stdout, time::Duration};
|
||||
|
||||
use color_eyre::{config::HookBuilder, Result};
|
||||
@@ -24,7 +26,7 @@ use crossterm::{
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
style::palette::tailwind,
|
||||
widgets::{block::Title, *},
|
||||
widgets::{block::Title, Block, Borders, Gauge, Padding, Paragraph},
|
||||
};
|
||||
|
||||
const GAUGE1_COLOR: Color = tailwind::RED.c800;
|
||||
@@ -84,7 +86,7 @@ impl App {
|
||||
// difference between how a continuous gauge acts for floor and rounded values.
|
||||
self.progress_columns = (self.progress_columns + 1).clamp(0, terminal_width);
|
||||
self.progress1 = self.progress_columns * 100 / terminal_width;
|
||||
self.progress2 = self.progress_columns as f64 * 100.0 / terminal_width as f64;
|
||||
self.progress2 = f64::from(self.progress_columns) * 100.0 / f64::from(terminal_width);
|
||||
|
||||
// progress3 and progress4 similarly show the difference between unicode and non-unicode
|
||||
// gauges measuring the same thing.
|
||||
@@ -119,6 +121,7 @@ impl App {
|
||||
}
|
||||
|
||||
impl Widget for &App {
|
||||
#[allow(clippy::similar_names)]
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
use Constraint::*;
|
||||
let layout = Layout::vertical([Length(2), Min(0), Length(1)]);
|
||||
@@ -127,8 +130,8 @@ impl Widget for &App {
|
||||
let layout = Layout::vertical([Ratio(1, 4); 4]);
|
||||
let [gauge1_area, gauge2_area, gauge3_area, gauge4_area] = layout.areas(gauge_area);
|
||||
|
||||
self.render_header(header_area, buf);
|
||||
self.render_footer(footer_area, buf);
|
||||
render_header(header_area, buf);
|
||||
render_footer(footer_area, buf);
|
||||
|
||||
self.render_gauge1(gauge1_area, buf);
|
||||
self.render_gauge2(gauge2_area, buf);
|
||||
@@ -137,23 +140,23 @@ impl Widget for &App {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_header(area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new("Ratatui Gauge Example")
|
||||
.bold()
|
||||
.alignment(Alignment::Center)
|
||||
.fg(CUSTOM_LABEL_COLOR)
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn render_footer(area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new("Press ENTER to start")
|
||||
.alignment(Alignment::Center)
|
||||
.fg(CUSTOM_LABEL_COLOR)
|
||||
.bold()
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn render_header(&self, area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new("Ratatui Gauge Example")
|
||||
.bold()
|
||||
.alignment(Alignment::Center)
|
||||
.fg(CUSTOM_LABEL_COLOR)
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn render_footer(&self, area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new("Press ENTER to start")
|
||||
.alignment(Alignment::Center)
|
||||
.fg(CUSTOM_LABEL_COLOR)
|
||||
.bold()
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn render_gauge1(&self, area: Rect, buf: &mut Buffer) {
|
||||
let title = title_block("Gauge with percentage");
|
||||
Gauge::default()
|
||||
@@ -220,7 +223,7 @@ fn init_error_hooks() -> color_eyre::Result<()> {
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info)
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ use crossterm::{
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{prelude::*, widgets::Paragraph};
|
||||
|
||||
/// This is a bare minimum example. There are many approaches to running an application loop, so
|
||||
/// this is not meant to be prescriptive. It is only meant to demonstrate the basic setup and
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
#![allow(clippy::wildcard_imports)]
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, VecDeque},
|
||||
error::Error,
|
||||
@@ -129,6 +131,7 @@ fn input_handling(tx: mpsc::Sender<Event>) {
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_precision_loss, clippy::needless_pass_by_value)]
|
||||
fn workers(tx: mpsc::Sender<Event>) -> Vec<Worker> {
|
||||
(0..4)
|
||||
.map(|id| {
|
||||
@@ -168,6 +171,7 @@ fn downloads() -> Downloads {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn run_app<B: Backend>(
|
||||
terminal: &mut Terminal<B>,
|
||||
workers: Vec<Worker>,
|
||||
@@ -194,7 +198,7 @@ fn run_app<B: Backend>(
|
||||
Event::DownloadUpdate(worker_id, _download_id, progress) => {
|
||||
let download = downloads.in_progress.get_mut(&worker_id).unwrap();
|
||||
download.progress = progress;
|
||||
redraw = false
|
||||
redraw = false;
|
||||
}
|
||||
Event::DownloadDone(worker_id, download_id) => {
|
||||
let download = downloads.in_progress.remove(&worker_id).unwrap();
|
||||
@@ -242,6 +246,7 @@ fn ui(f: &mut Frame, downloads: &Downloads) {
|
||||
|
||||
// total progress
|
||||
let done = NUM_DOWNLOADS - downloads.pending.len() - downloads.in_progress.len();
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
let progress = LineGauge::default()
|
||||
.gauge_style(Style::default().fg(Color::Blue))
|
||||
.label(format!("{done}/{NUM_DOWNLOADS}"))
|
||||
@@ -271,6 +276,7 @@ fn ui(f: &mut Frame, downloads: &Downloads) {
|
||||
let list = List::new(items);
|
||||
f.render_widget(list, list_area);
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
for (i, (_, download)) in downloads.in_progress.iter().enumerate() {
|
||||
let gauge = Gauge::default()
|
||||
.gauge_style(Style::default().fg(Color::Yellow))
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
#![allow(clippy::enum_glob_use)]
|
||||
|
||||
use std::{error::Error, io};
|
||||
|
||||
use crossterm::{
|
||||
@@ -21,7 +23,11 @@ use crossterm::{
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use ratatui::{layout::Constraint::*, prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
layout::Constraint::*,
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
@@ -55,13 +61,14 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||
terminal.draw(ui)?;
|
||||
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if let KeyCode::Char('q') = key.code {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn ui(frame: &mut Frame) {
|
||||
let vertical = Layout::vertical([
|
||||
Length(4), // text
|
||||
@@ -94,12 +101,12 @@ fn ui(frame: &mut Frame) {
|
||||
.iter()
|
||||
.flat_map(|area| {
|
||||
Layout::horizontal([
|
||||
Constraint::Length(14),
|
||||
Constraint::Length(14),
|
||||
Constraint::Length(14),
|
||||
Constraint::Length(14),
|
||||
Constraint::Length(14),
|
||||
Constraint::Min(0), // fills remaining space
|
||||
Length(14),
|
||||
Length(14),
|
||||
Length(14),
|
||||
Length(14),
|
||||
Length(14),
|
||||
Min(0), // fills remaining space
|
||||
])
|
||||
.split(*area)
|
||||
.iter()
|
||||
@@ -191,8 +198,8 @@ fn render_example_combination(
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(block, area);
|
||||
let layout = Layout::vertical(vec![Length(1); constraints.len() + 1]).split(inner);
|
||||
for (i, (a, b)) in constraints.iter().enumerate() {
|
||||
render_single_example(frame, layout[i], vec![*a, *b, Min(0)]);
|
||||
for (i, (a, b)) in constraints.into_iter().enumerate() {
|
||||
render_single_example(frame, layout[i], vec![a, b, Min(0)]);
|
||||
}
|
||||
// This is to make it easy to visually see the alignment of the examples
|
||||
// with the constraints.
|
||||
@@ -213,11 +220,11 @@ fn render_single_example(frame: &mut Frame, area: Rect, constraints: Vec<Constra
|
||||
|
||||
fn constraint_label(constraint: Constraint) -> String {
|
||||
match constraint {
|
||||
Length(n) => format!("{n}"),
|
||||
Min(n) => format!("{n}"),
|
||||
Max(n) => format!("{n}"),
|
||||
Percentage(n) => format!("{n}"),
|
||||
Fill(n) => format!("{n}"),
|
||||
Ratio(a, b) => format!("{a}:{b}"),
|
||||
Constraint::Ratio(a, b) => format!("{a}:{b}"),
|
||||
Constraint::Length(n)
|
||||
| Constraint::Min(n)
|
||||
| Constraint::Max(n)
|
||||
| Constraint::Percentage(n)
|
||||
| Constraint::Fill(n) => format!("{n}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
#![allow(clippy::enum_glob_use, clippy::wildcard_imports)]
|
||||
|
||||
use std::{error::Error, io, io::stdout};
|
||||
|
||||
use color_eyre::config::HookBuilder;
|
||||
@@ -81,7 +83,7 @@ fn init_error_hooks() -> color_eyre::Result<()> {
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info)
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
@@ -100,9 +102,9 @@ fn restore_terminal() -> color_eyre::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl App<'_> {
|
||||
fn new<'a>() -> App<'a> {
|
||||
App {
|
||||
impl<'a> App<'a> {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
items: StatefulList::with_items([
|
||||
("Rewrite everything with Rust!", "I can't hold my inner voice. He tells me to rewrite the complete universe with Rust", Status::Todo),
|
||||
("Rewrite all of your tui apps with Ratatui", "Yes, you heard that right. Go and replace your tui with Ratatui.", Status::Completed),
|
||||
@@ -125,11 +127,11 @@ impl App<'_> {
|
||||
}
|
||||
|
||||
fn go_top(&mut self) {
|
||||
self.items.state.select(Some(0))
|
||||
self.items.state.select(Some(0));
|
||||
}
|
||||
|
||||
fn go_bottom(&mut self) {
|
||||
self.items.state.select(Some(self.items.items.len() - 1))
|
||||
self.items.state.select(Some(self.items.items.len() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,21 +179,14 @@ impl Widget for &mut App<'_> {
|
||||
let vertical = Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]);
|
||||
let [upper_item_list_area, lower_item_list_area] = vertical.areas(rest_area);
|
||||
|
||||
self.render_title(header_area, buf);
|
||||
render_title(header_area, buf);
|
||||
self.render_todo(upper_item_list_area, buf);
|
||||
self.render_info(lower_item_list_area, buf);
|
||||
self.render_footer(footer_area, buf);
|
||||
render_footer(footer_area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl App<'_> {
|
||||
fn render_title(&self, area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new("Ratatui List Example")
|
||||
.bold()
|
||||
.centered()
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn render_todo(&mut self, area: Rect, buf: &mut Buffer) {
|
||||
// We create two blocks, one is for the header (outer) and the other is for list (inner).
|
||||
let outer_block = Block::default()
|
||||
@@ -278,14 +273,19 @@ impl App<'_> {
|
||||
// We can now render the item info
|
||||
info_paragraph.render(inner_info_area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_footer(&self, area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new(
|
||||
"\nUse ↓↑ to move, ← to unselect, → to change status, g/G to go top/bottom.",
|
||||
)
|
||||
fn render_title(area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new("Ratatui List Example")
|
||||
.bold()
|
||||
.centered()
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn render_footer(area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new("\nUse ↓↑ to move, ← to unselect, → to change status, g/G to go top/bottom.")
|
||||
.centered()
|
||||
.render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulList<'_> {
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
/// This example is useful for testing how your terminal emulator handles different modifiers.
|
||||
/// It will render a grid of combinations of foreground and background colors with all
|
||||
/// modifiers applied to them.
|
||||
// This example is useful for testing how your terminal emulator handles different modifiers.
|
||||
// It will render a grid of combinations of foreground and background colors with all
|
||||
// modifiers applied to them.
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
io::{self, Stdout},
|
||||
@@ -30,7 +31,7 @@ use crossterm::{
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{prelude::*, widgets::Paragraph};
|
||||
|
||||
type Result<T> = result::Result<T, Box<dyn Error>>;
|
||||
|
||||
@@ -50,7 +51,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||
|
||||
if event::poll(Duration::from_millis(250))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if let KeyCode::Char('q') = key.code {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -87,8 +88,8 @@ fn ui(frame: &mut Frame) {
|
||||
.chain(Modifier::all().iter())
|
||||
.collect_vec();
|
||||
let mut index = 0;
|
||||
for bg in colors.iter() {
|
||||
for fg in colors.iter() {
|
||||
for bg in &colors {
|
||||
for fg in &colors {
|
||||
for modifier in &all_modifiers {
|
||||
let modifier_name = format!("{modifier:11?}");
|
||||
let padding = (" ").repeat(12 - modifier_name.len());
|
||||
|
||||
@@ -35,7 +35,10 @@ use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
};
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
||||
|
||||
|
||||
@@ -24,15 +24,18 @@ use crossterm::{
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, Paragraph, Wrap},
|
||||
};
|
||||
|
||||
struct App {
|
||||
scroll: u16,
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn new() -> App {
|
||||
App { scroll: 0 }
|
||||
const fn new() -> Self {
|
||||
Self { scroll: 0 }
|
||||
}
|
||||
|
||||
fn on_tick(&mut self) {
|
||||
@@ -82,7 +85,7 @@ fn run_app<B: Backend>(
|
||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||
if crossterm::event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if let KeyCode::Char('q') = key.code {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
/// See also https://github.com/joshka/tui-popup and
|
||||
/// https://github.com/sephiroth74/tui-confirm-dialog
|
||||
// See also https://github.com/joshka/tui-popup and
|
||||
// https://github.com/sephiroth74/tui-confirm-dialog
|
||||
|
||||
use std::{error::Error, io};
|
||||
|
||||
use crossterm::{
|
||||
@@ -22,15 +23,18 @@ use crossterm::{
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, Clear, Paragraph, Wrap},
|
||||
};
|
||||
|
||||
struct App {
|
||||
show_popup: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn new() -> App {
|
||||
App { show_popup: false }
|
||||
const fn new() -> Self {
|
||||
Self { show_popup: false }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,49 +22,46 @@ use std::{
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use indoc::indoc;
|
||||
use itertools::izip;
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{prelude::*, widgets::Paragraph};
|
||||
|
||||
/// A fun example of using half block characters to draw a logo
|
||||
fn main() -> io::Result<()> {
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn logo() -> String {
|
||||
let r = indoc! {"
|
||||
▄▄▄
|
||||
█▄▄▀
|
||||
█ █
|
||||
"}
|
||||
.lines();
|
||||
"};
|
||||
let a = indoc! {"
|
||||
▄▄
|
||||
█▄▄█
|
||||
█ █
|
||||
"}
|
||||
.lines();
|
||||
"};
|
||||
let t = indoc! {"
|
||||
▄▄▄
|
||||
█
|
||||
█
|
||||
"}
|
||||
.lines();
|
||||
"};
|
||||
let u = indoc! {"
|
||||
▄ ▄
|
||||
█ █
|
||||
▀▄▄▀
|
||||
"}
|
||||
.lines();
|
||||
"};
|
||||
let i = indoc! {"
|
||||
▄
|
||||
█
|
||||
█
|
||||
"}
|
||||
.lines();
|
||||
"};
|
||||
izip!(r.lines(), a.lines(), t.lines(), u.lines(), i.lines())
|
||||
.map(|(r, a, t, u, i)| format!("{r:5}{a:5}{t:4}{a:5}{t:4}{u:5}{i:5}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let mut terminal = init()?;
|
||||
terminal.draw(|frame| {
|
||||
let logo = izip!(r, a.clone(), t.clone(), a, t, u, i)
|
||||
.map(|(r, a, t, a2, t2, u, i)| {
|
||||
format!("{:5}{:5}{:4}{:5}{:4}{:5}{:5}", r, a, t, a2, t2, u, i)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
frame.render_widget(Paragraph::new(logo), frame.size());
|
||||
frame.render_widget(Paragraph::new(logo()), frame.size());
|
||||
})?;
|
||||
sleep(Duration::from_secs(5));
|
||||
restore()?;
|
||||
@@ -72,7 +69,7 @@ fn main() -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init() -> io::Result<Terminal<impl Backend>> {
|
||||
fn init() -> io::Result<Terminal<impl Backend>> {
|
||||
enable_raw_mode()?;
|
||||
let options = TerminalOptions {
|
||||
viewport: Viewport::Inline(3),
|
||||
@@ -80,7 +77,7 @@ pub fn init() -> io::Result<Terminal<impl Backend>> {
|
||||
Terminal::with_options(CrosstermBackend::new(stdout()), options)
|
||||
}
|
||||
|
||||
pub fn restore() -> io::Result<()> {
|
||||
fn restore() -> io::Result<()> {
|
||||
disable_raw_mode()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
#![warn(clippy::pedantic)]
|
||||
#![allow(clippy::wildcard_imports)]
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
io,
|
||||
@@ -107,6 +110,7 @@ fn run_app<B: Backend>(
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines, clippy::cast_possible_truncation)]
|
||||
fn ui(f: &mut Frame, app: &mut App) {
|
||||
let size = f.size();
|
||||
|
||||
@@ -133,10 +137,7 @@ fn ui(f: &mut Frame, app: &mut App) {
|
||||
Line::from("This is a line".reset()),
|
||||
Line::from(vec![
|
||||
Span::raw("Masked text: "),
|
||||
Span::styled(
|
||||
Masked::new("password", '*'),
|
||||
Style::default().fg(Color::Red),
|
||||
),
|
||||
Span::styled(Masked::new("password", '*'), Style::new().fg(Color::Red)),
|
||||
]),
|
||||
Line::from("This is a line "),
|
||||
Line::from("This is a line ".red()),
|
||||
@@ -146,10 +147,7 @@ fn ui(f: &mut Frame, app: &mut App) {
|
||||
Line::from("This is a line".reset()),
|
||||
Line::from(vec![
|
||||
Span::raw("Masked text: "),
|
||||
Span::styled(
|
||||
Masked::new("password", '*'),
|
||||
Style::default().fg(Color::Red),
|
||||
),
|
||||
Span::styled(Masked::new("password", '*'), Style::new().fg(Color::Red)),
|
||||
]),
|
||||
];
|
||||
app.vertical_scroll_state = app.vertical_scroll_state.content_length(text.len());
|
||||
@@ -157,9 +155,9 @@ fn ui(f: &mut Frame, app: &mut App) {
|
||||
|
||||
let create_block = |title: &'static str| Block::bordered().gray().title(title.bold());
|
||||
|
||||
let title = Block::default()
|
||||
.title("Use h j k l or ◄ ▲ ▼ ► to scroll ".bold())
|
||||
.title_alignment(Alignment::Center);
|
||||
let title = Block::new()
|
||||
.title_alignment(Alignment::Center)
|
||||
.title("Use h j k l or ◄ ▲ ▼ ► to scroll ".bold());
|
||||
f.render_widget(title, chunks[0]);
|
||||
|
||||
let paragraph = Paragraph::new(text.clone())
|
||||
@@ -168,8 +166,7 @@ fn ui(f: &mut Frame, app: &mut App) {
|
||||
.scroll((app.vertical_scroll as u16, 0));
|
||||
f.render_widget(paragraph, chunks[1]);
|
||||
f.render_stateful_widget(
|
||||
Scrollbar::default()
|
||||
.orientation(ScrollbarOrientation::VerticalRight)
|
||||
Scrollbar::new(ScrollbarOrientation::VerticalRight)
|
||||
.begin_symbol(Some("↑"))
|
||||
.end_symbol(Some("↓")),
|
||||
chunks[1],
|
||||
@@ -184,8 +181,7 @@ fn ui(f: &mut Frame, app: &mut App) {
|
||||
.scroll((app.vertical_scroll as u16, 0));
|
||||
f.render_widget(paragraph, chunks[2]);
|
||||
f.render_stateful_widget(
|
||||
Scrollbar::default()
|
||||
.orientation(ScrollbarOrientation::VerticalLeft)
|
||||
Scrollbar::new(ScrollbarOrientation::VerticalLeft)
|
||||
.symbols(scrollbar::VERTICAL)
|
||||
.begin_symbol(None)
|
||||
.track_symbol(None)
|
||||
@@ -205,8 +201,7 @@ fn ui(f: &mut Frame, app: &mut App) {
|
||||
.scroll((0, app.horizontal_scroll as u16));
|
||||
f.render_widget(paragraph, chunks[3]);
|
||||
f.render_stateful_widget(
|
||||
Scrollbar::default()
|
||||
.orientation(ScrollbarOrientation::HorizontalBottom)
|
||||
Scrollbar::new(ScrollbarOrientation::HorizontalBottom)
|
||||
.thumb_symbol("🬋")
|
||||
.end_symbol(None),
|
||||
chunks[3].inner(&Margin {
|
||||
@@ -224,8 +219,7 @@ fn ui(f: &mut Frame, app: &mut App) {
|
||||
.scroll((0, app.horizontal_scroll as u16));
|
||||
f.render_widget(paragraph, chunks[4]);
|
||||
f.render_stateful_widget(
|
||||
Scrollbar::default()
|
||||
.orientation(ScrollbarOrientation::HorizontalBottom)
|
||||
Scrollbar::new(ScrollbarOrientation::HorizontalBottom)
|
||||
.thumb_symbol("░")
|
||||
.track_symbol(Some("─")),
|
||||
chunks[4].inner(&Margin {
|
||||
|
||||
@@ -28,17 +28,20 @@ use rand::{
|
||||
distributions::{Distribution, Uniform},
|
||||
rngs::ThreadRng,
|
||||
};
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, Sparkline},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RandomSignal {
|
||||
struct RandomSignal {
|
||||
distribution: Uniform<u64>,
|
||||
rng: ThreadRng,
|
||||
}
|
||||
|
||||
impl RandomSignal {
|
||||
pub fn new(lower: u64, upper: u64) -> RandomSignal {
|
||||
RandomSignal {
|
||||
fn new(lower: u64, upper: u64) -> Self {
|
||||
Self {
|
||||
distribution: Uniform::new(lower, upper),
|
||||
rng: rand::thread_rng(),
|
||||
}
|
||||
@@ -60,12 +63,12 @@ struct App {
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn new() -> App {
|
||||
fn new() -> Self {
|
||||
let mut signal = RandomSignal::new(0, 100);
|
||||
let data1 = signal.by_ref().take(200).collect::<Vec<u64>>();
|
||||
let data2 = signal.by_ref().take(200).collect::<Vec<u64>>();
|
||||
let data3 = signal.by_ref().take(200).collect::<Vec<u64>>();
|
||||
App {
|
||||
Self {
|
||||
signal,
|
||||
data1,
|
||||
data2,
|
||||
@@ -127,7 +130,7 @@ fn run_app<B: Backend>(
|
||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||
if crossterm::event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if let KeyCode::Char('q') = key.code {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
#![allow(clippy::enum_glob_use, clippy::wildcard_imports)]
|
||||
|
||||
use std::{error::Error, io};
|
||||
|
||||
use crossterm::{
|
||||
@@ -48,7 +50,7 @@ struct TableColors {
|
||||
}
|
||||
|
||||
impl TableColors {
|
||||
fn new(color: &tailwind::Palette) -> Self {
|
||||
const fn new(color: &tailwind::Palette) -> Self {
|
||||
Self {
|
||||
buffer_bg: tailwind::SLATE.c950,
|
||||
header_bg: color.c900,
|
||||
@@ -69,7 +71,7 @@ struct Data {
|
||||
}
|
||||
|
||||
impl Data {
|
||||
fn ref_array(&self) -> [&String; 3] {
|
||||
const fn ref_array(&self) -> [&String; 3] {
|
||||
[&self.name, &self.address, &self.email]
|
||||
}
|
||||
|
||||
@@ -96,9 +98,9 @@ struct App {
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn new() -> App {
|
||||
fn new() -> Self {
|
||||
let data_vec = generate_fake_names();
|
||||
App {
|
||||
Self {
|
||||
state: TableState::default().with_selected(0),
|
||||
longest_item_lens: constraint_len_calculator(&data_vec),
|
||||
scroll_state: ScrollbarState::new((data_vec.len() - 1) * ITEM_HEIGHT),
|
||||
@@ -147,7 +149,7 @@ impl App {
|
||||
}
|
||||
|
||||
pub fn set_colors(&mut self) {
|
||||
self.colors = TableColors::new(&PALETTES[self.color_index])
|
||||
self.colors = TableColors::new(&PALETTES[self.color_index]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,8 +247,7 @@ fn render_table(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
.fg(app.colors.selected_style_fg);
|
||||
|
||||
let header = ["Name", "Address", "Email"]
|
||||
.iter()
|
||||
.cloned()
|
||||
.into_iter()
|
||||
.map(Cell::from)
|
||||
.collect::<Row>()
|
||||
.style(header_style)
|
||||
@@ -257,9 +258,8 @@ fn render_table(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
_ => app.colors.alt_row_color,
|
||||
};
|
||||
let item = data.ref_array();
|
||||
item.iter()
|
||||
.cloned()
|
||||
.map(|content| Cell::from(Text::from(format!("\n{}\n", content))))
|
||||
item.into_iter()
|
||||
.map(|content| Cell::from(Text::from(format!("\n{content}\n"))))
|
||||
.collect::<Row>()
|
||||
.style(Style::new().fg(app.colors.row_fg).bg(color))
|
||||
.height(4)
|
||||
@@ -308,6 +308,7 @@ fn constraint_len_calculator(items: &[Data]) -> (u16, u16, u16) {
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
(name_len as u16, address_len as u16, email_len as u16)
|
||||
}
|
||||
|
||||
@@ -325,7 +326,7 @@ fn render_scrollbar(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
);
|
||||
}
|
||||
|
||||
fn render_footer(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
fn render_footer(f: &mut Frame, app: &App, area: Rect) {
|
||||
let info_footer = Paragraph::new(Line::from(INFO_TEXT))
|
||||
.style(Style::new().fg(app.colors.row_fg).bg(app.colors.buffer_bg))
|
||||
.centered()
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
use std::{io, io::stdout};
|
||||
#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
|
||||
|
||||
use std::io::stdout;
|
||||
|
||||
use color_eyre::{config::HookBuilder, Result};
|
||||
use crossterm::{
|
||||
@@ -72,7 +74,7 @@ impl App {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_events(&mut self) -> Result<(), io::Error> {
|
||||
fn handle_events(&mut self) -> std::io::Result<()> {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
use KeyCode::*;
|
||||
@@ -102,17 +104,17 @@ impl App {
|
||||
|
||||
impl SelectedTab {
|
||||
/// Get the previous tab, if there is no previous tab return the current tab.
|
||||
fn previous(&self) -> Self {
|
||||
let current_index: usize = *self as usize;
|
||||
fn previous(self) -> Self {
|
||||
let current_index: usize = self as usize;
|
||||
let previous_index = current_index.saturating_sub(1);
|
||||
Self::from_repr(previous_index).unwrap_or(*self)
|
||||
Self::from_repr(previous_index).unwrap_or(self)
|
||||
}
|
||||
|
||||
/// Get the next tab, if there is no next tab return the current tab.
|
||||
fn next(&self) -> Self {
|
||||
let current_index = *self as usize;
|
||||
fn next(self) -> Self {
|
||||
let current_index = self as usize;
|
||||
let next_index = current_index.saturating_add(1);
|
||||
Self::from_repr(next_index).unwrap_or(*self)
|
||||
Self::from_repr(next_index).unwrap_or(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,20 +127,16 @@ impl Widget for &App {
|
||||
let horizontal = Layout::horizontal([Min(0), Length(20)]);
|
||||
let [tabs_area, title_area] = horizontal.areas(header_area);
|
||||
|
||||
self.render_title(title_area, buf);
|
||||
render_title(title_area, buf);
|
||||
self.render_tabs(tabs_area, buf);
|
||||
self.selected_tab.render(inner_area, buf);
|
||||
self.render_footer(footer_area, buf);
|
||||
render_footer(footer_area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn render_title(&self, area: Rect, buf: &mut Buffer) {
|
||||
"Ratatui Tabs Example".bold().render(area, buf);
|
||||
}
|
||||
|
||||
fn render_tabs(&self, area: Rect, buf: &mut Buffer) {
|
||||
let titles = SelectedTab::iter().map(|tab| tab.title());
|
||||
let titles = SelectedTab::iter().map(SelectedTab::title);
|
||||
let highlight_style = (Color::default(), self.selected_tab.palette().c700);
|
||||
let selected_tab_index = self.selected_tab as usize;
|
||||
Tabs::new(titles)
|
||||
@@ -148,61 +146,65 @@ impl App {
|
||||
.divider(" ")
|
||||
.render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_footer(&self, area: Rect, buf: &mut Buffer) {
|
||||
Line::raw("◄ ► to change tab | Press q to quit")
|
||||
.centered()
|
||||
.render(area, buf);
|
||||
}
|
||||
fn render_title(area: Rect, buf: &mut Buffer) {
|
||||
"Ratatui Tabs Example".bold().render(area, buf);
|
||||
}
|
||||
|
||||
fn render_footer(area: Rect, buf: &mut Buffer) {
|
||||
Line::raw("◄ ► to change tab | Press q to quit")
|
||||
.centered()
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
impl Widget for SelectedTab {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
// in a real app these might be separate widgets
|
||||
match self {
|
||||
SelectedTab::Tab1 => self.render_tab0(area, buf),
|
||||
SelectedTab::Tab2 => self.render_tab1(area, buf),
|
||||
SelectedTab::Tab3 => self.render_tab2(area, buf),
|
||||
SelectedTab::Tab4 => self.render_tab3(area, buf),
|
||||
Self::Tab1 => self.render_tab0(area, buf),
|
||||
Self::Tab2 => self.render_tab1(area, buf),
|
||||
Self::Tab3 => self.render_tab2(area, buf),
|
||||
Self::Tab4 => self.render_tab3(area, buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectedTab {
|
||||
/// Return tab's name as a styled `Line`
|
||||
fn title(&self) -> Line<'static> {
|
||||
fn title(self) -> Line<'static> {
|
||||
format!(" {self} ")
|
||||
.fg(tailwind::SLATE.c200)
|
||||
.bg(self.palette().c900)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn render_tab0(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_tab0(self, area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new("Hello, World!")
|
||||
.block(self.block())
|
||||
.render(area, buf)
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn render_tab1(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_tab1(self, area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new("Welcome to the Ratatui tabs example!")
|
||||
.block(self.block())
|
||||
.render(area, buf)
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn render_tab2(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_tab2(self, area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new("Look! I'm different than others!")
|
||||
.block(self.block())
|
||||
.render(area, buf)
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn render_tab3(&self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_tab3(self, area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new("I know, these are some basic changes. But I think you got the main idea.")
|
||||
.block(self.block())
|
||||
.render(area, buf)
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
/// A block surrounding the tab's content
|
||||
fn block(&self) -> Block<'static> {
|
||||
fn block(self) -> Block<'static> {
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_set(symbols::border::PROPORTIONAL_TALL)
|
||||
@@ -210,12 +212,12 @@ impl SelectedTab {
|
||||
.border_style(self.palette().c700)
|
||||
}
|
||||
|
||||
fn palette(&self) -> tailwind::Palette {
|
||||
const fn palette(self) -> tailwind::Palette {
|
||||
match self {
|
||||
SelectedTab::Tab1 => tailwind::BLUE,
|
||||
SelectedTab::Tab2 => tailwind::EMERALD,
|
||||
SelectedTab::Tab3 => tailwind::INDIGO,
|
||||
SelectedTab::Tab4 => tailwind::RED,
|
||||
Self::Tab1 => tailwind::BLUE,
|
||||
Self::Tab2 => tailwind::EMERALD,
|
||||
Self::Tab3 => tailwind::INDIGO,
|
||||
Self::Tab4 => tailwind::RED,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,7 +232,7 @@ fn init_error_hooks() -> color_eyre::Result<()> {
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info)
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,28 +13,31 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
// A simple example demonstrating how to handle user input. This is a bit out of the scope of
|
||||
// the library as it does not provide any input handling out of the box. However, it may helps
|
||||
// some to get started.
|
||||
//
|
||||
// This is a very simple example:
|
||||
// * An input box always focused. Every character you type is registered here.
|
||||
// * An entered character is inserted at the cursor position.
|
||||
// * Pressing Backspace erases the left character before the cursor position
|
||||
// * Pressing Enter pushes the current input in the history of previous messages. **Note: ** as
|
||||
// this is a relatively simple example unicode characters are unsupported and their use will
|
||||
// result in undefined behaviour.
|
||||
//
|
||||
// See also https://github.com/rhysd/tui-textarea and https://github.com/sayanarijit/tui-input/
|
||||
|
||||
use std::{error::Error, io};
|
||||
|
||||
/// A simple example demonstrating how to handle user input. This is a bit out of the scope of
|
||||
/// the library as it does not provide any input handling out of the box. However, it may helps
|
||||
/// some to get started.
|
||||
///
|
||||
/// This is a very simple example:
|
||||
/// * An input box always focused. Every character you type is registered here.
|
||||
/// * An entered character is inserted at the cursor position.
|
||||
/// * Pressing Backspace erases the left character before the cursor position
|
||||
/// * Pressing Enter pushes the current input in the history of previous messages. **Note: **
|
||||
/// as
|
||||
/// this is a relatively simple example unicode characters are unsupported and their use will
|
||||
/// result in undefined behaviour.
|
||||
///
|
||||
/// See also https://github.com/rhysd/tui-textarea and https://github.com/sayanarijit/tui-input/
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, List, ListItem, Paragraph},
|
||||
};
|
||||
|
||||
enum InputMode {
|
||||
Normal,
|
||||
@@ -53,18 +56,16 @@ struct App {
|
||||
messages: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for App {
|
||||
fn default() -> App {
|
||||
App {
|
||||
impl App {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
input: String::new(),
|
||||
input_mode: InputMode::Normal,
|
||||
messages: Vec::new(),
|
||||
cursor_position: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn move_cursor_left(&mut self) {
|
||||
let cursor_moved_left = self.cursor_position.saturating_sub(1);
|
||||
self.cursor_position = self.clamp_cursor(cursor_moved_left);
|
||||
@@ -127,7 +128,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
// create app and run it
|
||||
let app = App::default();
|
||||
let app = App::new();
|
||||
let res = run_app(&mut terminal, app);
|
||||
|
||||
// restore terminal
|
||||
@@ -180,7 +181,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<(
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
InputMode::Editing => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,13 +236,14 @@ fn ui(f: &mut Frame, app: &App) {
|
||||
InputMode::Editing => {
|
||||
// Make the cursor visible and ask ratatui to put it at the specified coordinates after
|
||||
// rendering
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
f.set_cursor(
|
||||
// Draw the cursor at the current position in the input field.
|
||||
// This position is can be controlled via the left and right arrow key
|
||||
input_area.x + app.cursor_position as u16 + 1,
|
||||
// Move one line down, from the border to the input line
|
||||
input_area.y + 1,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,4 +18,4 @@ Space
|
||||
Left
|
||||
Space
|
||||
Left
|
||||
Space
|
||||
Space
|
||||
|
||||
@@ -15,4 +15,4 @@ Enter
|
||||
Sleep 2s
|
||||
Show
|
||||
Type "d"
|
||||
Sleep 30s
|
||||
Sleep 30s
|
||||
|
||||
@@ -40,4 +40,4 @@ Tab
|
||||
# Weather
|
||||
Set TypingSpeed 100ms
|
||||
Down 40
|
||||
Sleep 2s
|
||||
Sleep 2s
|
||||
|
||||
@@ -46,4 +46,4 @@ Tab
|
||||
Screenshot "target/demo2-weather.png"
|
||||
Set TypingSpeed 100ms
|
||||
Down 40
|
||||
Sleep 2s
|
||||
Sleep 2s
|
||||
|
||||
@@ -97,8 +97,8 @@ where
|
||||
/// # use ratatui::prelude::*;
|
||||
/// let backend = CrosstermBackend::new(stdout());
|
||||
/// ```
|
||||
pub fn new(writer: W) -> CrosstermBackend<W> {
|
||||
CrosstermBackend { writer }
|
||||
pub const fn new(writer: W) -> Self {
|
||||
Self { writer }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,25 +252,25 @@ where
|
||||
impl From<Color> for CColor {
|
||||
fn from(color: Color) -> Self {
|
||||
match color {
|
||||
Color::Reset => CColor::Reset,
|
||||
Color::Black => CColor::Black,
|
||||
Color::Red => CColor::DarkRed,
|
||||
Color::Green => CColor::DarkGreen,
|
||||
Color::Yellow => CColor::DarkYellow,
|
||||
Color::Blue => CColor::DarkBlue,
|
||||
Color::Magenta => CColor::DarkMagenta,
|
||||
Color::Cyan => CColor::DarkCyan,
|
||||
Color::Gray => CColor::Grey,
|
||||
Color::DarkGray => CColor::DarkGrey,
|
||||
Color::LightRed => CColor::Red,
|
||||
Color::LightGreen => CColor::Green,
|
||||
Color::LightBlue => CColor::Blue,
|
||||
Color::LightYellow => CColor::Yellow,
|
||||
Color::LightMagenta => CColor::Magenta,
|
||||
Color::LightCyan => CColor::Cyan,
|
||||
Color::White => CColor::White,
|
||||
Color::Indexed(i) => CColor::AnsiValue(i),
|
||||
Color::Rgb(r, g, b) => CColor::Rgb { r, g, b },
|
||||
Color::Reset => Self::Reset,
|
||||
Color::Black => Self::Black,
|
||||
Color::Red => Self::DarkRed,
|
||||
Color::Green => Self::DarkGreen,
|
||||
Color::Yellow => Self::DarkYellow,
|
||||
Color::Blue => Self::DarkBlue,
|
||||
Color::Magenta => Self::DarkMagenta,
|
||||
Color::Cyan => Self::DarkCyan,
|
||||
Color::Gray => Self::Grey,
|
||||
Color::DarkGray => Self::DarkGrey,
|
||||
Color::LightRed => Self::Red,
|
||||
Color::LightGreen => Self::Green,
|
||||
Color::LightBlue => Self::Blue,
|
||||
Color::LightYellow => Self::Yellow,
|
||||
Color::LightMagenta => Self::Magenta,
|
||||
Color::LightCyan => Self::Cyan,
|
||||
Color::White => Self::White,
|
||||
Color::Indexed(i) => Self::AnsiValue(i),
|
||||
Color::Rgb(r, g, b) => Self::Rgb { r, g, b },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -304,14 +304,13 @@ impl From<CColor> for Color {
|
||||
/// 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.
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
struct ModifierDiff {
|
||||
pub from: Modifier,
|
||||
pub to: Modifier,
|
||||
}
|
||||
|
||||
impl ModifierDiff {
|
||||
fn queue<W>(&self, mut w: W) -> io::Result<()>
|
||||
fn queue<W>(self, mut w: W) -> io::Result<()>
|
||||
where
|
||||
W: io::Write,
|
||||
{
|
||||
@@ -377,22 +376,22 @@ impl From<CAttribute> for 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
|
||||
Modifier::from(CAttributes::from(value))
|
||||
Self::from(CAttributes::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CAttributes> for Modifier {
|
||||
fn from(value: CAttributes) -> Self {
|
||||
let mut res = Modifier::empty();
|
||||
let mut res = Self::empty();
|
||||
|
||||
if value.has(CAttribute::Bold) {
|
||||
res |= Modifier::BOLD;
|
||||
res |= Self::BOLD;
|
||||
}
|
||||
if value.has(CAttribute::Dim) {
|
||||
res |= Modifier::DIM;
|
||||
res |= Self::DIM;
|
||||
}
|
||||
if value.has(CAttribute::Italic) {
|
||||
res |= Modifier::ITALIC;
|
||||
res |= Self::ITALIC;
|
||||
}
|
||||
if value.has(CAttribute::Underlined)
|
||||
|| value.has(CAttribute::DoubleUnderlined)
|
||||
@@ -400,22 +399,22 @@ impl From<CAttributes> for Modifier {
|
||||
|| value.has(CAttribute::Underdotted)
|
||||
|| value.has(CAttribute::Underdashed)
|
||||
{
|
||||
res |= Modifier::UNDERLINED;
|
||||
res |= Self::UNDERLINED;
|
||||
}
|
||||
if value.has(CAttribute::SlowBlink) {
|
||||
res |= Modifier::SLOW_BLINK;
|
||||
res |= Self::SLOW_BLINK;
|
||||
}
|
||||
if value.has(CAttribute::RapidBlink) {
|
||||
res |= Modifier::RAPID_BLINK;
|
||||
res |= Self::RAPID_BLINK;
|
||||
}
|
||||
if value.has(CAttribute::Reverse) {
|
||||
res |= Modifier::REVERSED;
|
||||
res |= Self::REVERSED;
|
||||
}
|
||||
if value.has(CAttribute::Hidden) {
|
||||
res |= Modifier::HIDDEN;
|
||||
res |= Self::HIDDEN;
|
||||
}
|
||||
if value.has(CAttribute::CrossedOut) {
|
||||
res |= Modifier::CROSSED_OUT;
|
||||
res |= Self::CROSSED_OUT;
|
||||
}
|
||||
|
||||
res
|
||||
@@ -449,10 +448,10 @@ impl From<ContentStyle> for Style {
|
||||
}
|
||||
|
||||
Self {
|
||||
fg: value.foreground_color.map(|c| c.into()),
|
||||
bg: value.background_color.map(|c| c.into()),
|
||||
fg: value.foreground_color.map(Into::into),
|
||||
bg: value.background_color.map(Into::into),
|
||||
#[cfg(feature = "underline-color")]
|
||||
underline_color: value.underline_color.map(|c| c.into()),
|
||||
underline_color: value.underline_color.map(Into::into),
|
||||
add_modifier: value.attributes.into(),
|
||||
sub_modifier,
|
||||
}
|
||||
@@ -668,6 +667,6 @@ mod tests {
|
||||
..Default::default()
|
||||
}),
|
||||
Style::default().underline_color(Color::Red)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +82,8 @@ where
|
||||
/// # use ratatui::prelude::*;
|
||||
/// let backend = TermionBackend::new(stdout());
|
||||
/// ```
|
||||
pub fn new(writer: W) -> TermionBackend<W> {
|
||||
TermionBackend { writer }
|
||||
pub const fn new(writer: W) -> Self {
|
||||
Self { writer }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,16 +209,13 @@ where
|
||||
self.writer.flush()
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
struct Fg(Color);
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
struct Bg(Color);
|
||||
|
||||
/// 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.
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
struct ModifierDiff {
|
||||
from: Modifier,
|
||||
to: Modifier,
|
||||
@@ -319,37 +316,37 @@ from_termion_for_color!(LightWhite, White);
|
||||
|
||||
impl From<tcolor::AnsiValue> for Color {
|
||||
fn from(value: tcolor::AnsiValue) -> Self {
|
||||
Color::Indexed(value.0)
|
||||
Self::Indexed(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tcolor::Bg<tcolor::AnsiValue>> for Style {
|
||||
fn from(value: tcolor::Bg<tcolor::AnsiValue>) -> Self {
|
||||
Style::default().bg(Color::Indexed(value.0 .0))
|
||||
Self::default().bg(Color::Indexed(value.0 .0))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tcolor::Fg<tcolor::AnsiValue>> for Style {
|
||||
fn from(value: tcolor::Fg<tcolor::AnsiValue>) -> Self {
|
||||
Style::default().fg(Color::Indexed(value.0 .0))
|
||||
Self::default().fg(Color::Indexed(value.0 .0))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tcolor::Rgb> for Color {
|
||||
fn from(value: tcolor::Rgb) -> Self {
|
||||
Color::Rgb(value.0, value.1, value.2)
|
||||
Self::Rgb(value.0, value.1, value.2)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tcolor::Bg<tcolor::Rgb>> for Style {
|
||||
fn from(value: tcolor::Bg<tcolor::Rgb>) -> Self {
|
||||
Style::default().bg(Color::Rgb(value.0 .0, value.0 .1, value.0 .2))
|
||||
Self::default().bg(Color::Rgb(value.0 .0, value.0 .1, value.0 .2))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tcolor::Fg<tcolor::Rgb>> for Style {
|
||||
fn from(value: tcolor::Fg<tcolor::Rgb>) -> Self {
|
||||
Style::default().fg(Color::Rgb(value.0 .0, value.0 .1, value.0 .2))
|
||||
Self::default().fg(Color::Rgb(value.0 .0, value.0 .1, value.0 .2))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,7 +435,7 @@ from_termion_for_modifier!(Blink, SLOW_BLINK);
|
||||
|
||||
impl From<termion::style::Reset> for Modifier {
|
||||
fn from(_: termion::style::Reset) -> Self {
|
||||
Modifier::empty()
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,23 +84,23 @@ impl TermwizBackend {
|
||||
/// let backend = TermwizBackend::new()?;
|
||||
/// # Ok::<(), Box<dyn std::error::Error>>(())
|
||||
/// ```
|
||||
pub fn new() -> Result<TermwizBackend, Box<dyn Error>> {
|
||||
pub fn new() -> Result<Self, Box<dyn Error>> {
|
||||
let mut buffered_terminal =
|
||||
BufferedTerminal::new(SystemTerminal::new(Capabilities::new_from_env()?)?)?;
|
||||
buffered_terminal.terminal().set_raw_mode()?;
|
||||
buffered_terminal.terminal().enter_alternate_screen()?;
|
||||
Ok(TermwizBackend { buffered_terminal })
|
||||
Ok(Self { buffered_terminal })
|
||||
}
|
||||
|
||||
/// Creates a new Termwiz backend instance with the given buffered terminal.
|
||||
pub fn with_buffered_terminal(instance: BufferedTerminal<SystemTerminal>) -> TermwizBackend {
|
||||
TermwizBackend {
|
||||
pub const fn with_buffered_terminal(instance: BufferedTerminal<SystemTerminal>) -> Self {
|
||||
Self {
|
||||
buffered_terminal: instance,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the buffered terminal used by the backend.
|
||||
pub fn buffered_terminal(&self) -> &BufferedTerminal<SystemTerminal> {
|
||||
pub const fn buffered_terminal(&self) -> &BufferedTerminal<SystemTerminal> {
|
||||
&self.buffered_terminal
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ impl Backend for TermwizBackend {
|
||||
|
||||
impl From<CellAttributes> for Style {
|
||||
fn from(value: CellAttributes) -> Self {
|
||||
let mut style = Style::new()
|
||||
let mut style = Self::new()
|
||||
.add_modifier(value.intensity().into())
|
||||
.add_modifier(value.underline().into())
|
||||
.add_modifier(value.blink().into());
|
||||
@@ -283,9 +283,9 @@ impl From<CellAttributes> for Style {
|
||||
impl From<Intensity> for Modifier {
|
||||
fn from(value: Intensity) -> Self {
|
||||
match value {
|
||||
Intensity::Normal => Modifier::empty(),
|
||||
Intensity::Bold => Modifier::BOLD,
|
||||
Intensity::Half => Modifier::DIM,
|
||||
Intensity::Normal => Self::empty(),
|
||||
Intensity::Bold => Self::BOLD,
|
||||
Intensity::Half => Self::DIM,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,8 +293,8 @@ impl From<Intensity> for Modifier {
|
||||
impl From<Underline> for Modifier {
|
||||
fn from(value: Underline) -> Self {
|
||||
match value {
|
||||
Underline::None => Modifier::empty(),
|
||||
_ => Modifier::UNDERLINED,
|
||||
Underline::None => Self::empty(),
|
||||
_ => Self::UNDERLINED,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,17 +302,17 @@ impl From<Underline> for Modifier {
|
||||
impl From<Blink> for Modifier {
|
||||
fn from(value: Blink) -> Self {
|
||||
match value {
|
||||
Blink::None => Modifier::empty(),
|
||||
Blink::Slow => Modifier::SLOW_BLINK,
|
||||
Blink::Rapid => Modifier::RAPID_BLINK,
|
||||
Blink::None => Self::empty(),
|
||||
Blink::Slow => Self::SLOW_BLINK,
|
||||
Blink::Rapid => Self::RAPID_BLINK,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for ColorAttribute {
|
||||
fn from(color: Color) -> ColorAttribute {
|
||||
fn from(color: Color) -> Self {
|
||||
match color {
|
||||
Color::Reset => ColorAttribute::Default,
|
||||
Color::Reset => Self::Default,
|
||||
Color::Black => AnsiColor::Black.into(),
|
||||
Color::DarkGray => AnsiColor::Grey.into(),
|
||||
Color::Gray => AnsiColor::Silver.into(),
|
||||
@@ -329,10 +329,8 @@ impl From<Color> for ColorAttribute {
|
||||
Color::White => AnsiColor::White.into(),
|
||||
Color::Blue => AnsiColor::Navy.into(),
|
||||
Color::LightBlue => AnsiColor::Blue.into(),
|
||||
Color::Indexed(i) => ColorAttribute::PaletteIndex(i),
|
||||
Color::Rgb(r, g, b) => {
|
||||
ColorAttribute::TrueColorWithDefaultFallback(SrgbaTuple::from((r, g, b)))
|
||||
}
|
||||
Color::Indexed(i) => Self::PaletteIndex(i),
|
||||
Color::Rgb(r, g, b) => Self::TrueColorWithDefaultFallback(SrgbaTuple::from((r, g, b))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -340,22 +338,22 @@ impl From<Color> for ColorAttribute {
|
||||
impl From<AnsiColor> for Color {
|
||||
fn from(value: AnsiColor) -> Self {
|
||||
match value {
|
||||
AnsiColor::Black => Color::Black,
|
||||
AnsiColor::Grey => Color::DarkGray,
|
||||
AnsiColor::Silver => Color::Gray,
|
||||
AnsiColor::Maroon => Color::Red,
|
||||
AnsiColor::Red => Color::LightRed,
|
||||
AnsiColor::Green => Color::Green,
|
||||
AnsiColor::Lime => Color::LightGreen,
|
||||
AnsiColor::Olive => Color::Yellow,
|
||||
AnsiColor::Yellow => Color::LightYellow,
|
||||
AnsiColor::Purple => Color::Magenta,
|
||||
AnsiColor::Fuchsia => Color::LightMagenta,
|
||||
AnsiColor::Teal => Color::Cyan,
|
||||
AnsiColor::Aqua => Color::LightCyan,
|
||||
AnsiColor::White => Color::White,
|
||||
AnsiColor::Navy => Color::Blue,
|
||||
AnsiColor::Blue => Color::LightBlue,
|
||||
AnsiColor::Black => Self::Black,
|
||||
AnsiColor::Grey => Self::DarkGray,
|
||||
AnsiColor::Silver => Self::Gray,
|
||||
AnsiColor::Maroon => Self::Red,
|
||||
AnsiColor::Red => Self::LightRed,
|
||||
AnsiColor::Green => Self::Green,
|
||||
AnsiColor::Lime => Self::LightGreen,
|
||||
AnsiColor::Olive => Self::Yellow,
|
||||
AnsiColor::Yellow => Self::LightYellow,
|
||||
AnsiColor::Purple => Self::Magenta,
|
||||
AnsiColor::Fuchsia => Self::LightMagenta,
|
||||
AnsiColor::Teal => Self::Cyan,
|
||||
AnsiColor::Aqua => Self::LightCyan,
|
||||
AnsiColor::White => Self::White,
|
||||
AnsiColor::Navy => Self::Blue,
|
||||
AnsiColor::Blue => Self::LightBlue,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -365,8 +363,8 @@ impl From<ColorAttribute> for Color {
|
||||
match value {
|
||||
ColorAttribute::TrueColorWithDefaultFallback(srgba)
|
||||
| ColorAttribute::TrueColorWithPaletteFallback(srgba, _) => srgba.into(),
|
||||
ColorAttribute::PaletteIndex(i) => Color::Indexed(i),
|
||||
ColorAttribute::Default => Color::Reset,
|
||||
ColorAttribute::PaletteIndex(i) => Self::Indexed(i),
|
||||
ColorAttribute::Default => Self::Reset,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,8 +372,8 @@ impl From<ColorAttribute> for Color {
|
||||
impl From<ColorSpec> for Color {
|
||||
fn from(value: ColorSpec) -> Self {
|
||||
match value {
|
||||
ColorSpec::Default => Color::Reset,
|
||||
ColorSpec::PaletteIndex(i) => Color::Indexed(i),
|
||||
ColorSpec::Default => Self::Reset,
|
||||
ColorSpec::PaletteIndex(i) => Self::Indexed(i),
|
||||
ColorSpec::TrueColor(srgba) => srgba.into(),
|
||||
}
|
||||
}
|
||||
@@ -384,14 +382,14 @@ impl From<ColorSpec> for Color {
|
||||
impl From<SrgbaTuple> for Color {
|
||||
fn from(value: SrgbaTuple) -> Self {
|
||||
let (r, g, b, _) = value.to_srgb_u8();
|
||||
Color::Rgb(r, g, b)
|
||||
Self::Rgb(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RgbColor> for Color {
|
||||
fn from(value: RgbColor) -> Self {
|
||||
let (r, g, b) = value.to_tuple_rgb8();
|
||||
Color::Rgb(r, g, b)
|
||||
Self::Rgb(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::{
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{
|
||||
assert_buffer_eq,
|
||||
backend::{Backend, ClearType, WindowSize},
|
||||
buffer::{Buffer, Cell},
|
||||
layout::{Rect, Size},
|
||||
@@ -72,9 +73,9 @@ fn buffer_view(buffer: &Buffer) -> String {
|
||||
}
|
||||
|
||||
impl TestBackend {
|
||||
/// Creates a new TestBackend with the specified width and height.
|
||||
pub fn new(width: u16, height: u16) -> TestBackend {
|
||||
TestBackend {
|
||||
/// Creates a new `TestBackend` with the specified width and height.
|
||||
pub fn new(width: u16, height: u16) -> Self {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
buffer: Buffer::empty(Rect::new(0, 0, width, height)),
|
||||
@@ -83,60 +84,29 @@ impl TestBackend {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the internal buffer of the TestBackend.
|
||||
pub fn buffer(&self) -> &Buffer {
|
||||
/// Returns a reference to the internal buffer of the `TestBackend`.
|
||||
pub const fn buffer(&self) -> &Buffer {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
/// Resizes the TestBackend to the specified width and height.
|
||||
/// Resizes the `TestBackend` to the specified width and height.
|
||||
pub fn resize(&mut self, width: u16, height: u16) {
|
||||
self.buffer.resize(Rect::new(0, 0, width, height));
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
}
|
||||
|
||||
/// Asserts that the TestBackend's buffer is equal to the expected buffer.
|
||||
/// Asserts that the `TestBackend`'s buffer is equal to the expected buffer.
|
||||
/// If the buffers are not equal, a panic occurs with a detailed error message
|
||||
/// showing the differences between the expected and actual buffers.
|
||||
#[track_caller]
|
||||
pub fn assert_buffer(&self, expected: &Buffer) {
|
||||
assert_eq!(expected.area, self.buffer.area);
|
||||
let diff = expected.diff(&self.buffer);
|
||||
if diff.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut debug_info = String::from("Buffers are not equal");
|
||||
debug_info.push('\n');
|
||||
debug_info.push_str("Expected:");
|
||||
debug_info.push('\n');
|
||||
let expected_view = buffer_view(expected);
|
||||
debug_info.push_str(&expected_view);
|
||||
debug_info.push('\n');
|
||||
debug_info.push_str("Got:");
|
||||
debug_info.push('\n');
|
||||
let view = buffer_view(&self.buffer);
|
||||
debug_info.push_str(&view);
|
||||
debug_info.push('\n');
|
||||
|
||||
debug_info.push_str("Diff:");
|
||||
debug_info.push('\n');
|
||||
let nice_diff = diff
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, (x, y, cell))| {
|
||||
let expected_cell = expected.get(*x, *y);
|
||||
format!("{i}: at ({x}, {y}) expected {expected_cell:?} got {cell:?}")
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
debug_info.push_str(&nice_diff);
|
||||
panic!("{debug_info}");
|
||||
assert_buffer_eq!(&self.buffer, expected);
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TestBackend {
|
||||
/// Formats the TestBackend for display by calling the buffer_view function
|
||||
/// Formats the `TestBackend` for display by calling the `buffer_view` function
|
||||
/// on its internal buffer.
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", buffer_view(&self.buffer))
|
||||
@@ -297,7 +267,6 @@ mod tests {
|
||||
format!(
|
||||
r#""{multi_byte_char}" Hidden by multi-width symbols: [(1, " "), (2, " "), (3, " "), (4, " "), (5, " "), (6, " "), (7, " ")]
|
||||
"#,
|
||||
multi_byte_char = multi_byte_char
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -323,7 +292,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
#[should_panic = "buffer contents not equal"]
|
||||
fn assert_buffer_panics() {
|
||||
let backend = TestBackend::new(10, 2);
|
||||
let buffer = Buffer::with_lines(vec!["aaaaaaaaaa"; 2]);
|
||||
@@ -333,7 +302,7 @@ mod tests {
|
||||
#[test]
|
||||
fn display() {
|
||||
let backend = TestBackend::new(10, 2);
|
||||
assert_eq!(format!("{}", backend), "\" \"\n\" \"\n");
|
||||
assert_eq!(format!("{backend}"), "\" \"\n\" \"\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -63,7 +63,7 @@ mod tests {
|
||||
assert_buffer_eq!(buffer, other_buffer);
|
||||
}
|
||||
|
||||
#[should_panic]
|
||||
#[should_panic = "buffer areas not equal"]
|
||||
#[test]
|
||||
fn assert_buffer_eq_panics_on_unequal_area() {
|
||||
let buffer = Buffer::empty(Rect::new(0, 0, 5, 1));
|
||||
@@ -71,7 +71,7 @@ mod tests {
|
||||
assert_buffer_eq!(buffer, other_buffer);
|
||||
}
|
||||
|
||||
#[should_panic]
|
||||
#[should_panic = "buffer contents not equal"]
|
||||
#[test]
|
||||
fn assert_buffer_eq_panics_on_unequal_style() {
|
||||
let buffer = Buffer::empty(Rect::new(0, 0, 5, 1));
|
||||
|
||||
@@ -55,23 +55,23 @@ pub struct Buffer {
|
||||
|
||||
impl Buffer {
|
||||
/// Returns a Buffer with all cells set to the default one
|
||||
pub fn empty(area: Rect) -> Buffer {
|
||||
pub fn empty(area: Rect) -> Self {
|
||||
let cell = Cell::default();
|
||||
Buffer::filled(area, &cell)
|
||||
Self::filled(area, &cell)
|
||||
}
|
||||
|
||||
/// Returns a Buffer with all cells initialized with the attributes of the given Cell
|
||||
pub fn filled(area: Rect, cell: &Cell) -> Buffer {
|
||||
pub fn filled(area: Rect, cell: &Cell) -> Self {
|
||||
let size = area.area() as usize;
|
||||
let mut content = Vec::with_capacity(size);
|
||||
for _ in 0..size {
|
||||
content.push(cell.clone());
|
||||
}
|
||||
Buffer { area, content }
|
||||
Self { area, content }
|
||||
}
|
||||
|
||||
/// Returns a Buffer containing the given lines
|
||||
pub fn with_lines<'a, Iter>(lines: Iter) -> Buffer
|
||||
pub fn with_lines<'a, Iter>(lines: Iter) -> Self
|
||||
where
|
||||
Iter: IntoIterator,
|
||||
Iter::Item: Into<Line<'a>>,
|
||||
@@ -79,7 +79,7 @@ impl Buffer {
|
||||
let lines = lines.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||
let height = lines.len() as u16;
|
||||
let width = lines.iter().map(Line::width).max().unwrap_or_default() as u16;
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, width, height));
|
||||
let mut buffer = Self::empty(Rect::new(0, 0, width, height));
|
||||
for (y, line) in lines.iter().enumerate() {
|
||||
buffer.set_line(0, y as u16, line, width);
|
||||
}
|
||||
@@ -92,7 +92,7 @@ impl Buffer {
|
||||
}
|
||||
|
||||
/// Returns the area covered by this buffer
|
||||
pub fn area(&self) -> &Rect {
|
||||
pub const fn area(&self) -> &Rect {
|
||||
&self.area
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ impl Buffer {
|
||||
}
|
||||
|
||||
/// Merge an other buffer into this one
|
||||
pub fn merge(&mut self, other: &Buffer) {
|
||||
pub fn merge(&mut self, other: &Self) {
|
||||
let area = self.area.union(other.area);
|
||||
let cell = Cell::default();
|
||||
self.content.resize(area.area() as usize, cell.clone());
|
||||
@@ -358,7 +358,7 @@ impl Buffer {
|
||||
/// Next: `aコ`
|
||||
/// Updates: `0: a, 1: コ` (double width symbol at index 1 - skip index 2)
|
||||
/// ```
|
||||
pub fn diff<'a>(&self, other: &'a Buffer) -> Vec<(u16, u16, &'a Cell)> {
|
||||
pub fn diff<'a>(&self, other: &'a Self) -> Vec<(u16, u16, &'a Cell)> {
|
||||
let previous_buffer = &self.content;
|
||||
let next_buffer = &other.content;
|
||||
|
||||
@@ -664,7 +664,7 @@ mod tests {
|
||||
let actual_contents = small_one_line_buffer
|
||||
.content
|
||||
.iter()
|
||||
.map(|c| c.symbol())
|
||||
.map(Cell::symbol)
|
||||
.join("");
|
||||
let actual_styles = small_one_line_buffer
|
||||
.content
|
||||
|
||||
@@ -42,26 +42,26 @@ impl Cell {
|
||||
}
|
||||
|
||||
/// Sets the symbol of the cell.
|
||||
pub fn set_symbol(&mut self, symbol: &str) -> &mut Cell {
|
||||
pub fn set_symbol(&mut self, symbol: &str) -> &mut Self {
|
||||
self.symbol = CompactString::new(symbol);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the symbol of the cell to a single character.
|
||||
pub fn set_char(&mut self, ch: char) -> &mut 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
|
||||
}
|
||||
|
||||
/// Sets the foreground color of the cell.
|
||||
pub fn set_fg(&mut self, color: Color) -> &mut Cell {
|
||||
pub fn set_fg(&mut self, color: Color) -> &mut Self {
|
||||
self.fg = color;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the background color of the cell.
|
||||
pub fn set_bg(&mut self, color: Color) -> &mut Cell {
|
||||
pub fn set_bg(&mut self, color: Color) -> &mut Self {
|
||||
self.bg = color;
|
||||
self
|
||||
}
|
||||
@@ -70,7 +70,7 @@ impl Cell {
|
||||
///
|
||||
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
pub fn set_style<S: Into<Style>>(&mut self, style: S) -> &mut Cell {
|
||||
pub fn set_style<S: Into<Style>>(&mut self, style: S) -> &mut Self {
|
||||
let style = style.into();
|
||||
if let Some(c) = style.fg {
|
||||
self.fg = c;
|
||||
@@ -107,14 +107,14 @@ impl Cell {
|
||||
///
|
||||
/// This is helpful when it is necessary to prevent the buffer from overwriting a cell that is
|
||||
/// covered by an image from some terminal graphics protocol (Sixel / iTerm / Kitty ...).
|
||||
pub fn set_skip(&mut self, skip: bool) -> &mut Cell {
|
||||
pub fn set_skip(&mut self, skip: bool) -> &mut Self {
|
||||
self.skip = skip;
|
||||
self
|
||||
}
|
||||
|
||||
/// Resets the cell to the default state.
|
||||
pub fn reset(&mut self) {
|
||||
self.symbol = CompactString::new(" ");
|
||||
self.symbol = CompactString::new_inline(" ");
|
||||
self.fg = Color::Reset;
|
||||
self.bg = Color::Reset;
|
||||
#[cfg(feature = "underline-color")]
|
||||
@@ -127,9 +127,9 @@ impl Cell {
|
||||
}
|
||||
|
||||
impl Default for Cell {
|
||||
fn default() -> Cell {
|
||||
Cell {
|
||||
symbol: CompactString::new(" "),
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
symbol: CompactString::new_inline(" "),
|
||||
fg: Color::Reset,
|
||||
bg: Color::Reset,
|
||||
#[cfg(feature = "underline-color")]
|
||||
|
||||
@@ -197,22 +197,21 @@ impl Constraint {
|
||||
)]
|
||||
pub fn apply(&self, length: u16) -> u16 {
|
||||
match *self {
|
||||
Constraint::Percentage(p) => {
|
||||
let p = p as f32 / 100.0;
|
||||
let length = length as f32;
|
||||
Self::Percentage(p) => {
|
||||
let p = f32::from(p) / 100.0;
|
||||
let length = f32::from(length);
|
||||
(p * length).min(length) as u16
|
||||
}
|
||||
Constraint::Ratio(numerator, denominator) => {
|
||||
Self::Ratio(numerator, denominator) => {
|
||||
// avoid division by zero by using 1 when denominator is 0
|
||||
// this results in 0/0 -> 0 and x/0 -> x for x != 0
|
||||
let percentage = numerator as f32 / denominator.max(1) as f32;
|
||||
let length = length as f32;
|
||||
let length = f32::from(length);
|
||||
(percentage * length).min(length) as u16
|
||||
}
|
||||
Constraint::Length(l) => length.min(l),
|
||||
Constraint::Fill(l) => length.min(l),
|
||||
Constraint::Max(m) => length.min(m),
|
||||
Constraint::Min(m) => length.max(m),
|
||||
Self::Length(l) | Self::Fill(l) => length.min(l),
|
||||
Self::Max(m) => length.min(m),
|
||||
Self::Min(m) => length.max(m),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,11 +225,11 @@ impl Constraint {
|
||||
/// let constraints = Constraint::from_lengths([1, 2, 3]);
|
||||
/// let layout = Layout::default().constraints(constraints).split(area);
|
||||
/// ```
|
||||
pub fn from_lengths<T>(lengths: T) -> Vec<Constraint>
|
||||
pub fn from_lengths<T>(lengths: T) -> Vec<Self>
|
||||
where
|
||||
T: IntoIterator<Item = u16>,
|
||||
{
|
||||
lengths.into_iter().map(Constraint::Length).collect_vec()
|
||||
lengths.into_iter().map(Self::Length).collect_vec()
|
||||
}
|
||||
|
||||
/// Convert an iterator of ratios into a vector of constraints
|
||||
@@ -243,13 +242,13 @@ impl Constraint {
|
||||
/// let constraints = Constraint::from_ratios([(1, 4), (1, 2), (1, 4)]);
|
||||
/// let layout = Layout::default().constraints(constraints).split(area);
|
||||
/// ```
|
||||
pub fn from_ratios<T>(ratios: T) -> Vec<Constraint>
|
||||
pub fn from_ratios<T>(ratios: T) -> Vec<Self>
|
||||
where
|
||||
T: IntoIterator<Item = (u32, u32)>,
|
||||
{
|
||||
ratios
|
||||
.into_iter()
|
||||
.map(|(n, d)| Constraint::Ratio(n, d))
|
||||
.map(|(n, d)| Self::Ratio(n, d))
|
||||
.collect_vec()
|
||||
}
|
||||
|
||||
@@ -263,14 +262,11 @@ impl Constraint {
|
||||
/// let constraints = Constraint::from_percentages([25, 50, 25]);
|
||||
/// let layout = Layout::default().constraints(constraints).split(area);
|
||||
/// ```
|
||||
pub fn from_percentages<T>(percentages: T) -> Vec<Constraint>
|
||||
pub fn from_percentages<T>(percentages: T) -> Vec<Self>
|
||||
where
|
||||
T: IntoIterator<Item = u16>,
|
||||
{
|
||||
percentages
|
||||
.into_iter()
|
||||
.map(Constraint::Percentage)
|
||||
.collect_vec()
|
||||
percentages.into_iter().map(Self::Percentage).collect_vec()
|
||||
}
|
||||
|
||||
/// Convert an iterator of maxes into a vector of constraints
|
||||
@@ -283,11 +279,11 @@ impl Constraint {
|
||||
/// let constraints = Constraint::from_maxes([1, 2, 3]);
|
||||
/// let layout = Layout::default().constraints(constraints).split(area);
|
||||
/// ```
|
||||
pub fn from_maxes<T>(maxes: T) -> Vec<Constraint>
|
||||
pub fn from_maxes<T>(maxes: T) -> Vec<Self>
|
||||
where
|
||||
T: IntoIterator<Item = u16>,
|
||||
{
|
||||
maxes.into_iter().map(Constraint::Max).collect_vec()
|
||||
maxes.into_iter().map(Self::Max).collect_vec()
|
||||
}
|
||||
|
||||
/// Convert an iterator of mins into a vector of constraints
|
||||
@@ -300,11 +296,11 @@ impl Constraint {
|
||||
/// let constraints = Constraint::from_mins([1, 2, 3]);
|
||||
/// let layout = Layout::default().constraints(constraints).split(area);
|
||||
/// ```
|
||||
pub fn from_mins<T>(mins: T) -> Vec<Constraint>
|
||||
pub fn from_mins<T>(mins: T) -> Vec<Self>
|
||||
where
|
||||
T: IntoIterator<Item = u16>,
|
||||
{
|
||||
mins.into_iter().map(Constraint::Min).collect_vec()
|
||||
mins.into_iter().map(Self::Min).collect_vec()
|
||||
}
|
||||
|
||||
/// Convert an iterator of proportional factors into a vector of constraints
|
||||
@@ -317,22 +313,22 @@ impl Constraint {
|
||||
/// let constraints = Constraint::from_mins([1, 2, 3]);
|
||||
/// let layout = Layout::default().constraints(constraints).split(area);
|
||||
/// ```
|
||||
pub fn from_fills<T>(proportional_factors: T) -> Vec<Constraint>
|
||||
pub fn from_fills<T>(proportional_factors: T) -> Vec<Self>
|
||||
where
|
||||
T: IntoIterator<Item = u16>,
|
||||
{
|
||||
proportional_factors
|
||||
.into_iter()
|
||||
.map(Constraint::Fill)
|
||||
.map(Self::Fill)
|
||||
.collect_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for Constraint {
|
||||
/// Convert a u16 into a [Constraint::Length]
|
||||
/// Convert a `u16` into a [`Constraint::Length`]
|
||||
///
|
||||
/// This is useful when you want to specify a fixed size for a layout, but don't want to
|
||||
/// explicitly create a [Constraint::Length] yourself.
|
||||
/// explicitly create a [`Constraint::Length`] yourself.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -343,38 +339,38 @@ impl From<u16> for Constraint {
|
||||
/// let layout = Layout::horizontal([1, 2, 3]).split(area);
|
||||
/// let layout = Layout::vertical([1, 2, 3]).split(area);
|
||||
/// ````
|
||||
fn from(length: u16) -> Constraint {
|
||||
Constraint::Length(length)
|
||||
fn from(length: u16) -> Self {
|
||||
Self::Length(length)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Constraint> for Constraint {
|
||||
fn from(constraint: &Constraint) -> Self {
|
||||
impl From<&Self> for Constraint {
|
||||
fn from(constraint: &Self) -> Self {
|
||||
*constraint
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Constraint> for Constraint {
|
||||
fn as_ref(&self) -> &Constraint {
|
||||
impl AsRef<Self> for Constraint {
|
||||
fn as_ref(&self) -> &Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Constraint {
|
||||
fn default() -> Self {
|
||||
Constraint::Percentage(100)
|
||||
Self::Percentage(100)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Constraint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Constraint::Percentage(p) => write!(f, "Percentage({})", p),
|
||||
Constraint::Ratio(n, d) => write!(f, "Ratio({}, {})", n, d),
|
||||
Constraint::Length(l) => write!(f, "Length({})", l),
|
||||
Constraint::Fill(l) => write!(f, "Fill({})", l),
|
||||
Constraint::Max(m) => write!(f, "Max({})", m),
|
||||
Constraint::Min(m) => write!(f, "Min({})", m),
|
||||
Self::Percentage(p) => write!(f, "Percentage({p})"),
|
||||
Self::Ratio(n, d) => write!(f, "Ratio({n}, {d})"),
|
||||
Self::Length(l) => write!(f, "Length({l})"),
|
||||
Self::Fill(l) => write!(f, "Fill({l})"),
|
||||
Self::Max(m) => write!(f, "Max({m})"),
|
||||
Self::Min(m) => write!(f, "Min({m})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ type Cache = LruCache<(Rect, Layout), (Segments, Spacers)>;
|
||||
const FLOAT_PRECISION_MULTIPLIER: f64 = 100.0;
|
||||
|
||||
thread_local! {
|
||||
static LAYOUT_CACHE: OnceLock<RefCell<Cache>> = OnceLock::new();
|
||||
static LAYOUT_CACHE: OnceLock<RefCell<Cache>> = const { OnceLock::new() };
|
||||
}
|
||||
|
||||
/// A layout is a set of constraints that can be applied to a given area to split it into smaller
|
||||
@@ -58,7 +58,7 @@ thread_local! {
|
||||
/// many of the constraints in order of their priorities.
|
||||
///
|
||||
/// When the layout is computed, the result is cached in a thread-local cache, so that subsequent
|
||||
/// calls with the same parameters are faster. The cache is a LruCache, and the size of the cache
|
||||
/// calls with the same parameters are faster. The cache is a `LruCache`, and the size of the cache
|
||||
/// can be configured using [`Layout::init_cache()`].
|
||||
///
|
||||
/// # Constructors
|
||||
@@ -127,13 +127,13 @@ impl Layout {
|
||||
///
|
||||
/// The `constraints` parameter accepts any type that implements `IntoIterator<Item =
|
||||
/// Into<Constraint>>`. This includes arrays, slices, vectors, iterators. `Into<Constraint>` is
|
||||
/// implemented on u16, so you can pass an array, vec, etc. of u16 to this function to create a
|
||||
/// layout with fixed size chunks.
|
||||
/// implemented on `u16`, so you can pass an array, `Vec`, etc. of `u16` to this function to
|
||||
/// create a layout with fixed size chunks.
|
||||
///
|
||||
/// Default values for the other fields are:
|
||||
///
|
||||
/// - `margin`: 0, 0
|
||||
/// - `flex`: Flex::Start
|
||||
/// - `flex`: [`Flex::Start`]
|
||||
/// - `spacing`: 0
|
||||
///
|
||||
/// # Examples
|
||||
@@ -152,15 +152,15 @@ impl Layout {
|
||||
///
|
||||
/// Layout::new(Direction::Horizontal, vec![1, 2]);
|
||||
/// ```
|
||||
pub fn new<I>(direction: Direction, constraints: I) -> Layout
|
||||
pub fn new<I>(direction: Direction, constraints: I) -> Self
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Into<Constraint>,
|
||||
{
|
||||
Layout {
|
||||
Self {
|
||||
direction,
|
||||
constraints: constraints.into_iter().map(Into::into).collect(),
|
||||
..Layout::default()
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,12 +175,12 @@ impl Layout {
|
||||
/// # use ratatui::prelude::*;
|
||||
/// let layout = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
|
||||
/// ```
|
||||
pub fn vertical<I>(constraints: I) -> Layout
|
||||
pub fn vertical<I>(constraints: I) -> Self
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Into<Constraint>,
|
||||
{
|
||||
Layout::new(Direction::Vertical, constraints.into_iter().map(Into::into))
|
||||
Self::new(Direction::Vertical, constraints.into_iter().map(Into::into))
|
||||
}
|
||||
|
||||
/// Creates a new horizontal layout with default values.
|
||||
@@ -194,19 +194,19 @@ impl Layout {
|
||||
/// # use ratatui::prelude::*;
|
||||
/// let layout = Layout::horizontal([Constraint::Length(5), Constraint::Min(0)]);
|
||||
/// ```
|
||||
pub fn horizontal<I>(constraints: I) -> Layout
|
||||
pub fn horizontal<I>(constraints: I) -> Self
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Into<Constraint>,
|
||||
{
|
||||
Layout::new(
|
||||
Self::new(
|
||||
Direction::Horizontal,
|
||||
constraints.into_iter().map(Into::into),
|
||||
)
|
||||
}
|
||||
|
||||
/// Initialize an empty cache with a custom size. The cache is keyed on the layout and area, so
|
||||
/// that subsequent calls with the same parameters are faster. The cache is a LruCache, and
|
||||
/// that subsequent calls with the same parameters are faster. The cache is a `LruCache`, and
|
||||
/// grows until `cache_size` is reached.
|
||||
///
|
||||
/// Returns true if the cell's value was set by this call.
|
||||
@@ -214,7 +214,7 @@ impl Layout {
|
||||
/// has set this value or that the cache size is already initialized.
|
||||
///
|
||||
/// Note that a custom cache size will be set only if this function:
|
||||
/// * is called before [Layout::split()] otherwise, the cache size is
|
||||
/// * is called before [`Layout::split()`] otherwise, the cache size is
|
||||
/// [`Self::DEFAULT_CACHE_SIZE`].
|
||||
/// * is called for the first time, subsequent calls do not modify the cache size.
|
||||
pub fn init_cache(cache_size: usize) -> bool {
|
||||
@@ -246,7 +246,7 @@ impl Layout {
|
||||
/// assert_eq!(layout[..], [Rect::new(0, 0, 10, 5), Rect::new(0, 5, 10, 5)]);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn direction(mut self, direction: Direction) -> Layout {
|
||||
pub const fn direction(mut self, direction: Direction) -> Self {
|
||||
self.direction = direction;
|
||||
self
|
||||
}
|
||||
@@ -296,7 +296,7 @@ impl Layout {
|
||||
/// Layout::default().constraints(vec![1, 2, 3]);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn constraints<I>(mut self, constraints: I) -> Layout
|
||||
pub fn constraints<I>(mut self, constraints: I) -> Self
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Into<Constraint>,
|
||||
@@ -318,7 +318,7 @@ impl Layout {
|
||||
/// assert_eq!(layout[..], [Rect::new(2, 2, 6, 6)]);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn margin(mut self, margin: u16) -> Layout {
|
||||
pub const fn margin(mut self, margin: u16) -> Self {
|
||||
self.margin = Margin {
|
||||
horizontal: margin,
|
||||
vertical: margin,
|
||||
@@ -339,7 +339,7 @@ impl Layout {
|
||||
/// assert_eq!(layout[..], [Rect::new(2, 0, 6, 10)]);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn horizontal_margin(mut self, horizontal: u16) -> Layout {
|
||||
pub const fn horizontal_margin(mut self, horizontal: u16) -> Self {
|
||||
self.margin.horizontal = horizontal;
|
||||
self
|
||||
}
|
||||
@@ -357,7 +357,7 @@ impl Layout {
|
||||
/// assert_eq!(layout[..], [Rect::new(0, 2, 10, 6)]);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn vertical_margin(mut self, vertical: u16) -> Layout {
|
||||
pub const fn vertical_margin(mut self, vertical: u16) -> Self {
|
||||
self.margin.vertical = vertical;
|
||||
self
|
||||
}
|
||||
@@ -366,8 +366,8 @@ impl Layout {
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `flex`: A `Flex` enum value that represents the flex behavior of the layout. It can be one
|
||||
/// of the following:
|
||||
/// * `flex`: A [`Flex`] enum value that represents the flex behavior of the layout. It can be
|
||||
/// one of the following:
|
||||
/// - [`Flex::Legacy`]: The last item is stretched to fill the excess space.
|
||||
/// - [`Flex::Start`]: The items are aligned to the start of the layout.
|
||||
/// - [`Flex::Center`]: The items are aligned to the center of the layout.
|
||||
@@ -391,7 +391,8 @@ impl Layout {
|
||||
/// # use ratatui::layout::{Flex, Layout, Constraint::*};
|
||||
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).flex(Flex::Legacy);
|
||||
/// ```
|
||||
pub const fn flex(mut self, flex: Flex) -> Layout {
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn flex(mut self, flex: Flex) -> Self {
|
||||
self.flex = flex;
|
||||
self
|
||||
}
|
||||
@@ -414,13 +415,14 @@ impl Layout {
|
||||
/// # Notes
|
||||
///
|
||||
/// - If the layout has only one item, the spacing will not be applied.
|
||||
/// - Spacing will not be applied for `Flex::SpaceAround` and `Flex::SpaceBetween`
|
||||
pub const fn spacing(mut self, spacing: u16) -> Layout {
|
||||
/// - Spacing will not be applied for [`Flex::SpaceAround`] and [`Flex::SpaceBetween`]
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn spacing(mut self, spacing: u16) -> Self {
|
||||
self.spacing = spacing;
|
||||
self
|
||||
}
|
||||
|
||||
/// Split the rect into a number of sub-rects according to the given [`Layout`]`.
|
||||
/// Split the rect into a number of sub-rects according to the given [`Layout`].
|
||||
///
|
||||
/// An ergonomic wrapper around [`Layout::split`] that returns an array of `Rect`s instead of
|
||||
/// `Rc<[Rect]>`.
|
||||
@@ -449,7 +451,7 @@ impl Layout {
|
||||
areas.to_vec().try_into().expect("invalid number of rects")
|
||||
}
|
||||
|
||||
/// Split the rect into a number of sub-rects according to the given [`Layout`]` and return just
|
||||
/// Split the rect into a number of sub-rects according to the given [`Layout`] and return just
|
||||
/// the spacers between the areas.
|
||||
///
|
||||
/// This method requires the number of constraints to be known at compile time. If you don't
|
||||
@@ -495,8 +497,8 @@ impl Layout {
|
||||
///
|
||||
/// This method stores the result of the computation in a thread-local cache keyed on the layout
|
||||
/// and area, so that subsequent calls with the same parameters are faster. The cache is a
|
||||
/// LruCache, and grows until [`Self::DEFAULT_CACHE_SIZE`] is reached by default, if the cache
|
||||
/// is initialized with the [Layout::init_cache()] grows until the initialized cache size.
|
||||
/// `LruCache`, and grows until [`Self::DEFAULT_CACHE_SIZE`] is reached by default, if the cache
|
||||
/// is initialized with the [`Layout::init_cache()`] grows until the initialized cache size.
|
||||
///
|
||||
/// There is a helper method that can be used to split the whole area into smaller ones based on
|
||||
/// the layout: [`Layout::areas()`]. That method is a shortcut for calling this method. It
|
||||
@@ -532,8 +534,8 @@ impl Layout {
|
||||
///
|
||||
/// This method stores the result of the computation in a thread-local cache keyed on the layout
|
||||
/// and area, so that subsequent calls with the same parameters are faster. The cache is a
|
||||
/// LruCache, and grows until [`Self::DEFAULT_CACHE_SIZE`] is reached by default, if the cache
|
||||
/// is initialized with the [Layout::init_cache()] grows until the initialized cache size.
|
||||
/// `LruCache`, and grows until [`Self::DEFAULT_CACHE_SIZE`] is reached by default, if the cache
|
||||
/// is initialized with the [`Layout::init_cache()`] grows until the initialized cache size.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -698,7 +700,7 @@ fn configure_variable_constraints(
|
||||
area: Element,
|
||||
) -> Result<(), AddConstraintError> {
|
||||
// all variables are in the range [area.start, area.end]
|
||||
for &variable in variables.iter() {
|
||||
for &variable in variables {
|
||||
solver.add_constraint(variable | GE(REQUIRED) | area.start)?;
|
||||
solver.add_constraint(variable | LE(REQUIRED) | area.end)?;
|
||||
}
|
||||
@@ -733,7 +735,7 @@ fn configure_constraints(
|
||||
}
|
||||
}
|
||||
Constraint::Length(length) => {
|
||||
solver.add_constraint(element.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;
|
||||
@@ -764,7 +766,7 @@ fn configure_flex_constraints(
|
||||
let spacing_f64 = f64::from(spacing) * FLOAT_PRECISION_MULTIPLIER;
|
||||
match flex {
|
||||
Flex::Legacy => {
|
||||
for spacer in spacers_except_first_and_last.iter() {
|
||||
for spacer in spacers_except_first_and_last {
|
||||
solver.add_constraint(spacer.has_size(spacing_f64, SPACER_SIZE_EQ))?;
|
||||
}
|
||||
if let (Some(first), Some(last)) = (spacers.first(), spacers.last()) {
|
||||
@@ -776,9 +778,9 @@ fn configure_flex_constraints(
|
||||
// constraints are satisfied
|
||||
Flex::SpaceAround => {
|
||||
for (left, right) in spacers.iter().tuple_combinations() {
|
||||
solver.add_constraint(left.has_size(right, SPACER_SIZE_EQ))?
|
||||
solver.add_constraint(left.has_size(right, SPACER_SIZE_EQ))?;
|
||||
}
|
||||
for spacer in spacers.iter() {
|
||||
for spacer in spacers {
|
||||
solver.add_constraint(spacer.has_min_size(spacing, SPACER_SIZE_EQ))?;
|
||||
solver.add_constraint(spacer.has_size(area, SPACE_GROW))?;
|
||||
}
|
||||
@@ -788,12 +790,12 @@ fn configure_flex_constraints(
|
||||
// constraints are satisfied, but the first and last spacers are zero size
|
||||
Flex::SpaceBetween => {
|
||||
for (left, right) in spacers_except_first_and_last.iter().tuple_combinations() {
|
||||
solver.add_constraint(left.has_size(right.size(), SPACER_SIZE_EQ))?
|
||||
solver.add_constraint(left.has_size(right.size(), SPACER_SIZE_EQ))?;
|
||||
}
|
||||
for spacer in spacers_except_first_and_last.iter() {
|
||||
for spacer in spacers_except_first_and_last {
|
||||
solver.add_constraint(spacer.has_min_size(spacing, SPACER_SIZE_EQ))?;
|
||||
}
|
||||
for spacer in spacers_except_first_and_last.iter() {
|
||||
for spacer in spacers_except_first_and_last {
|
||||
solver.add_constraint(spacer.has_size(area, SPACE_GROW))?;
|
||||
}
|
||||
if let (Some(first), Some(last)) = (spacers.first(), spacers.last()) {
|
||||
@@ -846,7 +848,7 @@ fn configure_flex_constraints(
|
||||
/// │abcdef││abcdefabcdef│
|
||||
/// └──────┘└────────────┘
|
||||
///
|
||||
/// size == base_element * scaling_factor
|
||||
/// `size == base_element * scaling_factor`
|
||||
fn configure_fill_constraints(
|
||||
solver: &mut Solver,
|
||||
segments: &[Element],
|
||||
@@ -1075,8 +1077,6 @@ mod strengths {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::iter;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -1104,7 +1104,7 @@ mod tests {
|
||||
assert!(!Layout::init_cache(15));
|
||||
LAYOUT_CACHE.with(|c| {
|
||||
assert_eq!(c.get().unwrap().borrow().cap().get(), 10);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1130,7 +1130,7 @@ mod tests {
|
||||
c.get().unwrap().borrow().cap().get(),
|
||||
Layout::DEFAULT_CACHE_SIZE
|
||||
);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1207,7 +1207,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// The purpose of this test is to ensure that layout can be constructed with any type that
|
||||
/// implements IntoIterator<Item = AsRef<Constraint>>.
|
||||
/// implements `IntoIterator<Item = AsRef<Constraint>>`.
|
||||
#[test]
|
||||
#[allow(
|
||||
clippy::needless_borrow,
|
||||
@@ -1253,14 +1253,14 @@ mod tests {
|
||||
"constraints should be settable with an iter"
|
||||
);
|
||||
|
||||
let iterator = CONSTRAINTS.iter().map(|c| c.to_owned());
|
||||
let iterator = CONSTRAINTS.iter().map(ToOwned::to_owned);
|
||||
assert_eq!(
|
||||
Layout::default().constraints(iterator).constraints,
|
||||
CONSTRAINTS,
|
||||
"constraints should be settable with an iterator"
|
||||
);
|
||||
|
||||
let iterator_ref = CONSTRAINTS.iter().map(|c| c.as_ref());
|
||||
let iterator_ref = CONSTRAINTS.iter().map(AsRef::as_ref);
|
||||
assert_eq!(
|
||||
Layout::default().constraints(iterator_ref).constraints,
|
||||
CONSTRAINTS,
|
||||
@@ -1406,7 +1406,7 @@ mod tests {
|
||||
#[case] constraints: &[Constraint],
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
letters(flex, constraints, width, expected)
|
||||
letters(flex, constraints, width, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -1449,7 +1449,7 @@ mod tests {
|
||||
#[case] constraints: &[Constraint],
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
letters(flex, constraints, width, expected)
|
||||
letters(flex, constraints, width, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -1485,7 +1485,7 @@ mod tests {
|
||||
#[case] constraints: &[Constraint],
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
letters(flex, constraints, width, expected)
|
||||
letters(flex, constraints, width, expected);
|
||||
}
|
||||
|
||||
#[rstest] // flex, width, lengths, expected
|
||||
@@ -1618,7 +1618,7 @@ mod tests {
|
||||
#[case] constraints: &[Constraint],
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
letters(flex, constraints, width, expected)
|
||||
letters(flex, constraints, width, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -1655,7 +1655,7 @@ mod tests {
|
||||
#[case] constraints: &[Constraint],
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
letters(flex, constraints, width, expected)
|
||||
letters(flex, constraints, width, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -1692,7 +1692,7 @@ mod tests {
|
||||
#[case] constraints: &[Constraint],
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
letters(flex, constraints, width, expected)
|
||||
letters(flex, constraints, width, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -1795,7 +1795,7 @@ mod tests {
|
||||
#[case] constraints: &[Constraint],
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
letters(flex, constraints, width, expected)
|
||||
letters(flex, constraints, width, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -1832,7 +1832,7 @@ mod tests {
|
||||
#[case] constraints: &[Constraint],
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
letters(flex, constraints, width, expected)
|
||||
letters(flex, constraints, width, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -1869,7 +1869,7 @@ mod tests {
|
||||
#[case] constraints: &[Constraint],
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
letters(flex, constraints, width, expected)
|
||||
letters(flex, constraints, width, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1984,7 +1984,6 @@ mod tests {
|
||||
.flex(Flex::Legacy)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
@@ -2004,7 +2003,6 @@ mod tests {
|
||||
.flex(Flex::Start)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, r);
|
||||
@@ -2036,7 +2034,6 @@ mod tests {
|
||||
.flex(Flex::Legacy)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
@@ -2067,7 +2064,6 @@ mod tests {
|
||||
.flex(Flex::Start)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
@@ -2077,7 +2073,6 @@ mod tests {
|
||||
.flex(Flex::Center)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
@@ -2087,7 +2082,6 @@ mod tests {
|
||||
.flex(Flex::End)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
@@ -2097,7 +2091,6 @@ mod tests {
|
||||
.flex(Flex::SpaceAround)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
@@ -2107,7 +2100,6 @@ mod tests {
|
||||
.flex(Flex::SpaceBetween)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
@@ -2122,7 +2114,6 @@ mod tests {
|
||||
.flex(Flex::Legacy)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
@@ -2170,7 +2161,6 @@ mod tests {
|
||||
.flex(Flex::Legacy)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
@@ -2188,7 +2178,6 @@ mod tests {
|
||||
.flex(Flex::Legacy)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
@@ -2207,7 +2196,6 @@ mod tests {
|
||||
.flex(Flex::Legacy)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
@@ -2272,7 +2260,6 @@ mod tests {
|
||||
.flex(flex)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, r);
|
||||
@@ -2331,7 +2318,6 @@ mod tests {
|
||||
.flex(Flex::Legacy)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, r);
|
||||
@@ -2356,7 +2342,6 @@ mod tests {
|
||||
.flex(flex)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, r);
|
||||
|
||||
@@ -7,8 +7,8 @@ pub struct Margin {
|
||||
}
|
||||
|
||||
impl Margin {
|
||||
pub const fn new(horizontal: u16, vertical: u16) -> Margin {
|
||||
Margin {
|
||||
pub const fn new(horizontal: u16, vertical: u16) -> Self {
|
||||
Self {
|
||||
horizontal,
|
||||
vertical,
|
||||
}
|
||||
|
||||
@@ -39,13 +39,13 @@ pub struct Position {
|
||||
impl Position {
|
||||
/// Create a new position
|
||||
pub const fn new(x: u16, y: u16) -> Self {
|
||||
Position { x, y }
|
||||
Self { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u16, u16)> for Position {
|
||||
fn from((x, y): (u16, u16)) -> Self {
|
||||
Position { x, y }
|
||||
Self { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,13 +17,13 @@ pub use iter::*;
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Rect {
|
||||
/// The x coordinate of the top left corner of the rect.
|
||||
/// The x coordinate of the top left corner of the `Rect`.
|
||||
pub x: u16,
|
||||
/// The y coordinate of the top left corner of the rect.
|
||||
/// The y coordinate of the top left corner of the `Rect`.
|
||||
pub y: u16,
|
||||
/// The width of the rect.
|
||||
/// The width of the `Rect`.
|
||||
pub width: u16,
|
||||
/// The height of the rect.
|
||||
/// The height of the `Rect`.
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
@@ -40,6 +40,16 @@ pub struct Offset {
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
impl<X: Into<i32>, Y: Into<i32>> From<(X, Y)> for Offset {
|
||||
/// Creates a new `Offset` from a tuple of (x, y).
|
||||
fn from((x, y): (X, Y)) -> Self {
|
||||
Self {
|
||||
x: x.into(),
|
||||
y: y.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Rect {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}x{}+{}+{}", self.width, self.height, self.x, self.y)
|
||||
@@ -47,9 +57,9 @@ impl fmt::Display for Rect {
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
/// Creates a new rect, with width and height limited to keep the area under max u16. If
|
||||
/// 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) -> Rect {
|
||||
pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
|
||||
let max_area = u16::max_value();
|
||||
let (clipped_width, clipped_height) =
|
||||
if u32::from(width) * u32::from(height) > u32::from(max_area) {
|
||||
@@ -61,7 +71,7 @@ impl Rect {
|
||||
} else {
|
||||
(width, height)
|
||||
};
|
||||
Rect {
|
||||
Self {
|
||||
x,
|
||||
y,
|
||||
width: clipped_width,
|
||||
@@ -69,54 +79,57 @@ impl Rect {
|
||||
}
|
||||
}
|
||||
|
||||
/// The area of the rect. If the area is larger than the maximum value of u16, it will be
|
||||
/// clamped to u16::MAX.
|
||||
/// The area of the `Rect`. If the area is larger than the maximum value of `u16`, it will be
|
||||
/// clamped to `u16::MAX`.
|
||||
pub const fn area(self) -> u16 {
|
||||
self.width.saturating_mul(self.height)
|
||||
}
|
||||
|
||||
/// Returns true if the rect has no area.
|
||||
/// Returns true if the `Rect` has no area.
|
||||
pub const fn is_empty(self) -> bool {
|
||||
self.width == 0 || self.height == 0
|
||||
}
|
||||
|
||||
/// Returns the left coordinate of the rect.
|
||||
/// Returns the left coordinate of the `Rect`.
|
||||
pub const fn left(self) -> u16 {
|
||||
self.x
|
||||
}
|
||||
|
||||
/// Returns the right coordinate of the rect. This is the first coordinate outside of the rect.
|
||||
/// Returns the right coordinate of the `Rect`. This is the first coordinate outside of the
|
||||
/// `Rect`.
|
||||
///
|
||||
/// If the right coordinate is larger than the maximum value of u16, it will be clamped to
|
||||
/// u16::MAX.
|
||||
/// `u16::MAX`.
|
||||
pub const fn right(self) -> u16 {
|
||||
self.x.saturating_add(self.width)
|
||||
}
|
||||
|
||||
/// Returns the top coordinate of the rect.
|
||||
/// Returns the top coordinate of the `Rect`.
|
||||
pub const fn top(self) -> u16 {
|
||||
self.y
|
||||
}
|
||||
|
||||
/// Returns the bottom coordinate of the rect. This is the first coordinate outside of the rect.
|
||||
/// Returns the bottom coordinate of the `Rect`. This is the first coordinate outside of the
|
||||
/// `Rect`.
|
||||
///
|
||||
/// If the bottom coordinate is larger than the maximum value of u16, it will be clamped to
|
||||
/// u16::MAX.
|
||||
/// `u16::MAX`.
|
||||
pub const fn bottom(self) -> u16 {
|
||||
self.y.saturating_add(self.height)
|
||||
}
|
||||
|
||||
/// Returns a new rect inside the current one, with the given margin on each side.
|
||||
/// Returns a new `Rect` inside the current one, with the given margin on each side.
|
||||
///
|
||||
/// If the margin is larger than the rect, the returned rect will have no area.
|
||||
pub fn inner(self, margin: &Margin) -> Rect {
|
||||
/// If the margin is larger than the `Rect`, the returned `Rect` will have no area.
|
||||
#[must_use = "method returns the modified value"]
|
||||
pub fn inner(self, margin: &Margin) -> Self {
|
||||
let doubled_margin_horizontal = margin.horizontal.saturating_mul(2);
|
||||
let doubled_margin_vertical = margin.vertical.saturating_mul(2);
|
||||
|
||||
if self.width < doubled_margin_horizontal || self.height < doubled_margin_vertical {
|
||||
Rect::default()
|
||||
Self::default()
|
||||
} else {
|
||||
Rect {
|
||||
Self {
|
||||
x: self.x.saturating_add(margin.horizontal),
|
||||
y: self.y.saturating_add(margin.vertical),
|
||||
width: self.width.saturating_sub(doubled_margin_horizontal),
|
||||
@@ -132,26 +145,39 @@ impl Rect {
|
||||
/// - Positive `x` moves the whole `Rect` to the right, negative to the left.
|
||||
/// - Positive `y` moves the whole `Rect` to the bottom, negative to the top.
|
||||
///
|
||||
/// See [`Offset`] for details.
|
||||
pub fn offset(self, offset: Offset) -> Rect {
|
||||
Rect {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, layout::Offset};
|
||||
/// let rect = Rect::new(1, 2, 3, 4);
|
||||
/// let rect = rect.offset(Offset { x: 10, y: 20 });
|
||||
/// assert_eq!(rect, Rect::new(11, 22, 3, 4));
|
||||
///
|
||||
/// // offset can also be called with a tuple of (x, y)
|
||||
/// let rect = rect.offset((10, 20));
|
||||
/// ```
|
||||
#[must_use = "method returns the modified value"]
|
||||
pub fn offset<T: Into<Offset>>(self, offset: T) -> Self {
|
||||
let offset = offset.into();
|
||||
Self {
|
||||
x: i32::from(self.x)
|
||||
.saturating_add(offset.x)
|
||||
.clamp(0, (u16::MAX - self.width) as i32) as u16,
|
||||
.clamp(0, i32::from(u16::MAX - self.width)) as u16,
|
||||
y: i32::from(self.y)
|
||||
.saturating_add(offset.y)
|
||||
.clamp(0, (u16::MAX - self.height) as i32) as u16,
|
||||
.clamp(0, i32::from(u16::MAX - self.height)) as u16,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new rect that contains both the current one and the given one.
|
||||
pub fn union(self, other: Rect) -> Rect {
|
||||
/// Returns a new `Rect` that contains both the current one and the given one.
|
||||
#[must_use = "method returns the modified value"]
|
||||
pub fn union(self, other: Self) -> Self {
|
||||
let x1 = min(self.x, other.x);
|
||||
let y1 = min(self.y, other.y);
|
||||
let x2 = max(self.right(), other.right());
|
||||
let y2 = max(self.bottom(), other.bottom());
|
||||
Rect {
|
||||
Self {
|
||||
x: x1,
|
||||
y: y1,
|
||||
width: x2.saturating_sub(x1),
|
||||
@@ -159,15 +185,16 @@ impl Rect {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new rect that is the intersection of the current one and the given one.
|
||||
/// Returns a new `Rect` that is the intersection of the current one and the given one.
|
||||
///
|
||||
/// If the two rects do not intersect, the returned rect will have no area.
|
||||
pub fn intersection(self, other: Rect) -> Rect {
|
||||
/// If the two `Rect`s do not intersect, the returned `Rect` will have no area.
|
||||
#[must_use = "method returns the modified value"]
|
||||
pub fn intersection(self, other: Self) -> Self {
|
||||
let x1 = max(self.x, other.x);
|
||||
let y1 = max(self.y, other.y);
|
||||
let x2 = min(self.right(), other.right());
|
||||
let y2 = min(self.bottom(), other.bottom());
|
||||
Rect {
|
||||
Self {
|
||||
x: x1,
|
||||
y: y1,
|
||||
width: x2.saturating_sub(x1),
|
||||
@@ -175,17 +202,17 @@ impl Rect {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the two rects intersect.
|
||||
pub const fn intersects(self, other: Rect) -> bool {
|
||||
/// Returns true if the two `Rect`s intersect.
|
||||
pub const fn intersects(self, other: Self) -> bool {
|
||||
self.x < other.right()
|
||||
&& self.right() > other.x
|
||||
&& self.y < other.bottom()
|
||||
&& self.bottom() > other.y
|
||||
}
|
||||
|
||||
/// Returns true if the given position is inside the rect.
|
||||
/// Returns true if the given position is inside the `Rect`.
|
||||
///
|
||||
/// The position is considered inside the rect if it is on the rect's border.
|
||||
/// The position is considered inside the `Rect` if it is on the `Rect`'s border.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -201,20 +228,20 @@ impl Rect {
|
||||
&& position.y < self.bottom()
|
||||
}
|
||||
|
||||
/// Clamp this rect to fit inside the other rect.
|
||||
/// Clamp this `Rect` to fit inside the other `Rect`.
|
||||
///
|
||||
/// If the width or height of this rect is larger than the other rect, it will be clamped to the
|
||||
/// other rect's width or height.
|
||||
/// If the width or height of this `Rect` is larger than the other `Rect`, it will be clamped to
|
||||
/// the other `Rect`'s width or height.
|
||||
///
|
||||
/// If the left or top coordinate of this rect is smaller than the other rect, it will be
|
||||
/// clamped to the other rect's left or top coordinate.
|
||||
/// If the left or top coordinate of this `Rect` is smaller than the other `Rect`, it will be
|
||||
/// clamped to the other `Rect`'s left or top coordinate.
|
||||
///
|
||||
/// If the right or bottom coordinate of this rect is larger than the other rect, it will be
|
||||
/// clamped to the other rect's right or bottom coordinate.
|
||||
/// If the right or bottom coordinate of this `Rect` is larger than the other `Rect`, it will be
|
||||
/// clamped to the other `Rect`'s right or bottom coordinate.
|
||||
///
|
||||
/// This is different from [`Rect::intersection`] because it will move this rect to fit inside
|
||||
/// the other rect, while [`Rect::intersection`] instead would keep this rect's position and
|
||||
/// truncate its size to only that which is inside the other rect.
|
||||
/// This is different from [`Rect::intersection`] because it will move this `Rect` to fit inside
|
||||
/// the other `Rect`, while [`Rect::intersection`] instead would keep this `Rect`'s position and
|
||||
/// truncate its size to only that which is inside the other `Rect`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -225,12 +252,13 @@ impl Rect {
|
||||
/// let rect = Rect::new(0, 0, 100, 100).clamp(area);
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn clamp(self, other: Rect) -> Rect {
|
||||
#[must_use = "method returns the modified value"]
|
||||
pub fn clamp(self, other: Self) -> Self {
|
||||
let width = self.width.min(other.width);
|
||||
let height = self.height.min(other.height);
|
||||
let x = self.x.clamp(other.x, other.right().saturating_sub(width));
|
||||
let y = self.y.clamp(other.y, other.bottom().saturating_sub(height));
|
||||
Rect::new(x, y, width, height)
|
||||
Self::new(x, y, width, height)
|
||||
}
|
||||
|
||||
/// An iterator over rows within the `Rect`.
|
||||
@@ -283,7 +311,7 @@ impl Rect {
|
||||
Positions::new(self)
|
||||
}
|
||||
|
||||
/// Returns a [`Position`] with the same coordinates as this rect.
|
||||
/// Returns a [`Position`] with the same coordinates as this `Rect`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -299,7 +327,7 @@ impl Rect {
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the rect into a size struct.
|
||||
/// Converts the `Rect` into a size struct.
|
||||
pub const fn as_size(self) -> Size {
|
||||
Size {
|
||||
width: self.width,
|
||||
@@ -310,7 +338,7 @@ impl Rect {
|
||||
|
||||
impl From<(Position, Size)> for Rect {
|
||||
fn from((position, size): (Position, Size)) -> Self {
|
||||
Rect {
|
||||
Self {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
width: size.width,
|
||||
@@ -416,6 +444,11 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offset_from_tuple() {
|
||||
assert_eq!(Rect::new(1, 2, 3, 4).offset((5, 6)), Rect::new(6, 8, 3, 4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -112,7 +112,6 @@ impl Iterator for Positions {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::layout::Position;
|
||||
|
||||
#[test]
|
||||
fn rows() {
|
||||
|
||||
@@ -16,13 +16,13 @@ pub struct Size {
|
||||
impl Size {
|
||||
/// Create a new `Size` struct
|
||||
pub const fn new(width: u16, height: u16) -> Self {
|
||||
Size { width, height }
|
||||
Self { width, height }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u16, u16)> for Size {
|
||||
fn from((width, height): (u16, u16)) -> Self {
|
||||
Size { width, height }
|
||||
Self { width, height }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! 
|
||||
//!
|
||||
//! <div align="center">
|
||||
|
||||
33
src/style.rs
33
src/style.rs
@@ -33,7 +33,7 @@
|
||||
//! that implements [`Styled`]. E.g.:
|
||||
//! - Strings and string slices when styled return a [`Span`]
|
||||
//! - [`Span`]s can be styled again, which will merge the styles.
|
||||
//! - Many widget types can be styled directly rather than calling their style() method.
|
||||
//! - Many widget types can be styled directly rather than calling their `style()` method.
|
||||
//!
|
||||
//! See the [`Stylize`] and [`Styled`] traits for more information. These traits are re-exported in
|
||||
//! the [`prelude`] module for convenience.
|
||||
@@ -234,26 +234,26 @@ pub struct Style {
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Style {
|
||||
Style::new()
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for Style {
|
||||
type Item = Style;
|
||||
type Item = Self;
|
||||
|
||||
fn style(&self) -> Style {
|
||||
*self
|
||||
}
|
||||
|
||||
fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
|
||||
fn set_style<S: Into<Self>>(self, style: S) -> Self::Item {
|
||||
self.patch(style)
|
||||
}
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub const fn new() -> Style {
|
||||
Style {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
fg: None,
|
||||
bg: None,
|
||||
#[cfg(feature = "underline-color")]
|
||||
@@ -264,8 +264,8 @@ impl Style {
|
||||
}
|
||||
|
||||
/// Returns a `Style` resetting all properties.
|
||||
pub const fn reset() -> Style {
|
||||
Style {
|
||||
pub const fn reset() -> Self {
|
||||
Self {
|
||||
fg: Some(Color::Reset),
|
||||
bg: Some(Color::Reset),
|
||||
#[cfg(feature = "underline-color")]
|
||||
@@ -286,7 +286,7 @@ impl Style {
|
||||
/// assert_eq!(style.patch(diff), Style::default().fg(Color::Red));
|
||||
/// ```
|
||||
#[must_use = "`fg` returns the modified style without modifying the original"]
|
||||
pub const fn fg(mut self, color: Color) -> Style {
|
||||
pub const fn fg(mut self, color: Color) -> Self {
|
||||
self.fg = Some(color);
|
||||
self
|
||||
}
|
||||
@@ -302,7 +302,7 @@ impl Style {
|
||||
/// assert_eq!(style.patch(diff), Style::default().bg(Color::Red));
|
||||
/// ```
|
||||
#[must_use = "`bg` returns the modified style without modifying the original"]
|
||||
pub const fn bg(mut self, color: Color) -> Style {
|
||||
pub const fn bg(mut self, color: Color) -> Self {
|
||||
self.bg = Some(color);
|
||||
self
|
||||
}
|
||||
@@ -336,7 +336,7 @@ impl Style {
|
||||
/// ```
|
||||
#[cfg(feature = "underline-color")]
|
||||
#[must_use = "`underline_color` returns the modified style without modifying the original"]
|
||||
pub const fn underline_color(mut self, color: Color) -> Style {
|
||||
pub const fn underline_color(mut self, color: Color) -> Self {
|
||||
self.underline_color = Some(color);
|
||||
self
|
||||
}
|
||||
@@ -356,7 +356,7 @@ impl Style {
|
||||
/// assert_eq!(patched.sub_modifier, Modifier::empty());
|
||||
/// ```
|
||||
#[must_use = "`add_modifier` returns the modified style without modifying the original"]
|
||||
pub const fn add_modifier(mut self, modifier: Modifier) -> Style {
|
||||
pub const fn add_modifier(mut self, modifier: Modifier) -> Self {
|
||||
self.sub_modifier = self.sub_modifier.difference(modifier);
|
||||
self.add_modifier = self.add_modifier.union(modifier);
|
||||
self
|
||||
@@ -377,7 +377,7 @@ impl Style {
|
||||
/// assert_eq!(patched.sub_modifier, Modifier::ITALIC);
|
||||
/// ```
|
||||
#[must_use = "`remove_modifier` returns the modified style without modifying the original"]
|
||||
pub const fn remove_modifier(mut self, modifier: Modifier) -> Style {
|
||||
pub const fn remove_modifier(mut self, modifier: Modifier) -> Self {
|
||||
self.add_modifier = self.add_modifier.difference(modifier);
|
||||
self.sub_modifier = self.sub_modifier.union(modifier);
|
||||
self
|
||||
@@ -401,7 +401,7 @@ impl Style {
|
||||
/// );
|
||||
/// ```
|
||||
#[must_use = "`patch` returns the modified style without modifying the original"]
|
||||
pub fn patch<S: Into<Style>>(mut self, other: S) -> Style {
|
||||
pub fn patch<S: Into<Self>>(mut self, other: S) -> Self {
|
||||
let other = other.into();
|
||||
self.fg = other.fg.or(self.fg);
|
||||
self.bg = other.bg.or(self.bg);
|
||||
@@ -660,9 +660,10 @@ mod tests {
|
||||
.bg(Color::Black)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
.remove_modifier(Modifier::ITALIC)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[test]
|
||||
fn style_can_be_stylized() {
|
||||
// foreground colors
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::unreadable_literal)]
|
||||
|
||||
use std::{
|
||||
fmt::{self, Debug, Display},
|
||||
str::FromStr,
|
||||
@@ -131,11 +133,11 @@ impl Color {
|
||||
/// Convert a u32 to a Color
|
||||
///
|
||||
/// The u32 should be in the format 0x00RRGGBB.
|
||||
pub const fn from_u32(u: u32) -> Color {
|
||||
pub const fn from_u32(u: u32) -> Self {
|
||||
let r = (u >> 16) as u8;
|
||||
let g = (u >> 8) as u8;
|
||||
let b = u as u8;
|
||||
Color::Rgb(r, g, b)
|
||||
Self::Rgb(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,25 +252,25 @@ impl FromStr for Color {
|
||||
impl Display for Color {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Color::Reset => write!(f, "Reset"),
|
||||
Color::Black => write!(f, "Black"),
|
||||
Color::Red => write!(f, "Red"),
|
||||
Color::Green => write!(f, "Green"),
|
||||
Color::Yellow => write!(f, "Yellow"),
|
||||
Color::Blue => write!(f, "Blue"),
|
||||
Color::Magenta => write!(f, "Magenta"),
|
||||
Color::Cyan => write!(f, "Cyan"),
|
||||
Color::Gray => write!(f, "Gray"),
|
||||
Color::DarkGray => write!(f, "DarkGray"),
|
||||
Color::LightRed => write!(f, "LightRed"),
|
||||
Color::LightGreen => write!(f, "LightGreen"),
|
||||
Color::LightYellow => write!(f, "LightYellow"),
|
||||
Color::LightBlue => write!(f, "LightBlue"),
|
||||
Color::LightMagenta => write!(f, "LightMagenta"),
|
||||
Color::LightCyan => write!(f, "LightCyan"),
|
||||
Color::White => write!(f, "White"),
|
||||
Color::Rgb(r, g, b) => write!(f, "#{:02X}{:02X}{:02X}", r, g, b),
|
||||
Color::Indexed(i) => write!(f, "{}", i),
|
||||
Self::Reset => write!(f, "Reset"),
|
||||
Self::Black => write!(f, "Black"),
|
||||
Self::Red => write!(f, "Red"),
|
||||
Self::Green => write!(f, "Green"),
|
||||
Self::Yellow => write!(f, "Yellow"),
|
||||
Self::Blue => write!(f, "Blue"),
|
||||
Self::Magenta => write!(f, "Magenta"),
|
||||
Self::Cyan => write!(f, "Cyan"),
|
||||
Self::Gray => write!(f, "Gray"),
|
||||
Self::DarkGray => write!(f, "DarkGray"),
|
||||
Self::LightRed => write!(f, "LightRed"),
|
||||
Self::LightGreen => write!(f, "LightGreen"),
|
||||
Self::LightYellow => write!(f, "LightYellow"),
|
||||
Self::LightBlue => write!(f, "LightBlue"),
|
||||
Self::LightMagenta => write!(f, "LightMagenta"),
|
||||
Self::LightCyan => write!(f, "LightCyan"),
|
||||
Self::White => write!(f, "White"),
|
||||
Self::Rgb(r, g, b) => write!(f, "#{r:02X}{g:02X}{b:02X}"),
|
||||
Self::Indexed(i) => write!(f, "{i}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,8 +310,8 @@ impl Color {
|
||||
/// Converts normalized HSL (Hue, Saturation, Lightness) values to RGB (Red, Green, Blue) color
|
||||
/// representation. H, S, and L values should be in the range [0, 1].
|
||||
///
|
||||
/// Based on https://github.com/killercup/hsl-rs/blob/b8a30e11afd75f262e0550725333293805f4ead0/src/lib.rs
|
||||
fn normalized_hsl_to_rgb(h: f64, s: f64, l: f64) -> Color {
|
||||
/// Based on <https://github.com/killercup/hsl-rs/blob/b8a30e11afd75f262e0550725333293805f4ead0/src/lib.rs>
|
||||
fn normalized_hsl_to_rgb(hue: f64, saturation: f64, lightness: f64) -> Color {
|
||||
// This function can be made into `const` in the future.
|
||||
// This comment contains the relevant information for making it `const`.
|
||||
//
|
||||
@@ -329,33 +331,33 @@ fn normalized_hsl_to_rgb(h: f64, s: f64, l: f64) -> Color {
|
||||
// ```
|
||||
|
||||
// Initialize RGB components
|
||||
let r: f64;
|
||||
let g: f64;
|
||||
let b: f64;
|
||||
let red: f64;
|
||||
let green: f64;
|
||||
let blue: f64;
|
||||
|
||||
// Check if the color is achromatic (grayscale)
|
||||
if s == 0.0 {
|
||||
r = l;
|
||||
g = l;
|
||||
b = l;
|
||||
if saturation == 0.0 {
|
||||
red = lightness;
|
||||
green = lightness;
|
||||
blue = lightness;
|
||||
} else {
|
||||
// Calculate RGB components for colored cases
|
||||
let q = if l < 0.5 {
|
||||
l * (1.0 + s)
|
||||
let q = if lightness < 0.5 {
|
||||
lightness * (1.0 + saturation)
|
||||
} else {
|
||||
l + s - l * s
|
||||
lightness + saturation - lightness * saturation
|
||||
};
|
||||
let p = 2.0 * l - q;
|
||||
r = hue_to_rgb(p, q, h + 1.0 / 3.0);
|
||||
g = hue_to_rgb(p, q, h);
|
||||
b = hue_to_rgb(p, q, h - 1.0 / 3.0);
|
||||
let p = 2.0 * lightness - q;
|
||||
red = hue_to_rgb(p, q, hue + 1.0 / 3.0);
|
||||
green = hue_to_rgb(p, q, hue);
|
||||
blue = hue_to_rgb(p, q, hue - 1.0 / 3.0);
|
||||
}
|
||||
|
||||
// Scale RGB components to the range [0, 255] and create a Color::Rgb instance
|
||||
Color::Rgb(
|
||||
(r * 255.0).round() as u8,
|
||||
(g * 255.0).round() as u8,
|
||||
(b * 255.0).round() as u8,
|
||||
(red * 255.0).round() as u8,
|
||||
(green * 255.0).round() as u8,
|
||||
(blue * 255.0).round() as u8,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::unreadable_literal)]
|
||||
|
||||
//! A module for defining color palettes.
|
||||
|
||||
pub mod material;
|
||||
|
||||
@@ -455,11 +455,11 @@ pub struct NonAccentedPalette {
|
||||
}
|
||||
|
||||
impl AccentedPalette {
|
||||
/// Create a new AccentedPalette from the given variants
|
||||
/// Create a new `AccentedPalette` from the given variants
|
||||
///
|
||||
/// The variants should be in the format [0x00RRGGBB, ...]
|
||||
pub const fn from_variants(variants: [u32; 14]) -> AccentedPalette {
|
||||
AccentedPalette {
|
||||
pub const fn from_variants(variants: [u32; 14]) -> Self {
|
||||
Self {
|
||||
c50: Color::from_u32(variants[0]),
|
||||
c100: Color::from_u32(variants[1]),
|
||||
c200: Color::from_u32(variants[2]),
|
||||
@@ -479,11 +479,11 @@ impl AccentedPalette {
|
||||
}
|
||||
|
||||
impl NonAccentedPalette {
|
||||
/// Create a new NonAccented from the given variants
|
||||
/// Create a new `NonAccented` from the given variants
|
||||
///
|
||||
/// The variants should be in the format [0x00RRGGBB, ...]
|
||||
pub const fn from_variants(variants: [u32; 10]) -> NonAccentedPalette {
|
||||
NonAccentedPalette {
|
||||
pub const fn from_variants(variants: [u32; 10]) -> Self {
|
||||
Self {
|
||||
c50: Color::from_u32(variants[0]),
|
||||
c100: Color::from_u32(variants[1]),
|
||||
c200: Color::from_u32(variants[2]),
|
||||
|
||||
@@ -348,7 +348,7 @@ mod tests {
|
||||
|
||||
// format!() is used to create a temporary String inside a closure, which suffers the same
|
||||
// issue as above without the `Styled` trait impl for `String`
|
||||
let items = vec![String::from("a"), String::from("b")];
|
||||
let items = [String::from("a"), String::from("b")];
|
||||
let sss = items.iter().map(|s| format!("{s}{s}").red()).collect_vec();
|
||||
assert_eq!(sss, vec![Span::from("aa").red(), Span::from("bb").red()]);
|
||||
}
|
||||
@@ -393,12 +393,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn repeated_attributes() {
|
||||
let cyan_bg = Style::default().bg(Color::Cyan);
|
||||
let cyan_fg = Style::default().fg(Color::Cyan);
|
||||
let bg = Style::default().bg(Color::Cyan);
|
||||
let fg = Style::default().fg(Color::Cyan);
|
||||
|
||||
// Behavior: the last one set is the definitive one
|
||||
assert_eq!("hello".on_red().on_cyan(), Span::styled("hello", cyan_bg));
|
||||
assert_eq!("hello".red().cyan(), Span::styled("hello", cyan_fg));
|
||||
assert_eq!("hello".on_red().on_cyan(), Span::styled("hello", bg));
|
||||
assert_eq!("hello".red().cyan(), Span::styled("hello", fg));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -395,6 +395,7 @@ pub mod border {
|
||||
/// ▏xxxxx▕
|
||||
/// ▔▔▔▔▔▔▔
|
||||
/// ```
|
||||
#[allow(clippy::doc_markdown)]
|
||||
pub const ONE_EIGHTH_WIDE: Set = Set {
|
||||
top_right: ONE_EIGHTH_BOTTOM_EIGHT,
|
||||
top_left: ONE_EIGHTH_BOTTOM_EIGHT,
|
||||
@@ -414,6 +415,7 @@ pub mod border {
|
||||
/// ▕xx▏
|
||||
/// ▕▁▁▏
|
||||
/// ```
|
||||
#[allow(clippy::doc_markdown)]
|
||||
pub const ONE_EIGHTH_TALL: Set = Set {
|
||||
top_right: ONE_EIGHTH_LEFT_EIGHT,
|
||||
top_left: ONE_EIGHTH_RIGHT_EIGHT,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{prelude::*, widgets::WidgetRef};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A consistent view into the terminal state for rendering a single frame.
|
||||
///
|
||||
@@ -49,7 +49,7 @@ impl Frame<'_> {
|
||||
/// If your app listens for a resize event from the backend, it should ignore the values from
|
||||
/// the event for any calculations that are used to render the current frame and use this value
|
||||
/// instead as this is the size of the buffer that is used to render the current frame.
|
||||
pub fn size(&self) -> Rect {
|
||||
pub const fn size(&self) -> Rect {
|
||||
self.viewport_area
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ impl Frame<'_> {
|
||||
/// let area = Rect::new(0, 0, 5, 5);
|
||||
/// frame.render_widget_ref(block, area);
|
||||
/// ```
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[stability::unstable(feature = "widget-ref")]
|
||||
pub fn render_widget_ref<W: WidgetRef>(&mut self, widget: W, area: Rect) {
|
||||
widget.render_ref(area, self.buffer);
|
||||
@@ -146,6 +147,7 @@ impl Frame<'_> {
|
||||
/// let area = Rect::new(0, 0, 5, 5);
|
||||
/// frame.render_stateful_widget_ref(list, area, &mut state);
|
||||
/// ```
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[stability::unstable(feature = "widget-ref")]
|
||||
pub fn render_stateful_widget_ref<W>(&mut self, widget: W, area: Rect, state: &mut W::State)
|
||||
where
|
||||
@@ -177,7 +179,7 @@ impl Frame<'_> {
|
||||
///
|
||||
/// Each time a frame has been rendered, this count is incremented,
|
||||
/// providing a consistent way to reference the order and number of frames processed by the
|
||||
/// terminal. When count reaches its maximum value (usize::MAX), it wraps around to zero.
|
||||
/// terminal. When count reaches its maximum value (`usize::MAX`), it wraps around to zero.
|
||||
///
|
||||
/// This count is particularly useful when dealing with dynamic content or animations where the
|
||||
/// state of the display changes over time. By tracking the frame count, developers can
|
||||
@@ -193,7 +195,7 @@ impl Frame<'_> {
|
||||
/// let current_count = frame.count();
|
||||
/// println!("Current frame count: {}", current_count);
|
||||
/// ```
|
||||
pub fn count(&self) -> usize {
|
||||
pub const fn count(&self) -> usize {
|
||||
self.count
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,8 +111,8 @@ where
|
||||
/// let terminal = Terminal::new(backend)?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn new(backend: B) -> io::Result<Terminal<B>> {
|
||||
Terminal::with_options(
|
||||
pub fn new(backend: B) -> io::Result<Self> {
|
||||
Self::with_options(
|
||||
backend,
|
||||
TerminalOptions {
|
||||
viewport: Viewport::Fullscreen,
|
||||
@@ -132,7 +132,7 @@ where
|
||||
/// let terminal = Terminal::with_options(backend, TerminalOptions { viewport })?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn with_options(mut backend: B, options: TerminalOptions) -> io::Result<Terminal<B>> {
|
||||
pub fn with_options(mut backend: B, options: TerminalOptions) -> io::Result<Self> {
|
||||
let size = match options.viewport {
|
||||
Viewport::Fullscreen | Viewport::Inline(_) => backend.size()?,
|
||||
Viewport::Fixed(area) => area,
|
||||
@@ -142,7 +142,7 @@ where
|
||||
Viewport::Inline(height) => compute_inline_size(&mut backend, height, size, 0)?,
|
||||
Viewport::Fixed(area) => (area, (area.left(), area.top())),
|
||||
};
|
||||
Ok(Terminal {
|
||||
Ok(Self {
|
||||
backend,
|
||||
buffers: [Buffer::empty(viewport_area), Buffer::empty(viewport_area)],
|
||||
current: 0,
|
||||
@@ -172,7 +172,7 @@ where
|
||||
}
|
||||
|
||||
/// Gets the backend
|
||||
pub fn backend(&self) -> &B {
|
||||
pub const fn backend(&self) -> &B {
|
||||
&self.backend
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,9 @@ pub enum Viewport {
|
||||
impl fmt::Display for Viewport {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Viewport::Fullscreen => write!(f, "Fullscreen"),
|
||||
Viewport::Inline(height) => write!(f, "Inline({})", height),
|
||||
Viewport::Fixed(area) => write!(f, "Fixed({})", area),
|
||||
Self::Fullscreen => write!(f, "Fullscreen"),
|
||||
Self::Inline(height) => write!(f, "Inline({height})"),
|
||||
Self::Fixed(area) => write!(f, "Fixed({area})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ impl<'a> StyledGrapheme<'a> {
|
||||
///
|
||||
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
pub fn new<S: Into<Style>>(symbol: &'a str, style: S) -> StyledGrapheme<'a> {
|
||||
StyledGrapheme {
|
||||
pub fn new<S: Into<Style>>(symbol: &'a str, style: S) -> Self {
|
||||
Self {
|
||||
symbol,
|
||||
style: style.into(),
|
||||
}
|
||||
@@ -25,7 +25,7 @@ impl<'a> StyledGrapheme<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Styled for StyledGrapheme<'a> {
|
||||
type Item = StyledGrapheme<'a>;
|
||||
type Item = Self;
|
||||
|
||||
fn style(&self) -> Style {
|
||||
self.style
|
||||
|
||||
271
src/text/line.rs
271
src/text/line.rs
@@ -2,7 +2,7 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::StyledGrapheme;
|
||||
use crate::{prelude::*, widgets::Widget};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A line of text, consisting of one or more [`Span`]s.
|
||||
///
|
||||
@@ -10,28 +10,21 @@ use crate::{prelude::*, widgets::Widget};
|
||||
/// text. When a [`Line`] is rendered, it is rendered as a single line of text, with each [`Span`]
|
||||
/// being rendered in order (left to right).
|
||||
///
|
||||
/// [`Line`]s can be created from [`Span`]s, [`String`]s, and [`&str`]s. They can be styled with a
|
||||
/// [`Style`], and have an [`Alignment`].
|
||||
///
|
||||
/// The line's [`Alignment`] is used by the rendering widget to determine how to align the line
|
||||
/// within the available space. If the line is longer than the available space, the alignment is
|
||||
/// ignored and the line is truncated.
|
||||
///
|
||||
/// The line's [`Style`] is used by the rendering widget to determine how to style the line. If the
|
||||
/// line is longer than the available space, the style is applied to the entire line, and the line
|
||||
/// is truncated. Each [`Span`] in the line will be styled with the [`Style`] of the line, and then
|
||||
/// with its own [`Style`].
|
||||
///
|
||||
/// `Line` implements the [`Widget`] trait, which means it can be rendered to a [`Buffer`]. Usually
|
||||
/// apps will use the [`Paragraph`] widget instead of rendering a [`Line`] directly as it provides
|
||||
/// more functionality.
|
||||
///
|
||||
/// # Constructor Methods
|
||||
///
|
||||
/// - [`Line::default`] creates a line with empty content and the default style.
|
||||
/// - [`Line::raw`] creates a line with the given content and the default style.
|
||||
/// - [`Line::styled`] creates a line with the given content and style.
|
||||
///
|
||||
/// # Conversion Methods
|
||||
///
|
||||
/// - [`Line::from`] creates a `Line` from a [`String`].
|
||||
/// - [`Line::from`] creates a `Line` from a [`&str`].
|
||||
/// - [`Line::from`] creates a `Line` from a [`Vec`] of [`Span`]s.
|
||||
/// - [`Line::from`] creates a `Line` from single [`Span`].
|
||||
/// - [`String::from`] converts a line into a [`String`].
|
||||
/// - [`Line::from_iter`] creates a line from an iterator of items that are convertible to [`Span`].
|
||||
///
|
||||
/// # Setter Methods
|
||||
///
|
||||
/// These methods are fluent setters. They return a `Line` with the property set.
|
||||
@@ -39,6 +32,15 @@ use crate::{prelude::*, widgets::Widget};
|
||||
/// - [`Line::spans`] sets the content of the line.
|
||||
/// - [`Line::style`] sets the style of the line.
|
||||
/// - [`Line::alignment`] sets the alignment of the line.
|
||||
/// - [`Line::left_aligned`] sets the alignment of the line to [`Alignment::Left`].
|
||||
/// - [`Line::centered`] sets the alignment of the line to [`Alignment::Center`].
|
||||
/// - [`Line::right_aligned`] sets the alignment of the line to [`Alignment::Right`].
|
||||
///
|
||||
/// # Iteration Methods
|
||||
///
|
||||
/// - [`Line::iter`] returns an iterator over the spans of this line.
|
||||
/// - [`Line::iter_mut`] returns a mutable iterator over the spans of this line.
|
||||
/// - [`Line::into_iter`] returns an iterator over the spans of this line.
|
||||
///
|
||||
/// # Other Methods
|
||||
///
|
||||
@@ -46,6 +48,7 @@ use crate::{prelude::*, widgets::Widget};
|
||||
/// - [`Line::reset_style`] resets the style of the line.
|
||||
/// - [`Line::width`] returns the unicode width of the content held by this line.
|
||||
/// - [`Line::styled_graphemes`] returns an iterator over the graphemes held by this line.
|
||||
/// - [`Line::push_span`] adds a span to the line.
|
||||
///
|
||||
/// # Compatibility Notes
|
||||
///
|
||||
@@ -56,19 +59,90 @@ use crate::{prelude::*, widgets::Widget};
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ## Creating Lines
|
||||
/// [`Line`]s can be created from [`Span`]s, [`String`]s, and [`&str`]s. They can be styled with a
|
||||
/// [`Style`].
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::prelude::*;
|
||||
///
|
||||
/// Line::raw("unstyled");
|
||||
/// Line::styled("yellow text", Style::new().yellow());
|
||||
/// Line::from("red text").style(Style::new().red());
|
||||
/// Line::from(String::from("unstyled"));
|
||||
/// Line::from(vec![
|
||||
/// let style = Style::new().yellow();
|
||||
/// let line = Line::raw("Hello, world!").style(style);
|
||||
/// let line = Line::styled("Hello, world!", style);
|
||||
/// let line = Line::styled("Hello, world!", (Color::Yellow, Modifier::BOLD));
|
||||
///
|
||||
/// let line = Line::from("Hello, world!");
|
||||
/// let line = Line::from(String::from("Hello, world!"));
|
||||
/// let line = Line::from(vec![
|
||||
/// Span::styled("Hello", Style::new().blue()),
|
||||
/// Span::raw(" world!"),
|
||||
/// ]);
|
||||
/// ```
|
||||
///
|
||||
/// ## Styling Lines
|
||||
///
|
||||
/// The line's [`Style`] is used by the rendering widget to determine how to style the line. Each
|
||||
/// [`Span`] in the line will be styled with the [`Style`] of the line, and then with its own
|
||||
/// [`Style`]. If the line is longer than the available space, the style is applied to the entire
|
||||
/// line, and the line is truncated. `Line` also implements [`Styled`] which means you can use the
|
||||
/// methods of the [`Stylize`] trait.
|
||||
///
|
||||
/// ```rust
|
||||
/// # 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));
|
||||
/// let line = Line::from("Hello world!").style((Color::Yellow, Modifier::ITALIC));
|
||||
/// let line = Line::from("Hello world!").yellow().italic();
|
||||
/// ```
|
||||
///
|
||||
/// ## Aligning Lines
|
||||
///
|
||||
/// The line's [`Alignment`] is used by the rendering widget to determine how to align the line
|
||||
/// within the available space. If the line is longer than the available space, the alignment is
|
||||
/// ignored and the line is truncated.
|
||||
///
|
||||
/// ```rust
|
||||
/// # 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();
|
||||
/// let line = Line::from("Hello world!").right_aligned();
|
||||
/// ```
|
||||
///
|
||||
/// ## Rendering Lines
|
||||
///
|
||||
/// `Line` implements the [`Widget`] trait, which means it can be rendered to a [`Buffer`].
|
||||
///
|
||||
/// ```rust
|
||||
/// # 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());
|
||||
/// line.render(area, buf);
|
||||
/// # }
|
||||
///
|
||||
/// # fn draw(frame: &mut Frame, area: Rect) {
|
||||
/// // in a terminal.draw closure
|
||||
/// let line = Line::from("Hello world!").style(Style::new().yellow().italic());
|
||||
/// frame.render_widget(line, area);
|
||||
/// # }
|
||||
/// ```
|
||||
/// ## Rendering Lines with a Paragraph widget
|
||||
///
|
||||
/// Usually apps will use the [`Paragraph`] widget instead of rendering a [`Line`] directly as it
|
||||
/// provides more functionality.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// let line = Line::from("Hello world!").yellow().italic();
|
||||
/// Paragraph::new(line)
|
||||
/// .wrap(Wrap { trim: true })
|
||||
/// .render(area, buf);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`Paragraph`]: crate::widgets::Paragraph
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Line<'a> {
|
||||
@@ -102,11 +176,11 @@ impl<'a> Line<'a> {
|
||||
/// Line::raw(String::from("test content"));
|
||||
/// Line::raw(Cow::from("test content"));
|
||||
/// ```
|
||||
pub fn raw<T>(content: T) -> Line<'a>
|
||||
pub fn raw<T>(content: T) -> Self
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
{
|
||||
Line {
|
||||
Self {
|
||||
spans: content
|
||||
.into()
|
||||
.lines()
|
||||
@@ -135,12 +209,12 @@ impl<'a> Line<'a> {
|
||||
/// Line::styled(String::from("My text"), style);
|
||||
/// Line::styled(Cow::from("test content"), style);
|
||||
/// ```
|
||||
pub fn styled<T, S>(content: T, style: S) -> Line<'a>
|
||||
pub fn styled<T, S>(content: T, style: S) -> Self
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
S: Into<Style>,
|
||||
{
|
||||
Line {
|
||||
Self {
|
||||
spans: content
|
||||
.into()
|
||||
.lines()
|
||||
@@ -378,6 +452,23 @@ impl<'a> Line<'a> {
|
||||
pub fn iter_mut(&mut self) -> std::slice::IterMut<Span<'a>> {
|
||||
self.spans.iter_mut()
|
||||
}
|
||||
|
||||
/// Adds a span to the line.
|
||||
///
|
||||
/// `span` can be any type that is convertible into a `Span`. For example, you can pass a
|
||||
/// `&str`, a `String`, or a `Span`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// let mut line = Line::from("Hello, ");
|
||||
/// line.push_span(Span::raw("world!"));
|
||||
/// line.push_span(" How are you?");
|
||||
/// ```
|
||||
pub fn push_span<T: Into<Span<'a>>>(&mut self, span: T) {
|
||||
self.spans.push(span.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for Line<'a> {
|
||||
@@ -435,14 +526,23 @@ impl<'a> From<Span<'a>> for Line<'a> {
|
||||
}
|
||||
|
||||
impl<'a> From<Line<'a>> for String {
|
||||
fn from(line: Line<'a>) -> String {
|
||||
line.iter().fold(String::new(), |mut acc, s| {
|
||||
fn from(line: Line<'a>) -> Self {
|
||||
line.iter().fold(Self::new(), |mut acc, s| {
|
||||
acc.push_str(s.content.as_ref());
|
||||
acc
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> FromIterator<T> for Line<'a>
|
||||
where
|
||||
T: Into<Span<'a>>,
|
||||
{
|
||||
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
|
||||
Self::from(iter.into_iter().map(Into::into).collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Line<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_ref(area, buf);
|
||||
@@ -455,13 +555,12 @@ impl WidgetRef for Line<'_> {
|
||||
buf.set_style(area, self.style);
|
||||
let width = self.width() as u16;
|
||||
let offset = match self.alignment {
|
||||
Some(Alignment::Left) => 0,
|
||||
Some(Alignment::Center) => (area.width.saturating_sub(width)) / 2,
|
||||
Some(Alignment::Right) => area.width.saturating_sub(width),
|
||||
None => 0,
|
||||
Some(Alignment::Left) | None => 0,
|
||||
};
|
||||
let mut x = area.left().saturating_add(offset);
|
||||
for span in self.spans.iter() {
|
||||
for span in &self.spans {
|
||||
let span_width = span.width() as u16;
|
||||
let span_area = Rect {
|
||||
x,
|
||||
@@ -486,12 +585,31 @@ impl std::fmt::Display for Line<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Styled for Line<'a> {
|
||||
type Item = Self;
|
||||
|
||||
fn style(&self) -> Style {
|
||||
self.style
|
||||
}
|
||||
|
||||
fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
|
||||
self.style(style)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::iter;
|
||||
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[fixture]
|
||||
fn small_buf() -> Buffer {
|
||||
Buffer::empty(Rect::new(0, 0, 10, 1))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_str() {
|
||||
let line = Line::raw("test content");
|
||||
@@ -601,6 +719,16 @@ mod tests {
|
||||
assert_eq!(Style::reset(), line.style);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stylize() {
|
||||
assert_eq!(Line::default().green().style, Color::Green.into());
|
||||
assert_eq!(
|
||||
Line::default().on_green().style,
|
||||
Style::new().bg(Color::Green)
|
||||
);
|
||||
assert_eq!(Line::default().italic().style, Modifier::ITALIC.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_string() {
|
||||
let s = String::from("Hello, world!");
|
||||
@@ -625,6 +753,32 @@ mod tests {
|
||||
assert_eq!(spans, line.spans);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_iter() {
|
||||
let line = Line::from_iter(vec!["Hello".blue(), " world!".green()]);
|
||||
assert_eq!(
|
||||
line.spans,
|
||||
vec![
|
||||
Span::styled("Hello", Style::new().blue()),
|
||||
Span::styled(" world!", Style::new().green()),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect() {
|
||||
let line: Line = iter::once("Hello".blue())
|
||||
.chain(iter::once(" world!".green()))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
line.spans,
|
||||
vec![
|
||||
Span::styled("Hello", Style::new().blue()),
|
||||
Span::styled(" world!", Style::new().green()),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_span() {
|
||||
let span = Span::styled("Hello, world!", Style::default().fg(Color::Yellow));
|
||||
@@ -694,6 +848,35 @@ mod tests {
|
||||
assert_eq!(format!("{line_from_styled_span}"), "Hello, world!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn left_aligned() {
|
||||
let line = Line::from("Hello, world!").left_aligned();
|
||||
assert_eq!(line.alignment, Some(Alignment::Left));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn centered() {
|
||||
let line = Line::from("Hello, world!").centered();
|
||||
assert_eq!(line.alignment, Some(Alignment::Center));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn right_aligned() {
|
||||
let line = Line::from("Hello, world!").right_aligned();
|
||||
assert_eq!(line.alignment, Some(Alignment::Right));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn push_span() {
|
||||
let mut line = Line::from("A");
|
||||
line.push_span(Span::raw("B"));
|
||||
line.push_span("C");
|
||||
assert_eq!(
|
||||
line.spans,
|
||||
vec![Span::raw("A"), Span::raw("B"), Span::raw("C")]
|
||||
);
|
||||
}
|
||||
|
||||
mod widget {
|
||||
use super::*;
|
||||
use crate::assert_buffer_eq;
|
||||
@@ -701,6 +884,7 @@ mod tests {
|
||||
const GREEN: Style = Style::new().fg(Color::Green);
|
||||
const ITALIC: Style = Style::new().add_modifier(Modifier::ITALIC);
|
||||
|
||||
#[fixture]
|
||||
fn hello_world() -> Line<'static> {
|
||||
Line::from(vec![
|
||||
Span::styled("Hello ", BLUE),
|
||||
@@ -720,6 +904,13 @@ mod tests {
|
||||
assert_buffer_eq!(buf, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_out_of_bounds(hello_world: Line<'static>, mut small_buf: Buffer) {
|
||||
let out_of_bounds = Rect::new(20, 20, 10, 1);
|
||||
hello_world.render(out_of_bounds, &mut small_buf);
|
||||
assert_buffer_eq!(small_buf, Buffer::empty(small_buf.area));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_only_styles_line_area() {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 1));
|
||||
@@ -764,24 +955,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn left_aligned() {
|
||||
let line = Line::from("Hello, world!").left_aligned();
|
||||
assert_eq!(line.alignment, Some(Alignment::Left));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn centered() {
|
||||
let line = Line::from("Hello, world!").centered();
|
||||
assert_eq!(line.alignment, Some(Alignment::Center));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn right_aligned() {
|
||||
let line = Line::from("Hello, world!").right_aligned();
|
||||
assert_eq!(line.alignment, Some(Alignment::Right));
|
||||
}
|
||||
|
||||
mod iterators {
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ impl<'a> Masked<'a> {
|
||||
}
|
||||
|
||||
/// The character to use for masking.
|
||||
pub fn mask_char(&self) -> char {
|
||||
pub const fn mask_char(&self) -> char {
|
||||
self.mask_char
|
||||
}
|
||||
|
||||
@@ -49,37 +49,37 @@ impl<'a> Masked<'a> {
|
||||
impl Debug for Masked<'_> {
|
||||
/// Debug representation of a masked string is the underlying string
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.inner).map_err(|_| fmt::Error)
|
||||
Display::fmt(&self.inner, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Masked<'_> {
|
||||
/// Display representation of a masked string is the masked string
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.value()).map_err(|_| fmt::Error)
|
||||
Display::fmt(&self.value(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Masked<'a>> for Cow<'a, str> {
|
||||
fn from(masked: &'a Masked) -> Cow<'a, str> {
|
||||
fn from(masked: &'a Masked) -> Self {
|
||||
masked.value()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Masked<'a>> for Cow<'a, str> {
|
||||
fn from(masked: Masked<'a>) -> Cow<'a, str> {
|
||||
fn from(masked: Masked<'a>) -> Self {
|
||||
masked.value()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Masked<'_>> for Text<'a> {
|
||||
fn from(masked: &'a Masked) -> Text<'a> {
|
||||
fn from(masked: &'a Masked) -> Self {
|
||||
Text::raw(masked.value())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Masked<'a>> for Text<'a> {
|
||||
fn from(masked: Masked<'a>) -> Text<'a> {
|
||||
fn from(masked: Masked<'a>) -> Self {
|
||||
Text::raw(masked.value())
|
||||
}
|
||||
}
|
||||
|
||||
105
src/text/span.rs
105
src/text/span.rs
@@ -4,7 +4,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use super::StyledGrapheme;
|
||||
use crate::{prelude::*, widgets::Widget};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Represents a part of a line that is contiguous and where all characters share the same style.
|
||||
///
|
||||
@@ -107,11 +107,11 @@ impl<'a> Span<'a> {
|
||||
/// Span::raw("test content");
|
||||
/// Span::raw(String::from("test content"));
|
||||
/// ```
|
||||
pub fn raw<T>(content: T) -> Span<'a>
|
||||
pub fn raw<T>(content: T) -> Self
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
{
|
||||
Span {
|
||||
Self {
|
||||
content: content.into(),
|
||||
style: Style::default(),
|
||||
}
|
||||
@@ -133,12 +133,12 @@ impl<'a> Span<'a> {
|
||||
/// Span::styled("test content", style);
|
||||
/// Span::styled(String::from("test content"), style);
|
||||
/// ```
|
||||
pub fn styled<T, S>(content: T, style: S) -> Span<'a>
|
||||
pub fn styled<T, S>(content: T, style: S) -> Self
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
S: Into<Style>,
|
||||
{
|
||||
Span {
|
||||
Self {
|
||||
content: content.into(),
|
||||
style: style.into(),
|
||||
}
|
||||
@@ -282,36 +282,56 @@ impl<'a> Span<'a> {
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// let l = "Test Content".green().italic().to_left_aligned_line();
|
||||
/// let line = "Test Content".green().italic().into_left_aligned_line();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn to_left_aligned_line(self) -> Line<'a> {
|
||||
pub fn into_left_aligned_line(self) -> Line<'a> {
|
||||
Line::from(self).left_aligned()
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
#[deprecated = "use into_left_aligned_line"]
|
||||
pub fn to_left_aligned_line(self) -> Line<'a> {
|
||||
self.into_left_aligned_line()
|
||||
}
|
||||
|
||||
/// Converts this Span into a center-aligned [`Line`]
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// let l = "Test Content".green().italic().to_centered_line();
|
||||
/// let line = "Test Content".green().italic().into_centered_line();
|
||||
/// ```
|
||||
pub fn to_centered_line(self) -> Line<'a> {
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn into_centered_line(self) -> Line<'a> {
|
||||
Line::from(self).centered()
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
#[deprecated = "use into_centered_line"]
|
||||
pub fn to_centered_line(self) -> Line<'a> {
|
||||
self.into_centered_line()
|
||||
}
|
||||
|
||||
/// Converts this Span into a right-aligned [`Line`]
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// let l = "Test Content".green().italic().to_right_aligned_line();
|
||||
/// let line = "Test Content".green().italic().into_right_aligned_line();
|
||||
/// ```
|
||||
pub fn to_right_aligned_line(self) -> Line<'a> {
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn into_right_aligned_line(self) -> Line<'a> {
|
||||
Line::from(self).right_aligned()
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
#[deprecated = "use into_right_aligned_line"]
|
||||
pub fn to_right_aligned_line(self) -> Line<'a> {
|
||||
self.into_right_aligned_line()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> From<T> for Span<'a>
|
||||
@@ -324,7 +344,7 @@ where
|
||||
}
|
||||
|
||||
impl<'a> Styled for Span<'a> {
|
||||
type Item = Span<'a>;
|
||||
type Item = Self;
|
||||
|
||||
fn style(&self) -> Style {
|
||||
self.style
|
||||
@@ -343,6 +363,7 @@ impl Widget for Span<'_> {
|
||||
|
||||
impl WidgetRef for Span<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
let area = area.intersection(buf.area);
|
||||
let Rect {
|
||||
x: mut current_x,
|
||||
y,
|
||||
@@ -382,8 +403,15 @@ impl std::fmt::Display for Span<'_> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rstest::fixture;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[fixture]
|
||||
fn small_buf() -> Buffer {
|
||||
Buffer::empty(Rect::new(0, 0, 10, 1))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
let span = Span::default();
|
||||
@@ -512,9 +540,32 @@ mod tests {
|
||||
assert_eq!(format!("{stylized_span}"), "stylized test content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn left_aligned() {
|
||||
let span = Span::styled("Test Content", Style::new().green().italic());
|
||||
let line = span.into_left_aligned_line();
|
||||
assert_eq!(line.alignment, Some(Alignment::Left));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn centered() {
|
||||
let span = Span::styled("Test Content", Style::new().green().italic());
|
||||
let line = span.into_centered_line();
|
||||
assert_eq!(line.alignment, Some(Alignment::Center));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn right_aligned() {
|
||||
let span = Span::styled("Test Content", Style::new().green().italic());
|
||||
let line = span.into_right_aligned_line();
|
||||
assert_eq!(line.alignment, Some(Alignment::Right));
|
||||
}
|
||||
|
||||
mod widget {
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
use crate::{assert_buffer_eq, style::Stylize};
|
||||
use crate::assert_buffer_eq;
|
||||
|
||||
#[test]
|
||||
fn render() {
|
||||
@@ -530,6 +581,13 @@ mod tests {
|
||||
assert_buffer_eq!(buf, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_out_of_bounds(mut small_buf: Buffer) {
|
||||
let out_of_bounds = Rect::new(20, 20, 10, 1);
|
||||
Span::raw("Hello, World!").render(out_of_bounds, &mut small_buf);
|
||||
assert_eq!(small_buf, Buffer::empty(small_buf.area));
|
||||
}
|
||||
|
||||
/// When the content of the span is longer than the area passed to render, the content
|
||||
/// should be truncated
|
||||
#[test]
|
||||
@@ -612,25 +670,4 @@ mod tests {
|
||||
assert_buffer_eq!(buf, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn left_aligned() {
|
||||
let span = Span::styled("Test Content", Style::new().green().italic());
|
||||
let line = span.to_left_aligned_line();
|
||||
assert_eq!(line.alignment, Some(Alignment::Left));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn centered() {
|
||||
let span = Span::styled("Test Content", Style::new().green().italic());
|
||||
let line = span.to_centered_line();
|
||||
assert_eq!(line.alignment, Some(Alignment::Center));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn right_aligned() {
|
||||
let span = Span::styled("Test Content", Style::new().green().italic());
|
||||
let line = span.to_right_aligned_line();
|
||||
assert_eq!(line.alignment, Some(Alignment::Right));
|
||||
}
|
||||
}
|
||||
|
||||
375
src/text/text.rs
375
src/text/text.rs
@@ -3,32 +3,30 @@ use std::borrow::Cow;
|
||||
|
||||
use itertools::{Itertools, Position};
|
||||
|
||||
use crate::{prelude::*, widgets::Widget};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A string split over multiple lines where each line is composed of several clusters, each with
|
||||
/// their own style.
|
||||
/// A string split over one or more lines.
|
||||
///
|
||||
/// A [`Text`], like a [`Line`], can be constructed using one of the many `From` implementations
|
||||
/// or via the [`Text::raw`] and [`Text::styled`] methods. Helpfully, [`Text`] also implements
|
||||
/// [`core::iter::Extend`] which enables the concatenation of several [`Text`] blocks.
|
||||
///
|
||||
/// The text's [`Style`] is used by the rendering widget to determine how to style the text. Each
|
||||
/// [`Line`] in the text will be styled with the [`Style`] of the text, and then with its own
|
||||
/// [`Style`]. `Text` also implements [`Styled`] which means you can use the methods of the
|
||||
/// [`Stylize`] trait.
|
||||
///
|
||||
/// The text's [`Alignment`] can be set using [`Text::alignment`]. Lines composing the text can
|
||||
/// also be individually aligned with [`Line::alignment`].
|
||||
///
|
||||
/// `Text` implements the [`Widget`] trait, which means it can be rendered to a [`Buffer`].
|
||||
/// Usually apps will use the [`Paragraph`] widget instead of rendering a `Text` directly as it
|
||||
/// provides more functionality.
|
||||
/// [`Text`] is used wherever text is displayed in the terminal and represents one or more [`Line`]s
|
||||
/// of text. When a [`Text`] is rendered, each line is rendered as a single line of text from top to
|
||||
/// bottom of the area. The text can be styled and aligned.
|
||||
///
|
||||
/// # Constructor Methods
|
||||
///
|
||||
/// - [`Text::default`] creates a `Text` with empty content and the default style.
|
||||
/// - [`Text::raw`] creates a `Text` (potentially multiple lines) with no style.
|
||||
/// - [`Text::styled`] creates a `Text` (potentially multiple lines) with a style.
|
||||
/// - [`Text::default`] creates a `Text` with empty content and the default style.
|
||||
///
|
||||
/// # Conversion Methods
|
||||
///
|
||||
/// - [`Text::from`] creates a `Text` from a `String`.
|
||||
/// - [`Text::from`] creates a `Text` from a `&str`.
|
||||
/// - [`Text::from`] creates a `Text` from a `Cow<str>`.
|
||||
/// - [`Text::from`] creates a `Text` from a [`Span`].
|
||||
/// - [`Text::from`] creates a `Text` from a [`Line`].
|
||||
/// - [`Text::from`] creates a `Text` from a `Vec<Line>`.
|
||||
/// - [`Text::from_iter`] creates a `Text` from an iterator of items that can be converted into
|
||||
/// `Line`.
|
||||
///
|
||||
/// # Setter Methods
|
||||
///
|
||||
@@ -36,6 +34,15 @@ use crate::{prelude::*, widgets::Widget};
|
||||
///
|
||||
/// - [`Text::style`] sets the style of this `Text`.
|
||||
/// - [`Text::alignment`] sets the alignment for this `Text`.
|
||||
/// - [`Text::left_aligned`] sets the alignment to [`Alignment::Left`].
|
||||
/// - [`Text::centered`] sets the alignment to [`Alignment::Center`].
|
||||
/// - [`Text::right_aligned`] sets the alignment to [`Alignment::Right`].
|
||||
///
|
||||
/// # Iteration Methods
|
||||
///
|
||||
/// - [`Text::iter`] returns an iterator over the lines of the text.
|
||||
/// - [`Text::iter_mut`] returns an iterator that allows modifying each line.
|
||||
/// - [`Text::into_iter`] returns an iterator over the lines of the text.
|
||||
///
|
||||
/// # Other Methods
|
||||
///
|
||||
@@ -43,29 +50,121 @@ use crate::{prelude::*, widgets::Widget};
|
||||
/// - [`Text::height`] returns the height.
|
||||
/// - [`Text::patch_style`] patches the style of this `Text`, adding modifiers from the given style.
|
||||
/// - [`Text::reset_style`] resets the style of the `Text`.
|
||||
/// - [`Text::push_line`] adds a line to the text.
|
||||
/// - [`Text::push_span`] adds a span to the last line of the text.
|
||||
///
|
||||
/// [`Paragraph`]: crate::widgets::Paragraph
|
||||
/// [`Widget`]: crate::widgets::Widget
|
||||
/// # Examples
|
||||
///
|
||||
/// ## Creating Text
|
||||
///
|
||||
/// A [`Text`], like a [`Line`], can be constructed using one of the many `From` implementations or
|
||||
/// via the [`Text::raw`] and [`Text::styled`] methods. Helpfully, [`Text`] also implements
|
||||
/// [`core::iter::Extend`] which enables the concatenation of several [`Text`] blocks.
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::{borrow::Cow, iter};
|
||||
///
|
||||
/// use ratatui::prelude::*;
|
||||
///
|
||||
/// let style = Style::default()
|
||||
/// .fg(Color::Yellow)
|
||||
/// .add_modifier(Modifier::ITALIC);
|
||||
/// let style = Style::new().yellow().italic();
|
||||
/// let text = Text::raw("The first line\nThe second line").style(style);
|
||||
/// let text = Text::styled("The first line\nThe second line", style);
|
||||
/// let text = Text::styled(
|
||||
/// "The first line\nThe second line",
|
||||
/// (Color::Yellow, Modifier::ITALIC),
|
||||
/// );
|
||||
///
|
||||
/// // An initial two lines of `Text` built from a `&str`
|
||||
/// let mut text = Text::from("The first line\nThe second line");
|
||||
/// assert_eq!(2, text.height());
|
||||
/// let text = Text::from("The first line\nThe second line");
|
||||
/// let text = Text::from(String::from("The first line\nThe second line"));
|
||||
/// let text = Text::from(Cow::Borrowed("The first line\nThe second line"));
|
||||
/// let text = Text::from(Span::styled("The first line\nThe second line", style));
|
||||
/// let text = Text::from(Line::from("The first line"));
|
||||
/// let text = Text::from(vec![
|
||||
/// Line::from("The first line"),
|
||||
/// Line::from("The second line"),
|
||||
/// ]);
|
||||
/// let text = Text::from_iter(iter::once("The first line").chain(iter::once("The second line")));
|
||||
///
|
||||
/// // Adding two more unstyled lines
|
||||
/// text.extend(Text::raw("These are two\nmore lines!"));
|
||||
/// assert_eq!(4, text.height());
|
||||
///
|
||||
/// // Adding a final two styled lines
|
||||
/// text.extend(Text::styled("Some more lines\nnow with more style!", style));
|
||||
/// assert_eq!(6, text.height());
|
||||
/// let mut text = Text::default();
|
||||
/// text.extend(vec![
|
||||
/// Line::from("The first line"),
|
||||
/// Line::from("The second line"),
|
||||
/// ]);
|
||||
/// text.extend(Text::from("The third line\nThe fourth line"));
|
||||
/// ```
|
||||
///
|
||||
/// ## Styling Text
|
||||
///
|
||||
/// The text's [`Style`] is used by the rendering widget to determine how to style the text. Each
|
||||
/// [`Line`] in the text will be styled with the [`Style`] of the text, and then with its own
|
||||
/// [`Style`]. `Text` also implements [`Styled`] which means you can use the methods of the
|
||||
/// [`Stylize`] trait.
|
||||
///
|
||||
/// ```rust
|
||||
/// # 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()
|
||||
/// .italic();
|
||||
/// let text = Text::from(vec![
|
||||
/// Line::from("The first line").yellow(),
|
||||
/// Line::from("The second line").yellow(),
|
||||
/// ])
|
||||
/// .italic();
|
||||
/// ```
|
||||
///
|
||||
/// ## Aligning Text
|
||||
/// The text's [`Alignment`] can be set using [`Text::alignment`] or the related helper methods.
|
||||
/// Lines composing the text can also be individually aligned with [`Line::alignment`].
|
||||
///
|
||||
/// ```rust
|
||||
/// # 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![
|
||||
/// Line::from("The first line").left_aligned(),
|
||||
/// Line::from("The second line").right_aligned(),
|
||||
/// Line::from("The third line"),
|
||||
/// ])
|
||||
/// .centered();
|
||||
/// ```
|
||||
///
|
||||
/// ## Rendering Text
|
||||
/// `Text` implements the [`Widget`] trait, which means it can be rendered to a [`Buffer`] or to a
|
||||
/// [`Frame`].
|
||||
///
|
||||
/// ```rust
|
||||
/// # 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");
|
||||
/// text.render(area, buf);
|
||||
/// # }
|
||||
///
|
||||
/// // within a terminal.draw closure:
|
||||
/// # fn draw(frame: &mut Frame, area: Rect) {
|
||||
/// let text = Text::from("The first line\nThe second line");
|
||||
/// frame.render_widget(text, area);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Rendering Text with a Paragraph Widget
|
||||
///
|
||||
/// Usually apps will use the [`Paragraph`] widget instead of rendering a `Text` directly as it
|
||||
/// provides more functionality.
|
||||
///
|
||||
/// ```rust
|
||||
/// # 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)
|
||||
/// .wrap(Wrap { trim: true })
|
||||
/// .scroll((1, 1))
|
||||
/// .render(area, buf);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`Paragraph`]: crate::widgets::Paragraph
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Text<'a> {
|
||||
/// The lines that make up this piece of text.
|
||||
@@ -86,7 +185,7 @@ impl<'a> Text<'a> {
|
||||
/// Text::raw("The first line\nThe second line");
|
||||
/// Text::raw(String::from("The first line\nThe second line"));
|
||||
/// ```
|
||||
pub fn raw<T>(content: T) -> Text<'a>
|
||||
pub fn raw<T>(content: T) -> Self
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
{
|
||||
@@ -96,8 +195,7 @@ impl<'a> Text<'a> {
|
||||
Cow::Owned(s) if s.is_empty() => vec![Line::from("")],
|
||||
Cow::Owned(s) => s.lines().map(|l| Line::from(l.to_owned())).collect(),
|
||||
};
|
||||
|
||||
Text::from(lines)
|
||||
Self::from(lines)
|
||||
}
|
||||
|
||||
/// Create some text (potentially multiple lines) with a style.
|
||||
@@ -115,12 +213,12 @@ impl<'a> Text<'a> {
|
||||
/// Text::styled("The first line\nThe second line", style);
|
||||
/// Text::styled(String::from("The first line\nThe second line"), style);
|
||||
/// ```
|
||||
pub fn styled<T, S>(content: T, style: S) -> Text<'a>
|
||||
pub fn styled<T, S>(content: T, style: S) -> Self
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
S: Into<Style>,
|
||||
{
|
||||
Text::raw(content).patch_style(style)
|
||||
Self::raw(content).patch_style(style)
|
||||
}
|
||||
|
||||
/// Returns the max width of all the lines.
|
||||
@@ -345,6 +443,46 @@ impl<'a> Text<'a> {
|
||||
pub fn iter_mut(&mut self) -> std::slice::IterMut<Line<'a>> {
|
||||
self.lines.iter_mut()
|
||||
}
|
||||
|
||||
/// Adds a line to the text.
|
||||
///
|
||||
/// `line` can be any type that can be converted into a `Line`. For example, you can pass a
|
||||
/// `&str`, a `String`, a `Span`, or a `Line`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # 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?"));
|
||||
/// text.push_line("How are you?");
|
||||
/// ```
|
||||
pub fn push_line<T: Into<Line<'a>>>(&mut self, line: T) {
|
||||
self.lines.push(line.into());
|
||||
}
|
||||
|
||||
/// Adds a span to the last line of the text.
|
||||
///
|
||||
/// `span` can be any type that is convertible into a `Span`. For example, you can pass a
|
||||
/// `&str`, a `String`, or a `Span`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// let mut text = Text::from("Hello, world!");
|
||||
/// text.push_span(Span::from("How are you?"));
|
||||
/// text.push_span("How are you?");
|
||||
/// ```
|
||||
pub fn push_span<T: Into<Span<'a>>>(&mut self, span: T) {
|
||||
let span = span.into();
|
||||
if let Some(last) = self.lines.last_mut() {
|
||||
last.push_span(span);
|
||||
} else {
|
||||
self.lines.push(Line::from(span));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for Text<'a> {
|
||||
@@ -375,26 +513,26 @@ impl<'a> IntoIterator for &'a mut Text<'a> {
|
||||
}
|
||||
|
||||
impl<'a> From<String> for Text<'a> {
|
||||
fn from(s: String) -> Text<'a> {
|
||||
Text::raw(s)
|
||||
fn from(s: String) -> Self {
|
||||
Self::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Text<'a> {
|
||||
fn from(s: &'a str) -> Text<'a> {
|
||||
Text::raw(s)
|
||||
fn from(s: &'a str) -> Self {
|
||||
Self::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Cow<'a, str>> for Text<'a> {
|
||||
fn from(s: Cow<'a, str>) -> Text<'a> {
|
||||
Text::raw(s)
|
||||
fn from(s: Cow<'a, str>) -> Self {
|
||||
Self::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Span<'a>> for Text<'a> {
|
||||
fn from(span: Span<'a>) -> Text<'a> {
|
||||
Text {
|
||||
fn from(span: Span<'a>) -> Self {
|
||||
Self {
|
||||
lines: vec![Line::from(span)],
|
||||
..Default::default()
|
||||
}
|
||||
@@ -402,8 +540,8 @@ impl<'a> From<Span<'a>> for Text<'a> {
|
||||
}
|
||||
|
||||
impl<'a> From<Line<'a>> for Text<'a> {
|
||||
fn from(line: Line<'a>) -> Text<'a> {
|
||||
Text {
|
||||
fn from(line: Line<'a>) -> Self {
|
||||
Self {
|
||||
lines: vec![line],
|
||||
..Default::default()
|
||||
}
|
||||
@@ -411,8 +549,21 @@ impl<'a> From<Line<'a>> for Text<'a> {
|
||||
}
|
||||
|
||||
impl<'a> From<Vec<Line<'a>>> for Text<'a> {
|
||||
fn from(lines: Vec<Line<'a>>) -> Text<'a> {
|
||||
Text {
|
||||
fn from(lines: Vec<Line<'a>>) -> Self {
|
||||
Self {
|
||||
lines,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> FromIterator<T> for Text<'a>
|
||||
where
|
||||
T: Into<Line<'a>>,
|
||||
{
|
||||
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
|
||||
let lines = iter.into_iter().map(Into::into).collect();
|
||||
Self {
|
||||
lines,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -450,6 +601,7 @@ impl Widget for Text<'_> {
|
||||
|
||||
impl WidgetRef for Text<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
let area = area.intersection(buf.area);
|
||||
buf.set_style(area, self.style);
|
||||
for (line, row) in self.iter().zip(area.rows()) {
|
||||
let line_width = line.width() as u16;
|
||||
@@ -473,7 +625,7 @@ impl WidgetRef for Text<'_> {
|
||||
}
|
||||
|
||||
impl<'a> Styled for Text<'a> {
|
||||
type Item = Text<'a>;
|
||||
type Item = Self;
|
||||
|
||||
fn style(&self) -> Style {
|
||||
self.style
|
||||
@@ -486,10 +638,16 @@ impl<'a> Styled for Text<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::iter;
|
||||
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
use super::*;
|
||||
use crate::style::Stylize;
|
||||
|
||||
#[fixture]
|
||||
fn small_buf() -> Buffer {
|
||||
Buffer::empty(Rect::new(0, 0, 10, 1))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw() {
|
||||
@@ -601,6 +759,26 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_iterator() {
|
||||
let text = Text::from_iter(vec!["The first line", "The second line"]);
|
||||
assert_eq!(
|
||||
text.lines,
|
||||
vec![Line::from("The first line"), Line::from("The second line")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect() {
|
||||
let text: Text = iter::once("The first line")
|
||||
.chain(iter::once("The second line"))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
text.lines,
|
||||
vec![Line::from("The first line"), Line::from("The second line")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_iter() {
|
||||
let text = Text::from("The first line\nThe second line");
|
||||
@@ -718,9 +896,73 @@ mod tests {
|
||||
assert_eq!(Text::default().italic().style, Modifier::ITALIC.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn left_aligned() {
|
||||
let text = Text::from("Hello, world!").left_aligned();
|
||||
assert_eq!(text.alignment, Some(Alignment::Left));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn centered() {
|
||||
let text = Text::from("Hello, world!").centered();
|
||||
assert_eq!(text.alignment, Some(Alignment::Center));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn right_aligned() {
|
||||
let text = Text::from("Hello, world!").right_aligned();
|
||||
assert_eq!(text.alignment, Some(Alignment::Right));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_line() {
|
||||
let mut text = Text::from("A");
|
||||
text.push_line(Line::from("B"));
|
||||
text.push_line(Span::from("C"));
|
||||
text.push_line("D");
|
||||
assert_eq!(
|
||||
text.lines,
|
||||
vec![
|
||||
Line::raw("A"),
|
||||
Line::raw("B"),
|
||||
Line::raw("C"),
|
||||
Line::raw("D")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_line_empty() {
|
||||
let mut text = Text::default();
|
||||
text.push_line(Line::from("Hello, world!"));
|
||||
assert_eq!(text.lines, vec![Line::from("Hello, world!")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_span() {
|
||||
let mut text = Text::from("A");
|
||||
text.push_span(Span::raw("B"));
|
||||
text.push_span("C");
|
||||
assert_eq!(
|
||||
text.lines,
|
||||
vec![Line::from(vec![
|
||||
Span::raw("A"),
|
||||
Span::raw("B"),
|
||||
Span::raw("C")
|
||||
])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_span_empty() {
|
||||
let mut text = Text::default();
|
||||
text.push_span(Span::raw("Hello, world!"));
|
||||
assert_eq!(text.lines, vec![Line::from(Span::raw("Hello, world!"))],);
|
||||
}
|
||||
|
||||
mod widget {
|
||||
use super::*;
|
||||
use crate::{assert_buffer_eq, style::Color};
|
||||
use crate::assert_buffer_eq;
|
||||
|
||||
#[test]
|
||||
fn render() {
|
||||
@@ -735,6 +977,13 @@ mod tests {
|
||||
assert_buffer_eq!(buf, expected_buf);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_out_of_bounds(mut small_buf: Buffer) {
|
||||
let out_of_bounds_area = Rect::new(20, 20, 10, 1);
|
||||
Text::from("Hello, world!").render(out_of_bounds_area, &mut small_buf);
|
||||
assert_eq!(small_buf, Buffer::empty(small_buf.area));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_right_aligned() {
|
||||
let text = Text::from("foo").alignment(Alignment::Right);
|
||||
@@ -815,24 +1064,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn left_aligned() {
|
||||
let text = Text::from("Hello, world!").left_aligned();
|
||||
assert_eq!(text.alignment, Some(Alignment::Left));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn centered() {
|
||||
let text = Text::from("Hello, world!").centered();
|
||||
assert_eq!(text.alignment, Some(Alignment::Center));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn right_aligned() {
|
||||
let text = Text::from("Hello, world!").right_aligned();
|
||||
assert_eq!(text.alignment, Some(Alignment::Right));
|
||||
}
|
||||
|
||||
mod iterators {
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -355,7 +355,7 @@ impl<W: WidgetRef> WidgetRef for Option<W> {
|
||||
/// to a stateful widget and render it later. It also allows you to render boxed stateful widgets.
|
||||
///
|
||||
/// This trait was introduced in Ratatui 0.26.0 and is implemented for all the internal stateful
|
||||
/// widgets. Implemetors should prefer to implement this over the `StatefulWidget` trait and add an
|
||||
/// widgets. Implementors should prefer to implement this over the `StatefulWidget` trait and add an
|
||||
/// implementation of `StatefulWidget` that calls `StatefulWidgetRef::render_ref` where backwards
|
||||
/// compatibility is required.
|
||||
///
|
||||
@@ -438,7 +438,7 @@ impl Widget for &str {
|
||||
/// [`Rect`].
|
||||
impl WidgetRef for &str {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_string(area.x, area.y, self, crate::style::Style::default())
|
||||
buf.set_string(area.x, area.y, self, crate::style::Style::default());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,7 +459,7 @@ impl Widget for String {
|
||||
/// without the need to give up ownership of the underlying text.
|
||||
impl WidgetRef for String {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_string(area.x, area.y, self, crate::style::Style::default())
|
||||
buf.set_string(area.x, area.y, self, crate::style::Style::default());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,7 +508,7 @@ mod tests {
|
||||
impl StatefulWidgetRef for PersonalGreeting {
|
||||
type State = String;
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
Line::from(format!("Hello {}", state)).render(area, buf);
|
||||
Line::from(format!("Hello {state}")).render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ pub use bar_group::BarGroup;
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// The following example creates a `BarChart` with two groups of bars.
|
||||
/// The first group is added by an array slice (`&[(&str, u64)]`).
|
||||
/// The following example creates a `BarChart` with two groups of bars.
|
||||
/// The first group is added by an array slice (`&[(&str, u64)]`).
|
||||
/// The second group is added by a [`BarGroup`] instance.
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
@@ -86,8 +86,8 @@ pub struct BarChart<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Default for BarChart<'a> {
|
||||
fn default() -> BarChart<'a> {
|
||||
BarChart {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
block: None,
|
||||
max: None,
|
||||
data: Vec::new(),
|
||||
@@ -105,11 +105,11 @@ impl<'a> Default for BarChart<'a> {
|
||||
}
|
||||
|
||||
impl<'a> BarChart<'a> {
|
||||
/// Add group of bars to the BarChart
|
||||
/// Add group of bars to the `BarChart`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// The following example creates a BarChart with two groups of bars.
|
||||
/// The following example creates a `BarChart` with two groups of bars.
|
||||
/// The first group is added by an array slice (`&[(&str, u64)]`).
|
||||
/// The second group is added by a [`BarGroup`] instance.
|
||||
/// ```
|
||||
@@ -118,7 +118,8 @@ impl<'a> BarChart<'a> {
|
||||
/// .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
|
||||
/// .data(BarGroup::default().bars(&[Bar::default().value(10), Bar::default().value(20)]));
|
||||
/// ```
|
||||
pub fn data(mut self, data: impl Into<BarGroup<'a>>) -> BarChart<'a> {
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn data(mut self, data: impl Into<BarGroup<'a>>) -> Self {
|
||||
let group: BarGroup = data.into();
|
||||
if !group.bars.is_empty() {
|
||||
self.data.push(group);
|
||||
@@ -128,7 +129,7 @@ impl<'a> BarChart<'a> {
|
||||
|
||||
/// Surround the [`BarChart`] with a [`Block`].
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn block(mut self, block: Block<'a>) -> BarChart<'a> {
|
||||
pub fn block(mut self, block: Block<'a>) -> Self {
|
||||
self.block = Some(block);
|
||||
self
|
||||
}
|
||||
@@ -163,7 +164,7 @@ impl<'a> BarChart<'a> {
|
||||
/// // f b b
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn max(mut self, max: u64) -> BarChart<'a> {
|
||||
pub const fn max(mut self, max: u64) -> Self {
|
||||
self.max = Some(max);
|
||||
self
|
||||
}
|
||||
@@ -176,7 +177,7 @@ 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
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn bar_style<S: Into<Style>>(mut self, style: S) -> BarChart<'a> {
|
||||
pub fn bar_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.bar_style = style.into();
|
||||
self
|
||||
}
|
||||
@@ -186,17 +187,17 @@ impl<'a> BarChart<'a> {
|
||||
/// For [`Horizontal`](crate::layout::Direction::Horizontal) bars this becomes the height of
|
||||
/// the bar.
|
||||
///
|
||||
/// If not set, this defaults to `1`.
|
||||
/// If not set, this defaults to `1`.
|
||||
/// The bar label also uses this value as its width.
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn bar_width(mut self, width: u16) -> BarChart<'a> {
|
||||
pub const fn bar_width(mut self, width: u16) -> Self {
|
||||
self.bar_width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the gap between each bar.
|
||||
///
|
||||
/// If not set, this defaults to `1`.
|
||||
/// If not set, this defaults to `1`.
|
||||
/// The bar label will never be larger than the bar itself, even if the gap is sufficient.
|
||||
///
|
||||
/// # Example
|
||||
@@ -213,7 +214,7 @@ impl<'a> BarChart<'a> {
|
||||
/// // f b
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn bar_gap(mut self, gap: u16) -> BarChart<'a> {
|
||||
pub const fn bar_gap(mut self, gap: u16) -> Self {
|
||||
self.bar_gap = gap;
|
||||
self
|
||||
}
|
||||
@@ -222,7 +223,7 @@ impl<'a> BarChart<'a> {
|
||||
///
|
||||
/// If not set, the default is [`bar::NINE_LEVELS`](crate::symbols::bar::NINE_LEVELS).
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn bar_set(mut self, bar_set: symbols::bar::Set) -> BarChart<'a> {
|
||||
pub const fn bar_set(mut self, bar_set: symbols::bar::Set) -> Self {
|
||||
self.bar_set = bar_set;
|
||||
self
|
||||
}
|
||||
@@ -237,9 +238,9 @@ impl<'a> BarChart<'a> {
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// [Bar::value_style] to set the value style individually.
|
||||
/// [`Bar::value_style`] to set the value style individually.
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn value_style<S: Into<Style>>(mut self, style: S) -> BarChart<'a> {
|
||||
pub fn value_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.value_style = style.into();
|
||||
self
|
||||
}
|
||||
@@ -254,16 +255,16 @@ impl<'a> BarChart<'a> {
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// [Bar::label] to set the label style individually.
|
||||
/// [`Bar::label`] to set the label style individually.
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn label_style<S: Into<Style>>(mut self, style: S) -> BarChart<'a> {
|
||||
pub fn label_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.label_style = style.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the gap between [`BarGroup`].
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn group_gap(mut self, gap: u16) -> BarChart<'a> {
|
||||
pub const fn group_gap(mut self, gap: u16) -> Self {
|
||||
self.group_gap = gap;
|
||||
self
|
||||
}
|
||||
@@ -275,7 +276,7 @@ impl<'a> BarChart<'a> {
|
||||
///
|
||||
/// The style will be applied to everything that isn't styled (borders, bars, labels, ...).
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> BarChart<'a> {
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
@@ -300,12 +301,13 @@ impl<'a> BarChart<'a> {
|
||||
/// █bar██
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn direction(mut self, direction: Direction) -> BarChart<'a> {
|
||||
pub const fn direction(mut self, direction: Direction) -> Self {
|
||||
self.direction = direction;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct LabelInfo {
|
||||
group_label_visible: bool,
|
||||
bar_label_visible: bool,
|
||||
@@ -398,13 +400,13 @@ impl BarChart<'_> {
|
||||
.iter()
|
||||
.flat_map(|group| group.bars.iter().map(|bar| &bar.label))
|
||||
.flatten() // bar.label is an Option<Line>
|
||||
.map(|label| label.width())
|
||||
.map(Line::width)
|
||||
.max()
|
||||
.unwrap_or(0) as u16;
|
||||
|
||||
let label_x = area.x;
|
||||
let bars_area = {
|
||||
let margin = if label_size == 0 { 0 } else { 1 };
|
||||
let margin = u16::from(label_size != 0);
|
||||
Rect {
|
||||
x: area.x + label_size + margin,
|
||||
width: area.width - label_size - margin,
|
||||
@@ -528,7 +530,7 @@ impl BarChart<'_> {
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.max(1u64)
|
||||
.max(1)
|
||||
}
|
||||
|
||||
fn render_labels_and_values(
|
||||
@@ -598,7 +600,7 @@ impl WidgetRef for BarChart<'_> {
|
||||
}
|
||||
|
||||
impl<'a> Styled for BarChart<'a> {
|
||||
type Item = BarChart<'a>;
|
||||
type Item = Self;
|
||||
fn style(&self) -> Style {
|
||||
self.style
|
||||
}
|
||||
@@ -848,7 +850,7 @@ mod tests {
|
||||
.fg(Color::Black)
|
||||
.bg(Color::White)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -989,7 +991,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_horizontal_bars_label_width_greater_than_bar_with_style() {
|
||||
test_horizontal_bars_label_width_greater_than_bar(Some(Color::White))
|
||||
test_horizontal_bars_label_width_greater_than_bar(Some(Color::White));
|
||||
}
|
||||
|
||||
/// Tests horizontal bars label are presents
|
||||
|
||||
@@ -36,7 +36,7 @@ pub struct Bar<'a> {
|
||||
pub(super) style: Style,
|
||||
/// style of the value printed at the bottom of the bar.
|
||||
pub(super) value_style: Style,
|
||||
/// optional text_value to be shown on the bar instead of the actual value
|
||||
/// optional `text_value` to be shown on the bar instead of the actual value
|
||||
pub(super) text_value: Option<String>,
|
||||
}
|
||||
|
||||
@@ -47,10 +47,10 @@ impl<'a> Bar<'a> {
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// [`Bar::value_style`] to style the value.
|
||||
/// [`Bar::value_style`] to style the value.
|
||||
/// [`Bar::text_value`] to set the displayed value.
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn value(mut self, value: u64) -> Bar<'a> {
|
||||
pub const fn value(mut self, value: u64) -> Self {
|
||||
self.value = value;
|
||||
self
|
||||
}
|
||||
@@ -58,12 +58,12 @@ impl<'a> Bar<'a> {
|
||||
/// Set the label of the bar.
|
||||
///
|
||||
/// For [`Vertical`](crate::layout::Direction::Vertical) bars,
|
||||
/// display the label **under** the bar.
|
||||
/// display the label **under** the bar.
|
||||
/// For [`Horizontal`](crate::layout::Direction::Horizontal) bars,
|
||||
/// display the label **in** the bar.
|
||||
/// display the label **in** the bar.
|
||||
/// See [`BarChart::direction`](crate::widgets::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>) -> Bar<'a> {
|
||||
pub fn label(mut self, label: Line<'a>) -> Self {
|
||||
self.label = Some(label);
|
||||
self
|
||||
}
|
||||
@@ -75,7 +75,7 @@ impl<'a> Bar<'a> {
|
||||
///
|
||||
/// This will apply to every non-styled element. It can be seen and used as a default value.
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Bar<'a> {
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
@@ -89,21 +89,21 @@ impl<'a> Bar<'a> {
|
||||
///
|
||||
/// [`Bar::value`] to set the value.
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn value_style<S: Into<Style>>(mut self, style: S) -> Bar<'a> {
|
||||
pub fn value_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.value_style = style.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the text value printed in the bar.
|
||||
///
|
||||
/// If `text_value` is not set, then the [ToString] representation of `value` will be shown on
|
||||
/// If `text_value` is not set, then the [`ToString`] representation of `value` will be shown on
|
||||
/// the bar.
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// [`Bar::value`] to set the value.
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn text_value(mut self, text_value: String) -> Bar<'a> {
|
||||
pub fn text_value(mut self, text_value: String) -> Self {
|
||||
self.text_value = Some(text_value);
|
||||
self
|
||||
}
|
||||
@@ -111,9 +111,9 @@ impl<'a> Bar<'a> {
|
||||
/// Render the value of the bar.
|
||||
///
|
||||
/// [`text_value`](Bar::text_value) is used if set, otherwise the value is converted to string.
|
||||
/// The value is rendered using value_style. If the value width is greater than the
|
||||
/// The value is rendered using `value_style`. If the value width is greater than the
|
||||
/// bar width, then the value is split into 2 parts. the first part is rendered in the bar
|
||||
/// using value_style. The second part is rendered outside the bar using bar_style
|
||||
/// using `value_style`. The second part is rendered outside the bar using `bar_style`
|
||||
pub(super) fn render_value_with_different_styles(
|
||||
&self,
|
||||
buf: &mut Buffer,
|
||||
@@ -156,10 +156,10 @@ impl<'a> Bar<'a> {
|
||||
ticks: u64,
|
||||
) {
|
||||
if self.value != 0 {
|
||||
const TICKS_PER_LINE: u64 = 8;
|
||||
let value = self.value.to_string();
|
||||
let value_label = self.text_value.as_ref().unwrap_or(&value);
|
||||
let width = value_label.width() as u16;
|
||||
const TICKS_PER_LINE: u64 = 8;
|
||||
// if we have enough space or the ticks are greater equal than 1 cell (8)
|
||||
// then print the value
|
||||
if width < max_width || (width == max_width && ticks >= TICKS_PER_LINE) {
|
||||
|
||||
@@ -23,14 +23,14 @@ pub struct BarGroup<'a> {
|
||||
impl<'a> BarGroup<'a> {
|
||||
/// Set the group label
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn label(mut self, label: Line<'a>) -> BarGroup<'a> {
|
||||
pub fn label(mut self, label: Line<'a>) -> Self {
|
||||
self.label = Some(label);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the bars of the group to be shown
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn bars(mut self, bars: &[Bar<'a>]) -> BarGroup<'a> {
|
||||
pub fn bars(mut self, bars: &[Bar<'a>]) -> Self {
|
||||
self.bars = bars.to_vec();
|
||||
self
|
||||
}
|
||||
@@ -65,8 +65,8 @@ impl<'a> BarGroup<'a> {
|
||||
}
|
||||
|
||||
impl<'a> From<&[(&'a str, u64)]> for BarGroup<'a> {
|
||||
fn from(value: &[(&'a str, u64)]) -> BarGroup<'a> {
|
||||
BarGroup {
|
||||
fn from(value: &[(&'a str, u64)]) -> Self {
|
||||
Self {
|
||||
label: None,
|
||||
bars: value
|
||||
.iter()
|
||||
@@ -77,13 +77,13 @@ impl<'a> From<&[(&'a str, u64)]> for BarGroup<'a> {
|
||||
}
|
||||
|
||||
impl<'a, const N: usize> From<&[(&'a str, u64); N]> for BarGroup<'a> {
|
||||
fn from(value: &[(&'a str, u64); N]) -> BarGroup<'a> {
|
||||
fn from(value: &[(&'a str, u64); N]) -> Self {
|
||||
Self::from(value.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&Vec<(&'a str, u64)>> for BarGroup<'a> {
|
||||
fn from(value: &Vec<(&'a str, u64)>) -> BarGroup<'a> {
|
||||
fn from(value: &Vec<(&'a str, u64)>) -> Self {
|
||||
let array: &[(&str, u64)] = value;
|
||||
Self::from(array)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ pub use title::{Position, Title};
|
||||
/// both centered and non-centered titles are rendered, the centered space is calculated based on
|
||||
/// the full width of the block, rather than the leftover width.
|
||||
///
|
||||
/// Titles are not rendered in the corners of the block unless there is no border on that edge.
|
||||
/// Titles are not rendered in the corners of the block unless there is no border on that edge.
|
||||
/// If the block is too small and multiple titles overlap, the border may get cut off at a corner.
|
||||
///
|
||||
/// ```plain
|
||||
@@ -174,7 +174,7 @@ impl<'a> Block<'a> {
|
||||
|
||||
/// Create a new block with [all borders](Borders::ALL) shown
|
||||
pub const fn bordered() -> Self {
|
||||
let mut block = Block::new();
|
||||
let mut block = Self::new();
|
||||
block.borders = Borders::ALL;
|
||||
block
|
||||
}
|
||||
@@ -193,7 +193,7 @@ impl<'a> Block<'a> {
|
||||
/// [spans](crate::text::Span) (`Vec<Span>`).
|
||||
///
|
||||
/// By default, the titles will avoid being rendered in the corners of the block but will align
|
||||
/// against the left or right edge of the block if there is no border on that edge.
|
||||
/// against the left or right edge of the block if there is no border on that edge.
|
||||
/// The following demonstrates this behavior, notice the second title is one character off to
|
||||
/// the left.
|
||||
///
|
||||
@@ -234,7 +234,8 @@ impl<'a> Block<'a> {
|
||||
/// - [`Block::title_style`]
|
||||
/// - [`Block::title_alignment`]
|
||||
/// - [`Block::title_position`]
|
||||
pub fn title<T>(mut self, title: T) -> Block<'a>
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn title<T>(mut self, title: T) -> Self
|
||||
where
|
||||
T: Into<Title<'a>>,
|
||||
{
|
||||
@@ -305,7 +306,7 @@ impl<'a> Block<'a> {
|
||||
///
|
||||
/// If a [`Title`] already has a style, the title's style will add on top of this one.
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn title_style<S: Into<Style>>(mut self, style: S) -> Block<'a> {
|
||||
pub fn title_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.titles_style = style.into();
|
||||
self
|
||||
}
|
||||
@@ -332,7 +333,7 @@ impl<'a> Block<'a> {
|
||||
/// .title_alignment(Alignment::Center);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn title_alignment(mut self, alignment: Alignment) -> Block<'a> {
|
||||
pub const fn title_alignment(mut self, alignment: Alignment) -> Self {
|
||||
self.titles_alignment = alignment;
|
||||
self
|
||||
}
|
||||
@@ -359,7 +360,7 @@ impl<'a> Block<'a> {
|
||||
/// .title_position(Position::Bottom);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn title_position(mut self, position: Position) -> Block<'a> {
|
||||
pub const fn title_position(mut self, position: Position) -> Self {
|
||||
self.titles_position = position;
|
||||
self
|
||||
}
|
||||
@@ -381,7 +382,7 @@ impl<'a> Block<'a> {
|
||||
/// .border_style(Style::new().blue());
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn border_style<S: Into<Style>>(mut self, style: S) -> Block<'a> {
|
||||
pub fn border_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.border_style = style.into();
|
||||
self
|
||||
}
|
||||
@@ -397,7 +398,7 @@ impl<'a> Block<'a> {
|
||||
///
|
||||
/// This will also apply to the widget inside that block, unless the inner widget is styled.
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Block<'a> {
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
@@ -420,7 +421,7 @@ impl<'a> Block<'a> {
|
||||
/// Block::default().borders(Borders::LEFT | Borders::RIGHT);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn borders(mut self, flag: Borders) -> Block<'a> {
|
||||
pub const fn borders(mut self, flag: Borders) -> Self {
|
||||
self.borders = flag;
|
||||
self
|
||||
}
|
||||
@@ -446,7 +447,7 @@ impl<'a> Block<'a> {
|
||||
/// // ╰─────╯
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn border_type(mut self, border_type: BorderType) -> Block<'a> {
|
||||
pub const fn border_type(mut self, border_type: BorderType) -> Self {
|
||||
self.border_set = border_type.to_border_set();
|
||||
self
|
||||
}
|
||||
@@ -465,7 +466,7 @@ impl<'a> Block<'a> {
|
||||
/// // ║ ║
|
||||
/// // ╚═════╝
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn border_set(mut self, border_set: border::Set) -> Block<'a> {
|
||||
pub const fn border_set(mut self, border_set: border::Set) -> Self {
|
||||
self.border_set = border_set;
|
||||
self
|
||||
}
|
||||
@@ -562,7 +563,7 @@ impl<'a> Block<'a> {
|
||||
/// // └───────────┘
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn padding(mut self, padding: Padding) -> Block<'a> {
|
||||
pub const fn padding(mut self, padding: Padding) -> Self {
|
||||
self.padding = padding;
|
||||
self
|
||||
}
|
||||
@@ -570,14 +571,14 @@ impl<'a> Block<'a> {
|
||||
|
||||
impl BorderType {
|
||||
/// Convert this `BorderType` into the corresponding [`Set`](border::Set) of border symbols.
|
||||
pub const fn border_symbols(border_type: BorderType) -> border::Set {
|
||||
pub const fn border_symbols(border_type: Self) -> border::Set {
|
||||
match border_type {
|
||||
BorderType::Plain => border::PLAIN,
|
||||
BorderType::Rounded => border::ROUNDED,
|
||||
BorderType::Double => border::DOUBLE,
|
||||
BorderType::Thick => border::THICK,
|
||||
BorderType::QuadrantInside => border::QUADRANT_INSIDE,
|
||||
BorderType::QuadrantOutside => border::QUADRANT_OUTSIDE,
|
||||
Self::Plain => border::PLAIN,
|
||||
Self::Rounded => border::ROUNDED,
|
||||
Self::Double => border::DOUBLE,
|
||||
Self::Thick => border::THICK,
|
||||
Self::QuadrantInside => border::QUADRANT_INSIDE,
|
||||
Self::QuadrantOutside => border::QUADRANT_OUTSIDE,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,7 +710,8 @@ impl Block<'_> {
|
||||
/// Currently (due to the way lines are truncated), the right side of the leftmost title will
|
||||
/// be cut off if the block is too small to fit all titles. This is not ideal and should be
|
||||
/// the left side of that leftmost that is cut off. This is due to the line being truncated
|
||||
/// incorrectly. See https://github.com/ratatui-org/ratatui/issues/932
|
||||
/// incorrectly. See <https://github.com/ratatui-org/ratatui/issues/932>
|
||||
#[allow(clippy::similar_names)]
|
||||
fn render_right_titles(&self, position: Position, area: Rect, buf: &mut Buffer) {
|
||||
let titles = self.filtered_titles(position, Alignment::Right);
|
||||
let mut titles_area = self.titles_area(area, position);
|
||||
@@ -744,6 +746,7 @@ impl Block<'_> {
|
||||
/// Currently this method aligns the titles to the left inside a centered area. This is not
|
||||
/// ideal and should be fixed in the future to align the titles to the center of the block and
|
||||
/// truncate both sides of the titles if the block is too small to fit all titles.
|
||||
#[allow(clippy::similar_names)]
|
||||
fn render_center_titles(&self, position: Position, area: Rect, buf: &mut Buffer) {
|
||||
let titles = self
|
||||
.filtered_titles(position, Alignment::Center)
|
||||
@@ -778,6 +781,7 @@ impl Block<'_> {
|
||||
}
|
||||
|
||||
/// Render titles aligned to the left of the block
|
||||
#[allow(clippy::similar_names)]
|
||||
fn render_left_titles(&self, position: Position, area: Rect, buf: &mut Buffer) {
|
||||
let titles = self.filtered_titles(position, Alignment::Left);
|
||||
let mut titles_area = self.titles_area(area, position);
|
||||
@@ -849,7 +853,7 @@ impl BlockExt for Option<Block<'_>> {
|
||||
}
|
||||
|
||||
impl<'a> Styled for Block<'a> {
|
||||
type Item = Block<'a>;
|
||||
type Item = Self;
|
||||
|
||||
fn style(&self) -> Style {
|
||||
self.style
|
||||
@@ -865,11 +869,7 @@ mod tests {
|
||||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
assert_buffer_eq,
|
||||
layout::{Alignment, Rect},
|
||||
style::{Color, Modifier, Stylize},
|
||||
};
|
||||
use crate::assert_buffer_eq;
|
||||
|
||||
#[test]
|
||||
fn create_with_all_borders() {
|
||||
@@ -877,6 +877,7 @@ mod tests {
|
||||
assert_eq!(block.borders, Borders::all());
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[test]
|
||||
fn inner_takes_into_account_the_borders() {
|
||||
// No borders
|
||||
@@ -1107,7 +1108,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn border_type_can_be_const() {
|
||||
const fn border_type_can_be_const() {
|
||||
const _PLAIN: border::Set = BorderType::border_symbols(BorderType::Plain);
|
||||
}
|
||||
|
||||
@@ -1126,11 +1127,11 @@ mod tests {
|
||||
style: Style::new(),
|
||||
padding: Padding::zero(),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_can_be_const() {
|
||||
const fn block_can_be_const() {
|
||||
const _DEFAULT_STYLE: Style = Style::new();
|
||||
const _DEFAULT_PADDING: Padding = Padding::uniform(1);
|
||||
const _DEFAULT_BLOCK: Block = Block::new()
|
||||
@@ -1144,7 +1145,7 @@ mod tests {
|
||||
.padding(_DEFAULT_PADDING);
|
||||
}
|
||||
|
||||
/// This test ensures that we have some coverage on the Style::from() implementations
|
||||
/// This test ensures that we have some coverage on the [`Style::from()`] implementations
|
||||
#[test]
|
||||
fn block_style() {
|
||||
// nominal style
|
||||
@@ -1198,14 +1199,14 @@ mod tests {
|
||||
.bg(Color::White)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
.remove_modifier(Modifier::DIM)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn title() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
use Alignment::*;
|
||||
use Position::*;
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
Block::bordered()
|
||||
.title(Title::from("A").position(Top).alignment(Left))
|
||||
.title(Title::from("B").position(Top).alignment(Center))
|
||||
@@ -1279,7 +1280,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a regression test for bug https://github.com/ratatui-org/ratatui/issues/929
|
||||
/// This is a regression test for bug <https://github.com/ratatui-org/ratatui/issues/929>
|
||||
#[test]
|
||||
fn render_right_aligned_empty_title() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Padding {
|
||||
///
|
||||
/// Note: the order of the fields does not match the order of the CSS properties.
|
||||
pub const fn new(left: u16, right: u16, top: u16, bottom: u16) -> Self {
|
||||
Padding {
|
||||
Self {
|
||||
left,
|
||||
right,
|
||||
top,
|
||||
@@ -49,7 +49,7 @@ impl Padding {
|
||||
|
||||
/// Creates a `Padding` with all fields set to `0`.
|
||||
pub const fn zero() -> Self {
|
||||
Padding {
|
||||
Self {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
@@ -59,7 +59,7 @@ impl Padding {
|
||||
|
||||
/// Creates a `Padding` with the same value for `left` and `right`.
|
||||
pub const fn horizontal(value: u16) -> Self {
|
||||
Padding {
|
||||
Self {
|
||||
left: value,
|
||||
right: value,
|
||||
top: 0,
|
||||
@@ -69,7 +69,7 @@ impl Padding {
|
||||
|
||||
/// Creates a `Padding` with the same value for `top` and `bottom`.
|
||||
pub const fn vertical(value: u16) -> Self {
|
||||
Padding {
|
||||
Self {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: value,
|
||||
@@ -79,7 +79,7 @@ impl Padding {
|
||||
|
||||
/// Creates a `Padding` with the same value for all fields.
|
||||
pub const fn uniform(value: u16) -> Self {
|
||||
Padding {
|
||||
Self {
|
||||
left: value,
|
||||
right: value,
|
||||
top: value,
|
||||
@@ -92,7 +92,7 @@ impl Padding {
|
||||
/// This represents a padding of 2x the value for `left` and `right` and 1x the value for
|
||||
/// `top` and `bottom`.
|
||||
pub const fn proportional(value: u16) -> Self {
|
||||
Padding {
|
||||
Self {
|
||||
left: 2 * value,
|
||||
right: 2 * value,
|
||||
top: value,
|
||||
@@ -105,7 +105,7 @@ impl Padding {
|
||||
/// The `x` value is used for `left` and `right` and the `y` value is used for `top` and
|
||||
/// `bottom`.
|
||||
pub const fn symmetric(x: u16, y: u16) -> Self {
|
||||
Padding {
|
||||
Self {
|
||||
left: x,
|
||||
right: x,
|
||||
top: y,
|
||||
@@ -115,7 +115,7 @@ impl Padding {
|
||||
|
||||
/// Creates a `Padding` that only sets the `left` padding.
|
||||
pub const fn left(value: u16) -> Self {
|
||||
Padding {
|
||||
Self {
|
||||
left: value,
|
||||
right: 0,
|
||||
top: 0,
|
||||
@@ -125,7 +125,7 @@ impl Padding {
|
||||
|
||||
/// Creates a `Padding` that only sets the `right` padding.
|
||||
pub const fn right(value: u16) -> Self {
|
||||
Padding {
|
||||
Self {
|
||||
left: 0,
|
||||
right: value,
|
||||
top: 0,
|
||||
@@ -135,7 +135,7 @@ impl Padding {
|
||||
|
||||
/// Creates a `Padding` that only sets the `top` padding.
|
||||
pub const fn top(value: u16) -> Self {
|
||||
Padding {
|
||||
Self {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: value,
|
||||
@@ -145,7 +145,7 @@ impl Padding {
|
||||
|
||||
/// Creates a `Padding` that only sets the `bottom` padding.
|
||||
pub const fn bottom(value: u16) -> Self {
|
||||
Padding {
|
||||
Self {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
@@ -168,7 +168,7 @@ mod tests {
|
||||
top: 3,
|
||||
bottom: 4
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -186,7 +186,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_be_const() {
|
||||
const fn can_be_const() {
|
||||
const _PADDING: Padding = Padding::new(1, 1, 1, 1);
|
||||
const _UNI_PADDING: Padding = Padding::uniform(1);
|
||||
const _NO_PADDING: Padding = Padding::zero();
|
||||
|
||||
@@ -87,7 +87,8 @@ pub enum Position {
|
||||
|
||||
impl<'a> Title<'a> {
|
||||
/// Set the title content.
|
||||
pub fn content<T>(mut self, content: T) -> Title<'a>
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn content<T>(mut self, content: T) -> Self
|
||||
where
|
||||
T: Into<Line<'a>>,
|
||||
{
|
||||
@@ -97,14 +98,14 @@ impl<'a> Title<'a> {
|
||||
|
||||
/// Set the title alignment.
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn alignment(mut self, alignment: Alignment) -> Title<'a> {
|
||||
pub const fn alignment(mut self, alignment: Alignment) -> Self {
|
||||
self.alignment = Some(alignment);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the title position.
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn position(mut self, position: Position) -> Title<'a> {
|
||||
pub const fn position(mut self, position: Position) -> Self {
|
||||
self.position = Some(position);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ impl Debug for Borders {
|
||||
}
|
||||
let mut first = true;
|
||||
for (name, border) in self.iter_names() {
|
||||
if border == Borders::NONE {
|
||||
if border == Self::NONE {
|
||||
continue;
|
||||
}
|
||||
if first {
|
||||
@@ -51,39 +51,52 @@ impl Debug for Borders {
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro that constructs and returns a [`Borders`] object from TOP, BOTTOM, LEFT, RIGHT, NONE, and
|
||||
/// ALL. Internally it creates an empty `Borders` object and then inserts each bit flag specified
|
||||
/// into it using `Borders::insert()`.
|
||||
/// Macro that constructs and returns a combination of the [`Borders`] object from TOP, BOTTOM, LEFT
|
||||
/// and RIGHT.
|
||||
///
|
||||
/// When used with NONE you should consider omitting this completely. For ALL you should consider
|
||||
/// [`Block::bordered()`](crate::widgets::Block::bordered) instead.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
///```
|
||||
/// use ratatui::{border, prelude::*, widgets::*};
|
||||
///
|
||||
/// Block::default()
|
||||
/// //Construct a `Borders` object and use it in place
|
||||
/// ```
|
||||
/// # use ratatui::{border, prelude::*, widgets::*};
|
||||
/// Block::new()
|
||||
/// .title("Construct Borders and use them in place")
|
||||
/// .borders(border!(TOP, BOTTOM));
|
||||
/// ```
|
||||
///
|
||||
/// //`border!` can be called with any order of individual sides
|
||||
/// let bottom_first = border!(BOTTOM, LEFT, TOP);
|
||||
/// //with the ALL keyword which works as expected
|
||||
/// let all = border!(ALL);
|
||||
/// //or with nothing to return a `Borders::NONE' bitflag.
|
||||
/// let none = border!(NONE);
|
||||
/// `border!` can be called with any number of individual sides:
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{border, prelude::*, widgets::*};
|
||||
/// let right_open = border!(TOP, LEFT, BOTTOM);
|
||||
/// assert_eq!(right_open, Borders::TOP | Borders::LEFT | Borders::BOTTOM);
|
||||
/// ```
|
||||
///
|
||||
/// Single borders work but using `Borders::` directly would be simpler.
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{border, prelude::*, widgets::*};
|
||||
/// assert_eq!(border!(TOP), Borders::TOP);
|
||||
/// assert_eq!(border!(ALL), Borders::ALL);
|
||||
/// assert_eq!(border!(), Borders::NONE);
|
||||
/// ```
|
||||
#[cfg(feature = "macros")]
|
||||
#[macro_export]
|
||||
macro_rules! border {
|
||||
( $($b:tt), +) => {{
|
||||
let mut border = Borders::empty();
|
||||
() => {
|
||||
Borders::NONE
|
||||
};
|
||||
($b:ident) => {
|
||||
Borders::$b
|
||||
};
|
||||
($first:ident,$($other:ident),*) => {
|
||||
Borders::$first
|
||||
$(
|
||||
border.insert(Borders::$b);
|
||||
.union(Borders::$other)
|
||||
)*
|
||||
border
|
||||
}};
|
||||
() =>{
|
||||
Borders::NONE
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -107,3 +120,40 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "macros"))]
|
||||
mod macro_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn can_be_const() {
|
||||
const NOTHING: Borders = border!();
|
||||
const JUST_TOP: Borders = border!(TOP);
|
||||
const TOP_BOTTOM: Borders = border!(TOP, BOTTOM);
|
||||
const RIGHT_OPEN: Borders = border!(TOP, LEFT, BOTTOM);
|
||||
|
||||
assert_eq!(NOTHING, Borders::NONE);
|
||||
assert_eq!(JUST_TOP, Borders::TOP);
|
||||
assert_eq!(TOP_BOTTOM, Borders::TOP | Borders::BOTTOM);
|
||||
assert_eq!(RIGHT_OPEN, Borders::TOP | Borders::LEFT | Borders::BOTTOM);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn border_empty() {
|
||||
let empty = Borders::NONE;
|
||||
assert_eq!(empty, border!());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn border_all() {
|
||||
let all = Borders::ALL;
|
||||
assert_eq!(all, border!(ALL));
|
||||
assert_eq!(all, border!(TOP, BOTTOM, LEFT, RIGHT));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn border_left_right() {
|
||||
let left_right = Borders::from_bits(Borders::LEFT.bits() | Borders::RIGHT.bits());
|
||||
assert_eq!(left_right, Some(border!(RIGHT, LEFT)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//! The [`Monthly`] widget will display a calendar for the monh provided in `display_date`. Days are
|
||||
//! styled using the default style unless:
|
||||
//! The [`Monthly`] widget will display a calendar for the month provided in `display_date`. Days
|
||||
//! are styled using the default style unless:
|
||||
//! * `show_surrounding` is set, then days not in the `display_date` month will use that style.
|
||||
//! * a style is returned by the [`DateStyler`] for the day
|
||||
//!
|
||||
@@ -46,6 +46,7 @@ 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>`]).
|
||||
#[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());
|
||||
self
|
||||
@@ -55,6 +56,7 @@ 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>`]).
|
||||
#[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());
|
||||
self
|
||||
@@ -64,6 +66,7 @@ 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>`]).
|
||||
#[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());
|
||||
self
|
||||
@@ -73,12 +76,14 @@ 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>`]).
|
||||
#[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();
|
||||
self
|
||||
}
|
||||
|
||||
/// Render the calendar within a [Block]
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn block(mut self, block: Block<'a>) -> Self {
|
||||
self.block = Some(block);
|
||||
self
|
||||
@@ -94,7 +99,12 @@ impl<'a, DS: DateStyler> Monthly<'a, DS> {
|
||||
|
||||
/// All logic to style a date goes here.
|
||||
fn format_date(&self, date: Date) -> Span {
|
||||
if date.month() != self.display_date.month() {
|
||||
if date.month() == self.display_date.month() {
|
||||
Span::styled(
|
||||
format!("{:2?}", date.day()),
|
||||
self.default_style.patch(self.events.get_style(date)),
|
||||
)
|
||||
} else {
|
||||
match self.show_surrounding {
|
||||
None => Span::styled(" ", self.default_bg()),
|
||||
Some(s) => {
|
||||
@@ -105,11 +115,6 @@ impl<'a, DS: DateStyler> Monthly<'a, DS> {
|
||||
Span::styled(format!("{:2?}", date.day()), style)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Span::styled(
|
||||
format!("{:2?}", date.day()),
|
||||
self.default_style.patch(self.events.get_style(date)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,7 +248,6 @@ mod tests {
|
||||
use time::Month;
|
||||
|
||||
use super::*;
|
||||
use crate::style::Color;
|
||||
|
||||
#[test]
|
||||
fn event_store() {
|
||||
|
||||
@@ -30,7 +30,7 @@ pub use self::{
|
||||
points::Points,
|
||||
rectangle::Rectangle,
|
||||
};
|
||||
use crate::{prelude::*, symbols, text::Line as TextLine, widgets::Block};
|
||||
use crate::{prelude::*, text::Line as TextLine, widgets::Block};
|
||||
|
||||
/// Something that can be drawn on a [`Canvas`].
|
||||
///
|
||||
@@ -55,7 +55,7 @@ pub struct Label<'a> {
|
||||
///
|
||||
/// This allows the canvas to be drawn in multiple layers. This is useful if you want to draw
|
||||
/// multiple shapes on the canvas in specific order.
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug)]
|
||||
struct Layer {
|
||||
// A string of characters representing the grid. This will be wrapped to the width of the grid
|
||||
// when rendering
|
||||
@@ -71,10 +71,6 @@ struct Layer {
|
||||
/// Braille patterns will have a resolution of 2x4 dots per cell. This means that a grid of 10x10
|
||||
/// cells will have a resolution of 20x40 dots.
|
||||
trait Grid: Debug {
|
||||
/// Get the width of the grid in number of terminal columns
|
||||
fn width(&self) -> u16;
|
||||
/// Get the height of the grid in number of terminal rows
|
||||
fn height(&self) -> u16;
|
||||
/// Get the resolution of the grid in number of dots.
|
||||
///
|
||||
/// This doesn't have to be the same as the number of rows and columns of the grid. For example,
|
||||
@@ -92,7 +88,7 @@ trait Grid: Debug {
|
||||
fn reset(&mut self);
|
||||
}
|
||||
|
||||
/// The BrailleGrid is a grid made up of cells each containing a Braille pattern.
|
||||
/// The `BrailleGrid` is a grid made up of cells each containing a Braille pattern.
|
||||
///
|
||||
/// This makes it possible to draw shapes with a resolution of 2x4 dots per cell. This is useful
|
||||
/// when you want to draw shapes with a high resolution. Font support for Braille patterns is
|
||||
@@ -101,7 +97,7 @@ trait Grid: Debug {
|
||||
///
|
||||
/// This grid type only supports a single foreground color for each 2x4 dots cell. There is no way
|
||||
/// to set the individual color of each dot in the braille pattern.
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug)]
|
||||
struct BrailleGrid {
|
||||
/// Width of the grid in number of terminal columns
|
||||
width: u16,
|
||||
@@ -117,11 +113,11 @@ struct BrailleGrid {
|
||||
}
|
||||
|
||||
impl BrailleGrid {
|
||||
/// Create a new BrailleGrid with the given width and height measured in terminal columns and
|
||||
/// Create a new `BrailleGrid` with the given width and height measured in terminal columns and
|
||||
/// rows respectively.
|
||||
fn new(width: u16, height: u16) -> BrailleGrid {
|
||||
fn new(width: u16, height: u16) -> Self {
|
||||
let length = usize::from(width * height);
|
||||
BrailleGrid {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
utf16_code_points: vec![symbols::braille::BLANK; length],
|
||||
@@ -131,14 +127,6 @@ impl BrailleGrid {
|
||||
}
|
||||
|
||||
impl Grid for BrailleGrid {
|
||||
fn width(&self) -> u16 {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> u16 {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn resolution(&self) -> (f64, f64) {
|
||||
(f64::from(self.width) * 2.0, f64::from(self.height) * 4.0)
|
||||
}
|
||||
@@ -168,11 +156,11 @@ impl Grid for BrailleGrid {
|
||||
}
|
||||
}
|
||||
|
||||
/// The CharGrid is a grid made up of cells each containing a single character.
|
||||
/// The `CharGrid` is a grid made up of cells each containing a single character.
|
||||
///
|
||||
/// This makes it possible to draw shapes with a resolution of 1x1 dots per cell. This is useful
|
||||
/// when you want to draw shapes with a low resolution.
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug)]
|
||||
struct CharGrid {
|
||||
/// Width of the grid in number of terminal columns
|
||||
width: u16,
|
||||
@@ -187,11 +175,11 @@ struct CharGrid {
|
||||
}
|
||||
|
||||
impl CharGrid {
|
||||
/// Create a new CharGrid with the given width and height measured in terminal columns and
|
||||
/// Create a new `CharGrid` with the given width and height measured in terminal columns and
|
||||
/// rows respectively.
|
||||
fn new(width: u16, height: u16, cell_char: char) -> CharGrid {
|
||||
fn new(width: u16, height: u16, cell_char: char) -> Self {
|
||||
let length = usize::from(width * height);
|
||||
CharGrid {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
cells: vec![' '; length],
|
||||
@@ -202,14 +190,6 @@ impl CharGrid {
|
||||
}
|
||||
|
||||
impl Grid for CharGrid {
|
||||
fn width(&self) -> u16 {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> u16 {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn resolution(&self) -> (f64, f64) {
|
||||
(f64::from(self.width), f64::from(self.height))
|
||||
}
|
||||
@@ -239,7 +219,7 @@ impl Grid for CharGrid {
|
||||
}
|
||||
}
|
||||
|
||||
/// The HalfBlockGrid is a grid made up of cells each containing a half block character.
|
||||
/// The `HalfBlockGrid` is a grid made up of cells each containing a half block character.
|
||||
///
|
||||
/// In terminals, each character is usually twice as tall as it is wide. Unicode has a couple of
|
||||
/// vertical half block characters, the upper half block '▀' and lower half block '▄' which take up
|
||||
@@ -249,10 +229,10 @@ impl Grid for CharGrid {
|
||||
/// and lower half of each cell. This allows us to draw shapes with a resolution of 1x2 "pixels" per
|
||||
/// cell.
|
||||
///
|
||||
/// This allows for more flexibility than the BrailleGrid which only supports a single
|
||||
/// foreground color for each 2x4 dots cell, and the CharGrid which only supports a single
|
||||
/// This allows for more flexibility than the `BrailleGrid` which only supports a single
|
||||
/// foreground color for each 2x4 dots cell, and the `CharGrid` which only supports a single
|
||||
/// character for each cell.
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug)]
|
||||
struct HalfBlockGrid {
|
||||
/// Width of the grid in number of terminal columns
|
||||
width: u16,
|
||||
@@ -265,8 +245,8 @@ struct HalfBlockGrid {
|
||||
impl HalfBlockGrid {
|
||||
/// Create a new `HalfBlockGrid` with the given width and height measured in terminal columns
|
||||
/// and rows respectively.
|
||||
fn new(width: u16, height: u16) -> HalfBlockGrid {
|
||||
HalfBlockGrid {
|
||||
fn new(width: u16, height: u16) -> Self {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
pixels: vec![vec![Color::Reset; width as usize]; height as usize * 2],
|
||||
@@ -275,14 +255,6 @@ impl HalfBlockGrid {
|
||||
}
|
||||
|
||||
impl Grid for HalfBlockGrid {
|
||||
fn width(&self) -> u16 {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> u16 {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn resolution(&self) -> (f64, f64) {
|
||||
(f64::from(self.width), f64::from(self.height) * 2.0)
|
||||
}
|
||||
@@ -439,9 +411,9 @@ impl<'a, 'b> Painter<'a, 'b> {
|
||||
}
|
||||
|
||||
impl<'a, 'b> From<&'a mut Context<'b>> for Painter<'a, 'b> {
|
||||
fn from(context: &'a mut Context<'b>) -> Painter<'a, 'b> {
|
||||
fn from(context: &'a mut Context<'b>) -> Self {
|
||||
let resolution = context.grid.resolution();
|
||||
Painter {
|
||||
Self {
|
||||
context,
|
||||
resolution,
|
||||
}
|
||||
@@ -493,7 +465,7 @@ impl<'a> Context<'a> {
|
||||
x_bounds: [f64; 2],
|
||||
y_bounds: [f64; 2],
|
||||
marker: symbols::Marker,
|
||||
) -> Context<'a> {
|
||||
) -> Self {
|
||||
let dot = symbols::DOT.chars().next().unwrap();
|
||||
let block = symbols::block::FULL.chars().next().unwrap();
|
||||
let bar = symbols::bar::HALF.chars().next().unwrap();
|
||||
@@ -504,7 +476,7 @@ impl<'a> Context<'a> {
|
||||
symbols::Marker::Braille => Box::new(BrailleGrid::new(width, height)),
|
||||
symbols::Marker::HalfBlock => Box::new(HalfBlockGrid::new(width, height)),
|
||||
};
|
||||
Context {
|
||||
Self {
|
||||
x_bounds,
|
||||
y_bounds,
|
||||
grid,
|
||||
@@ -570,9 +542,9 @@ impl<'a> Context<'a> {
|
||||
///
|
||||
/// See [Unicode Braille Patterns](https://en.wikipedia.org/wiki/Braille_Patterns) for more info.
|
||||
///
|
||||
/// The HalfBlock marker is useful when you want to draw shapes with a higher resolution than a
|
||||
/// CharGrid but lower than a BrailleGrid. This grid type supports a foreground and background color
|
||||
/// for each terminal cell. This allows for more flexibility than the BrailleGrid which only
|
||||
/// The `HalfBlock` marker is useful when you want to draw shapes with a higher resolution than a
|
||||
/// `CharGrid` but lower than a `BrailleGrid`. This grid type supports a foreground and background
|
||||
/// color for each terminal cell. This allows for more flexibility than the `BrailleGrid` which only
|
||||
/// supports a single foreground color for each 2x4 dots cell.
|
||||
///
|
||||
/// The Canvas widget is used by calling the [`Canvas::paint`] method and passing a closure that
|
||||
@@ -639,8 +611,8 @@ impl<'a, F> Default for Canvas<'a, F>
|
||||
where
|
||||
F: Fn(&mut Context),
|
||||
{
|
||||
fn default() -> Canvas<'a, F> {
|
||||
Canvas {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
block: None,
|
||||
x_bounds: [0.0, 0.0],
|
||||
y_bounds: [0.0, 0.0],
|
||||
@@ -659,7 +631,7 @@ where
|
||||
///
|
||||
/// This is a fluent setter method which must be chained or used as it consumes self
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn block(mut self, block: Block<'a>) -> Canvas<'a, F> {
|
||||
pub fn block(mut self, block: Block<'a>) -> Self {
|
||||
self.block = Some(block);
|
||||
self
|
||||
}
|
||||
@@ -671,7 +643,7 @@ where
|
||||
///
|
||||
/// This is a fluent setter method which must be chained or used as it consumes self
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn x_bounds(mut self, bounds: [f64; 2]) -> Canvas<'a, F> {
|
||||
pub const fn x_bounds(mut self, bounds: [f64; 2]) -> Self {
|
||||
self.x_bounds = bounds;
|
||||
self
|
||||
}
|
||||
@@ -683,7 +655,7 @@ where
|
||||
///
|
||||
/// This is a fluent setter method which must be chained or used as it consumes self
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn y_bounds(mut self, bounds: [f64; 2]) -> Canvas<'a, F> {
|
||||
pub const fn y_bounds(mut self, bounds: [f64; 2]) -> Self {
|
||||
self.y_bounds = bounds;
|
||||
self
|
||||
}
|
||||
@@ -692,7 +664,7 @@ where
|
||||
///
|
||||
/// This is a fluent setter method which must be chained or used as it consumes self
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn paint(mut self, f: F) -> Canvas<'a, F> {
|
||||
pub fn paint(mut self, f: F) -> Self {
|
||||
self.paint_func = Some(f);
|
||||
self
|
||||
}
|
||||
@@ -701,7 +673,7 @@ where
|
||||
///
|
||||
/// This is a fluent setter method which must be chained or used as it consumes self
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn background_color(mut self, color: Color) -> Canvas<'a, F> {
|
||||
pub const fn background_color(mut self, color: Color) -> Self {
|
||||
self.background_color = color;
|
||||
self
|
||||
}
|
||||
@@ -715,7 +687,7 @@ where
|
||||
/// The [`HalfBlock`] marker is useful when you want to draw shapes with a higher resolution
|
||||
/// than with a grid of characters (e.g. with [`Block`] or [`Dot`]) but lower than with
|
||||
/// [`Braille`]. This grid type supports a foreground and background color for each terminal
|
||||
/// cell. This allows for more flexibility than the BrailleGrid which only supports a single
|
||||
/// cell. This allows for more flexibility than the `BrailleGrid` which only supports a single
|
||||
/// foreground color for each 2x4 dots cell.
|
||||
///
|
||||
/// [`Braille`]: crate::symbols::Marker::Braille
|
||||
@@ -744,7 +716,8 @@ where
|
||||
/// .marker(symbols::Marker::Block)
|
||||
/// .paint(|ctx| {});
|
||||
/// ```
|
||||
pub fn marker(mut self, marker: symbols::Marker) -> Canvas<'a, F> {
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn marker(mut self, marker: symbols::Marker) -> Self {
|
||||
self.marker = marker;
|
||||
self
|
||||
}
|
||||
@@ -838,7 +811,7 @@ mod tests {
|
||||
use indoc::indoc;
|
||||
|
||||
use super::*;
|
||||
use crate::{buffer::Cell, symbols::Marker};
|
||||
use crate::buffer::Cell;
|
||||
|
||||
// helper to test the canvas checks that drawing a vertical and horizontal line
|
||||
// results in the expected output
|
||||
|
||||
@@ -115,6 +115,7 @@ mod tests {
|
||||
use super::Line;
|
||||
use crate::{assert_buffer_eq, prelude::*, widgets::canvas::Canvas};
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[track_caller]
|
||||
fn test(line: Line, expected_lines: Vec<&str>) {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
|
||||
@@ -128,7 +129,7 @@ mod tests {
|
||||
canvas.render(buffer.area, &mut buffer);
|
||||
|
||||
let mut expected = Buffer::with_lines(expected_lines);
|
||||
for cell in expected.content.iter_mut() {
|
||||
for cell in &mut expected.content {
|
||||
if cell.symbol() == "•" {
|
||||
cell.set_style(Style::new().red());
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ pub enum MapResolution {
|
||||
impl MapResolution {
|
||||
fn data(self) -> &'static [(f64, f64)] {
|
||||
match self {
|
||||
MapResolution::Low => &WORLD_LOW_RESOLUTION,
|
||||
MapResolution::High => &WORLD_HIGH_RESOLUTION,
|
||||
Self::Low => &WORLD_LOW_RESOLUTION,
|
||||
Self::High => &WORLD_HIGH_RESOLUTION,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/// [Source data](http://www.gnuplotting.org/plotting-the-world-revisited)
|
||||
|
||||
pub static WORLD_HIGH_RESOLUTION: [(f64, f64); 5125] = [
|
||||
(-163.7128, -78.5956),
|
||||
(-163.1058, -78.2233),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user