Compare commits
24 Commits
v0.27.0-al
...
jm/widget-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f99c224fc1 | ||
|
|
8061813f32 | ||
|
|
74a32afbae | ||
|
|
4ce67fc84e | ||
|
|
df4b706674 | ||
|
|
8b447ec4d6 | ||
|
|
7a48c5b11b | ||
|
|
8cfc316bcc | ||
|
|
2f8a9363fc | ||
|
|
d92997105b | ||
|
|
42cda6d287 | ||
|
|
4f7791079e | ||
|
|
cf67ed9b88 | ||
|
|
8a60a561c9 | ||
|
|
35941809e1 | ||
|
|
73fd367a74 | ||
|
|
1de9a82b7a | ||
|
|
d6587bc6b0 | ||
|
|
f429f688da | ||
|
|
4770e71581 | ||
|
|
eef1afe915 | ||
|
|
257db6257f | ||
|
|
70df102de0 | ||
|
|
bf2036987f |
@@ -10,6 +10,11 @@ GitHub with a [breaking change] label.
|
||||
|
||||
This is a quick summary of the sections below:
|
||||
|
||||
- [Unreleased](#unreleased)
|
||||
- `Rect::inner` takes `Margin` directly instead of reference
|
||||
- `Buffer::filled` takes `Cell` directly instead of reference
|
||||
- `Stylize::bg()` now accepts `Into<Color>`
|
||||
- Removed deprecated `List::start_corner`
|
||||
- [v0.26.0](#v0260)
|
||||
- `Flex::Start` is the new default flex mode for `Layout`
|
||||
- `patch_style` & `reset_style` now consume and return `Self`
|
||||
@@ -47,6 +52,63 @@ This is a quick summary of the sections below:
|
||||
- MSRV is now 1.63.0
|
||||
- `List` no longer ignores empty strings
|
||||
|
||||
## Unreleased
|
||||
|
||||
### `Rect::inner` takes `Margin` directly instead of reference ([#1008])
|
||||
|
||||
[#1008]: https://github.com/ratatui-org/ratatui/pull/1008
|
||||
|
||||
`Margin` needs to be passed without reference now.
|
||||
|
||||
```diff
|
||||
-let area = area.inner(&Margin {
|
||||
+let area = area.inner(Margin {
|
||||
vertical: 0,
|
||||
horizontal: 2,
|
||||
});
|
||||
```
|
||||
|
||||
### `Buffer::filled` takes `Cell` directly instead of reference ([#1148])
|
||||
|
||||
[#1148]: https://github.com/ratatui-org/ratatui/pull/1148
|
||||
|
||||
`Buffer::filled` moves the `Cell` instead of taking a reference.
|
||||
|
||||
```diff
|
||||
-Buffer::filled(area, &Cell::new("X"));
|
||||
+Buffer::filled(area, Cell::new("X"));
|
||||
```
|
||||
|
||||
### `Stylize::bg()` now accepts `Into<Color>` ([#1103])
|
||||
|
||||
[#1103]: https://github.com/ratatui-org/ratatui/pull/1103
|
||||
|
||||
Previously, `Stylize::bg()` accepted `Color` but now accepts `Into<Color>`. This allows more
|
||||
flexible types from calling scopes, though it can break some type inference in the calling scope.
|
||||
|
||||
### Remove deprecated `List::start_corner` and `layout::Corner` ([#757])
|
||||
|
||||
[#757]: https://github.com/ratatui-org/ratatui/pull/757
|
||||
|
||||
`List::start_corner` was deprecated in v0.25. Use `List::direction` and `ListDirection` instead.
|
||||
|
||||
```diff
|
||||
- list.start_corner(Corner::TopLeft);
|
||||
- list.start_corner(Corner::TopRight);
|
||||
// This is not an error, BottomRight rendered top to bottom previously
|
||||
- list.start_corner(Corner::BottomRight);
|
||||
// all becomes
|
||||
+ list.direction(ListDirection::TopToBottom);
|
||||
```
|
||||
|
||||
```diff
|
||||
- list.start_corner(Corner::BottomLeft);
|
||||
// becomes
|
||||
+ list.direction(ListDirection::BottomToTop);
|
||||
```
|
||||
|
||||
`layout::Corner` was removed entirely.
|
||||
|
||||
## [v0.26.0](https://github.com/ratatui-org/ratatui/releases/tag/v0.26.0)
|
||||
|
||||
### `Flex::Start` is the new default flex mode for `Layout` ([#881])
|
||||
@@ -74,7 +136,7 @@ existing layouts with `Flex::Start`. However, to get old behavior, use `Flex::Le
|
||||
|
||||
[#774]: https://github.com/ratatui-org/ratatui/pull/774
|
||||
|
||||
Previously, `Table::new()` accepted `IntoIterator<Item=Row<'a>>`. The argument change to
|
||||
Previously, `Table::new()` accepted `IntoIterator<Item=Row<'a>>`. The argument change to
|
||||
`IntoIterator<Item: Into<Row<'a>>>`, This allows more flexible types from calling scopes, though it
|
||||
can some break type inference in the calling scope for empty containers.
|
||||
|
||||
@@ -91,7 +153,7 @@ This can be resolved either by providing an explicit type (e.g. `Vec::<Row>::new
|
||||
|
||||
[#776]: https://github.com/ratatui-org/ratatui/pull/776
|
||||
|
||||
Previously, `Tabs::new()` accepted `Vec<T>` where `T: Into<Line<'a>>`. This allows more flexible
|
||||
Previously, `Tabs::new()` accepted `Vec<T>` where `T: Into<Line<'a>>`. This allows more flexible
|
||||
types from calling scopes, though it can break some type inference in the calling scope.
|
||||
|
||||
This typically occurs when collecting an iterator prior to calling `Tabs::new`, and can be resolved
|
||||
|
||||
77
Cargo.toml
77
Cargo.toml
@@ -74,7 +74,6 @@ 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"
|
||||
@@ -88,11 +87,15 @@ map_err_ignore = "warn"
|
||||
missing_const_for_fn = "warn"
|
||||
mixed_read_write_in_expression = "warn"
|
||||
mod_module_files = "warn"
|
||||
needless_pass_by_ref_mut = "warn"
|
||||
needless_raw_strings = "warn"
|
||||
or_fun_call = "warn"
|
||||
redundant_type_annotations = "warn"
|
||||
rest_pat_in_fully_bound_structs = "warn"
|
||||
string_lit_chars_any = "warn"
|
||||
string_slice = "warn"
|
||||
string_to_string = "warn"
|
||||
unnecessary_self_imports = "warn"
|
||||
use_self = "warn"
|
||||
|
||||
[features]
|
||||
@@ -103,15 +106,15 @@ use_self = "warn"
|
||||
## which allows you to set the underline color of text.
|
||||
default = ["crossterm", "underline-color"]
|
||||
#! Generally an application will only use one backend, so you should only enable one of the following features:
|
||||
## enables the [`CrosstermBackend`] backend and adds a dependency on the [Crossterm crate].
|
||||
## enables the [`CrosstermBackend`](backend::CrosstermBackend) backend and adds a dependency on [`crossterm`].
|
||||
crossterm = ["dep:crossterm"]
|
||||
## enables the [`TermionBackend`] backend and adds a dependency on the [Termion crate].
|
||||
## enables the [`TermionBackend`](backend::TermionBackend) backend and adds a dependency on [`termion`].
|
||||
termion = ["dep:termion"]
|
||||
## enables the [`TermwizBackend`] backend and adds a dependency on the [Termwiz crate].
|
||||
## enables the [`TermwizBackend`](backend::TermwizBackend) backend and adds a dependency on [`termwiz`].
|
||||
termwiz = ["dep:termwiz"]
|
||||
|
||||
#! The following optional features are available for all backends:
|
||||
## enables serialization and deserialization of style and color types using the [Serde crate].
|
||||
## enables serialization and deserialization of style and color types using the [`serde`] crate.
|
||||
## This is useful if you want to save themes to a file.
|
||||
serde = ["dep:serde", "bitflags/serde", "compact_str/serde"]
|
||||
|
||||
@@ -123,12 +126,14 @@ all-widgets = ["widget-calendar"]
|
||||
|
||||
#! Widgets that add dependencies are gated behind feature flags to prevent unused transitive
|
||||
#! dependencies. The available features are:
|
||||
## enables the [`calendar`] widget module and adds a dependency on the [Time crate].
|
||||
## enables the [`calendar`](widgets::calendar) widget module and adds a dependency on [`time`].
|
||||
widget-calendar = ["dep:time"]
|
||||
|
||||
#! Underline color is only supported by the [`CrosstermBackend`] backend, and is not supported
|
||||
#! on Windows 7.
|
||||
#! The following optional features are only available for some backends:
|
||||
|
||||
## enables the backend code that sets the underline color.
|
||||
## Underline color is only supported by the [`CrosstermBackend`](backend::CrosstermBackend) backend,
|
||||
## and is not supported on Windows 7.
|
||||
underline-color = ["dep:crossterm"]
|
||||
|
||||
#! The following features are unstable and may change in the future:
|
||||
@@ -136,13 +141,13 @@ underline-color = ["dep:crossterm"]
|
||||
## Enable all unstable features.
|
||||
unstable = ["unstable-rendered-line-info", "unstable-widget-ref"]
|
||||
|
||||
## Enables the [`Paragraph::line_count`](crate::widgets::Paragraph::line_count)
|
||||
## [`Paragraph::line_width`](crate::widgets::Paragraph::line_width) methods
|
||||
## Enables the [`Paragraph::line_count`](widgets::Paragraph::line_count)
|
||||
## [`Paragraph::line_width`](widgets::Paragraph::line_width) methods
|
||||
## which are experimental and may change in the future.
|
||||
## See [Issue 293](https://github.com/ratatui-org/ratatui/issues/293) for more details.
|
||||
unstable-rendered-line-info = []
|
||||
|
||||
## Enables the `WidgetRef` and `StatefulWidgetRef` traits which are experimental and may change in
|
||||
## Enables the [`WidgetRef`](widgets::WidgetRef) and [`StatefulWidgetRef`](widgets::StatefulWidgetRef) traits which are experimental and may change in
|
||||
## the future.
|
||||
unstable-widget-ref = []
|
||||
|
||||
@@ -152,6 +157,11 @@ all-features = true
|
||||
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
# Improve benchmark consistency
|
||||
[profile.bench]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[[bench]]
|
||||
name = "barchart"
|
||||
harness = false
|
||||
@@ -191,13 +201,13 @@ required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "canvas"
|
||||
required-features = ["crossterm"]
|
||||
name = "calendar"
|
||||
required-features = ["crossterm", "widget-calendar"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "calendar"
|
||||
required-features = ["crossterm", "widget-calendar"]
|
||||
name = "canvas"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
@@ -216,6 +226,16 @@ name = "colors_rgb"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "constraint-explorer"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "constraints"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = false
|
||||
|
||||
[[example]]
|
||||
name = "custom_widget"
|
||||
required-features = ["crossterm"]
|
||||
@@ -236,6 +256,11 @@ name = "docsrs"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = false
|
||||
|
||||
[[example]]
|
||||
name = "flex"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "gauge"
|
||||
required-features = ["crossterm"]
|
||||
@@ -246,23 +271,18 @@ name = "hello_world"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "inline"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "layout"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "constraints"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = false
|
||||
|
||||
[[example]]
|
||||
name = "flex"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "constraint-explorer"
|
||||
name = "line_gauge"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
@@ -328,11 +348,6 @@ name = "user_input"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "inline"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[test]]
|
||||
name = "state_serde"
|
||||
required-features = ["serde"]
|
||||
|
||||
44
README.md
44
README.md
@@ -43,10 +43,10 @@ Ratatui was forked from the [tui-rs] crate in 2023 in order to continue its deve
|
||||
|
||||
## Installation
|
||||
|
||||
Add `ratatui` and `crossterm` as dependencies to your cargo.toml:
|
||||
Add `ratatui` as a dependency to your cargo.toml:
|
||||
|
||||
```shell
|
||||
cargo add ratatui crossterm
|
||||
cargo add ratatui
|
||||
```
|
||||
|
||||
Ratatui uses [Crossterm] by default as it works on most platforms. See the [Installation]
|
||||
@@ -110,7 +110,8 @@ module] and the [Backends] section of the [Ratatui Website] for more info.
|
||||
|
||||
The drawing logic is delegated to a closure that takes a [`Frame`] instance as argument. The
|
||||
[`Frame`] provides the size of the area to draw to and allows the app to render any [`Widget`]
|
||||
using the provided [`render_widget`] method. See the [Widgets] section of the [Ratatui Website]
|
||||
using the provided [`render_widget`] method. After this closure returns, a diff is performed and
|
||||
only the changes are drawn to the terminal. See the [Widgets] section of the [Ratatui Website]
|
||||
for more info.
|
||||
|
||||
### Handling events
|
||||
@@ -125,12 +126,17 @@ Website] for more info. For example, if you are using [Crossterm], you can use t
|
||||
```rust
|
||||
use std::io::{self, stdout};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
use ratatui::{
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
terminal::{
|
||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
},
|
||||
ExecutableCommand,
|
||||
},
|
||||
prelude::*,
|
||||
widgets::*,
|
||||
};
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
enable_raw_mode()?;
|
||||
@@ -161,8 +167,7 @@ fn handle_events() -> io::Result<bool> {
|
||||
|
||||
fn ui(frame: &mut Frame) {
|
||||
frame.render_widget(
|
||||
Paragraph::new("Hello World!")
|
||||
.block(Block::bordered().title("Greeting")),
|
||||
Paragraph::new("Hello World!").block(Block::bordered().title("Greeting")),
|
||||
frame.size(),
|
||||
);
|
||||
}
|
||||
@@ -206,14 +211,8 @@ fn ui(frame: &mut Frame) {
|
||||
[Constraint::Percentage(50), Constraint::Percentage(50)],
|
||||
)
|
||||
.split(main_layout[1]);
|
||||
frame.render_widget(
|
||||
Block::bordered().title("Left"),
|
||||
inner_layout[0],
|
||||
);
|
||||
frame.render_widget(
|
||||
Block::bordered().title("Right"),
|
||||
inner_layout[1],
|
||||
);
|
||||
frame.render_widget(Block::bordered().title("Left"), inner_layout[0]);
|
||||
frame.render_widget(Block::bordered().title("Right"), inner_layout[1]);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -331,17 +330,14 @@ Running this example produces the following output:
|
||||
[License Badge]: https://img.shields.io/crates/l/ratatui?style=flat-square&color=1370D3
|
||||
[CI Badge]: https://img.shields.io/github/actions/workflow/status/ratatui-org/ratatui/ci.yml?style=flat-square&logo=github
|
||||
[CI Workflow]: https://github.com/ratatui-org/ratatui/actions/workflows/ci.yml
|
||||
[Codecov Badge]:
|
||||
https://img.shields.io/codecov/c/github/ratatui-org/ratatui?logo=codecov&style=flat-square&token=BAQ8SOKEST&color=C43AC3&logoColor=C43AC3
|
||||
[Codecov Badge]: https://img.shields.io/codecov/c/github/ratatui-org/ratatui?logo=codecov&style=flat-square&token=BAQ8SOKEST&color=C43AC3&logoColor=C43AC3
|
||||
[Codecov]: https://app.codecov.io/gh/ratatui-org/ratatui
|
||||
[Deps.rs Badge]: https://deps.rs/repo/github/ratatui-org/ratatui/status.svg?style=flat-square
|
||||
[Deps.rs]: https://deps.rs/repo/github/ratatui-org/ratatui
|
||||
[Discord Badge]:
|
||||
https://img.shields.io/discord/1070692720437383208?label=discord&logo=discord&style=flat-square&color=1370D3&logoColor=1370D3
|
||||
[Discord Badge]: https://img.shields.io/discord/1070692720437383208?label=discord&logo=discord&style=flat-square&color=1370D3&logoColor=1370D3
|
||||
[Discord Server]: https://discord.gg/pMCEU9hNEj
|
||||
[Docs Badge]: https://img.shields.io/docsrs/ratatui?logo=rust&style=flat-square&logoColor=E05D44
|
||||
[Matrix Badge]:
|
||||
https://img.shields.io/matrix/ratatui-general%3Amatrix.org?style=flat-square&logo=matrix&label=Matrix&color=C43AC3
|
||||
[Matrix Badge]: https://img.shields.io/matrix/ratatui-general%3Amatrix.org?style=flat-square&logo=matrix&label=Matrix&color=C43AC3
|
||||
[Matrix]: https://matrix.to/#/#ratatui:matrix.org
|
||||
[Sponsors Badge]: https://img.shields.io/github/sponsors/ratatui-org?logo=github&style=flat-square&color=1370D3
|
||||
|
||||
|
||||
@@ -164,6 +164,18 @@ cargo run --example=gauge --features=crossterm
|
||||
|
||||
![Gauge][gauge.gif]
|
||||
|
||||
## Line Gauge
|
||||
|
||||
Demonstrates the [`Line
|
||||
Gauge`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.LineGauge.html) widget. Source:
|
||||
[line_gauge.rs](./line_gauge.rs).
|
||||
|
||||
```shell
|
||||
cargo run --example=line_gauge --features=crossterm
|
||||
```
|
||||
|
||||
![LineGauge][line_gauge.gif]
|
||||
|
||||
## Inline
|
||||
|
||||
Demonstrates how to use the
|
||||
@@ -346,6 +358,7 @@ examples/vhs/generate.bash
|
||||
[inline.gif]: https://github.com/ratatui-org/ratatui/blob/images/examples/inline.gif?raw=true
|
||||
[layout.gif]: https://github.com/ratatui-org/ratatui/blob/images/examples/layout.gif?raw=true
|
||||
[list.gif]: https://github.com/ratatui-org/ratatui/blob/images/examples/list.gif?raw=true
|
||||
[line_gauge.gif]: https://github.com/ratatui-org/ratatui/blob/images/examples/line_gauge.gif?raw=true
|
||||
[modifiers.gif]: https://github.com/ratatui-org/ratatui/blob/images/examples/modifiers.gif?raw=true
|
||||
[panic.gif]: https://github.com/ratatui-org/ratatui/blob/images/examples/panic.gif?raw=true
|
||||
[paragraph.gif]: https://github.com/ratatui-org/ratatui/blob/images/examples/paragraph.gif?raw=true
|
||||
|
||||
@@ -19,13 +19,17 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
terminal::{Frame, Terminal},
|
||||
text::{Line, Span},
|
||||
widgets::{Bar, BarChart, BarGroup, Block, Paragraph},
|
||||
};
|
||||
|
||||
|
||||
@@ -20,14 +20,18 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
backend::CrosstermBackend,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
layout::{Alignment, Constraint, Layout, Rect},
|
||||
style::{Style, Stylize},
|
||||
terminal::Frame,
|
||||
text::Line,
|
||||
widgets::{
|
||||
block::{Position, Title},
|
||||
Block, BorderType, Borders, Padding, Paragraph, Wrap,
|
||||
|
||||
@@ -13,16 +13,20 @@
|
||||
//! [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::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
widgets::calendar::{CalendarEventStore, DateStyler, Monthly},
|
||||
Frame, Terminal,
|
||||
};
|
||||
use ratatui::{prelude::*, widgets::calendar::*};
|
||||
use time::{Date, Month, OffsetDateTime};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
@@ -52,8 +56,8 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(f: &mut Frame) {
|
||||
let app_area = f.size();
|
||||
fn draw(frame: &mut Frame) {
|
||||
let app_area = frame.size();
|
||||
|
||||
let calarea = Rect {
|
||||
x: app_area.x + 1,
|
||||
@@ -80,7 +84,7 @@ fn draw(f: &mut Frame) {
|
||||
});
|
||||
for col in cols {
|
||||
let cal = cals::get_cal(start.month(), start.year(), &list);
|
||||
f.render_widget(cal, col);
|
||||
frame.render_widget(cal, col);
|
||||
start = start.replace_month(start.month().next()).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -166,6 +170,7 @@ fn make_dates(current_year: i32) -> CalendarEventStore {
|
||||
}
|
||||
|
||||
mod cals {
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use super::*;
|
||||
|
||||
pub fn get_cal<'a, DS: DateStyler>(m: Month, y: i32, es: DS) -> Monthly<'a, DS> {
|
||||
|
||||
@@ -13,21 +13,26 @@
|
||||
//! [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},
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{canvas::*, *},
|
||||
backend::CrosstermBackend,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{Color, Stylize},
|
||||
symbols::Marker,
|
||||
terminal::{Frame, Terminal},
|
||||
widgets::{
|
||||
canvas::{Canvas, Circle, Map, MapResolution, Rectangle},
|
||||
Block, Widget,
|
||||
},
|
||||
};
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
|
||||
@@ -19,13 +19,18 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
layout::{Alignment, Constraint, Layout, Rect},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
symbols::{self, Marker},
|
||||
terminal::{Frame, Terminal},
|
||||
text::Span,
|
||||
widgets::{block::Title, Axis, Block, Chart, Dataset, GraphType, LegendPosition},
|
||||
};
|
||||
|
||||
|
||||
@@ -23,14 +23,18 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
layout::{Alignment, Constraint, Layout, Rect},
|
||||
style::{Color, Style, Stylize},
|
||||
terminal::{Frame, Terminal},
|
||||
text::Line,
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
};
|
||||
|
||||
|
||||
@@ -33,13 +33,21 @@ use std::{
|
||||
};
|
||||
|
||||
use color_eyre::{config::HookBuilder, eyre, Result};
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
};
|
||||
use palette::{convert::FromColorUnclamped, Okhsv, Srgb};
|
||||
use ratatui::prelude::*;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::Color,
|
||||
terminal::Terminal,
|
||||
text::Text,
|
||||
widgets::Widget,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct App {
|
||||
@@ -141,8 +149,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::*;
|
||||
use Constraint::{Length, Min};
|
||||
let [top, colors] = Layout::vertical([Length(1), Min(0)]).areas(area);
|
||||
let [title, fps] = Layout::horizontal([Min(0), Length(8)]).areas(top);
|
||||
Text::from("colors_rgb example. Press q to quit")
|
||||
|
||||
@@ -13,23 +13,30 @@
|
||||
//! [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};
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use ratatui::{
|
||||
layout::{Constraint::*, Flex},
|
||||
prelude::*,
|
||||
style::palette::tailwind::*,
|
||||
symbols::line,
|
||||
widgets::{Block, Paragraph, Wrap},
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
layout::{
|
||||
Constraint::{self, Fill, Length, Max, Min, Percentage, Ratio},
|
||||
Flex, Layout, Rect,
|
||||
},
|
||||
style::{
|
||||
palette::tailwind::{BLUE, SKY, SLATE, STONE},
|
||||
Color, Style, Stylize,
|
||||
},
|
||||
symbols::{self, line},
|
||||
terminal::Terminal,
|
||||
text::{Line, Span, Text},
|
||||
widgets::{Block, Paragraph, Widget, Wrap},
|
||||
};
|
||||
use strum::{Display, EnumIter, FromRepr};
|
||||
|
||||
@@ -123,24 +130,23 @@ impl App {
|
||||
}
|
||||
|
||||
fn handle_events(&mut self) -> Result<()> {
|
||||
use KeyCode::*;
|
||||
match event::read()? {
|
||||
Event::Key(key) if key.kind == KeyEventKind::Press => match key.code {
|
||||
Char('q') | Esc => self.exit(),
|
||||
Char('1') => self.swap_constraint(ConstraintName::Min),
|
||||
Char('2') => self.swap_constraint(ConstraintName::Max),
|
||||
Char('3') => self.swap_constraint(ConstraintName::Length),
|
||||
Char('4') => self.swap_constraint(ConstraintName::Percentage),
|
||||
Char('5') => self.swap_constraint(ConstraintName::Ratio),
|
||||
Char('6') => self.swap_constraint(ConstraintName::Fill),
|
||||
Char('+') => self.increment_spacing(),
|
||||
Char('-') => self.decrement_spacing(),
|
||||
Char('x') => self.delete_block(),
|
||||
Char('a') => self.insert_block(),
|
||||
Char('k') | Up => self.increment_value(),
|
||||
Char('j') | Down => self.decrement_value(),
|
||||
Char('h') | Left => self.prev_block(),
|
||||
Char('l') | Right => self.next_block(),
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.exit(),
|
||||
KeyCode::Char('1') => self.swap_constraint(ConstraintName::Min),
|
||||
KeyCode::Char('2') => self.swap_constraint(ConstraintName::Max),
|
||||
KeyCode::Char('3') => self.swap_constraint(ConstraintName::Length),
|
||||
KeyCode::Char('4') => self.swap_constraint(ConstraintName::Percentage),
|
||||
KeyCode::Char('5') => self.swap_constraint(ConstraintName::Ratio),
|
||||
KeyCode::Char('6') => self.swap_constraint(ConstraintName::Fill),
|
||||
KeyCode::Char('+') => self.increment_spacing(),
|
||||
KeyCode::Char('-') => self.decrement_spacing(),
|
||||
KeyCode::Char('x') => self.delete_block(),
|
||||
KeyCode::Char('a') => self.insert_block(),
|
||||
KeyCode::Char('k') | KeyCode::Up => self.increment_value(),
|
||||
KeyCode::Char('j') | KeyCode::Down => self.decrement_value(),
|
||||
KeyCode::Char('h') | KeyCode::Left => self.prev_block(),
|
||||
KeyCode::Char('l') | KeyCode::Right => self.next_block(),
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
|
||||
@@ -13,17 +13,30 @@
|
||||
//! [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};
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
layout::{
|
||||
Constraint::{self, Fill, Length, Max, Min, Percentage, Ratio},
|
||||
Layout, Rect,
|
||||
},
|
||||
style::{palette::tailwind, Color, Modifier, Style, Stylize},
|
||||
symbols,
|
||||
terminal::Terminal,
|
||||
text::Line,
|
||||
widgets::{
|
||||
Block, Padding, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget,
|
||||
Tabs, Widget,
|
||||
},
|
||||
};
|
||||
use ratatui::{layout::Constraint::*, prelude::*, style::palette::tailwind, widgets::*};
|
||||
use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
|
||||
|
||||
const SPACER_HEIGHT: u16 = 0;
|
||||
@@ -108,18 +121,17 @@ impl App {
|
||||
|
||||
fn handle_events(&mut self) -> Result<()> {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
use KeyCode::*;
|
||||
if key.kind != KeyEventKind::Press {
|
||||
return Ok(());
|
||||
}
|
||||
match key.code {
|
||||
Char('q') | Esc => self.quit(),
|
||||
Char('l') | Right => self.next(),
|
||||
Char('h') | Left => self.previous(),
|
||||
Char('j') | Down => self.down(),
|
||||
Char('k') | Up => self.up(),
|
||||
Char('g') | Home => self.top(),
|
||||
Char('G') | End => self.bottom(),
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.quit(),
|
||||
KeyCode::Char('l') | KeyCode::Right => self.next(),
|
||||
KeyCode::Char('h') | KeyCode::Left => self.previous(),
|
||||
KeyCode::Char('j') | KeyCode::Down => self.down(),
|
||||
KeyCode::Char('k') | KeyCode::Up => self.up(),
|
||||
KeyCode::Char('g') | KeyCode::Home => self.top(),
|
||||
KeyCode::Char('G') | KeyCode::End => self.bottom(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,15 +15,23 @@
|
||||
|
||||
use std::{error::Error, io, ops::ControlFlow, time::Duration};
|
||||
|
||||
use crossterm::{
|
||||
event::{
|
||||
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, MouseButton, MouseEvent,
|
||||
MouseEventKind,
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{
|
||||
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, MouseButton, MouseEvent,
|
||||
MouseEventKind,
|
||||
},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{Color, Style},
|
||||
terminal::{Frame, Terminal},
|
||||
text::Line,
|
||||
widgets::{Paragraph, Widget},
|
||||
};
|
||||
use ratatui::{prelude::*, widgets::Paragraph};
|
||||
|
||||
/// A custom widget that renders a button with a label, theme and state.
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -4,12 +4,15 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
terminal::Terminal,
|
||||
};
|
||||
use ratatui::prelude::*;
|
||||
|
||||
use crate::{app::App, ui};
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use std::{error::Error, io, sync::mpsc, thread, time::Duration};
|
||||
|
||||
use ratatui::prelude::*;
|
||||
use termion::{
|
||||
event::Key,
|
||||
input::{MouseTerminal, TermRead},
|
||||
raw::IntoRawMode,
|
||||
screen::IntoAlternateScreen,
|
||||
use ratatui::{
|
||||
backend::{Backend, TermionBackend},
|
||||
terminal::Terminal,
|
||||
termion::{
|
||||
event::Key,
|
||||
input::{MouseTerminal, TermRead},
|
||||
raw::IntoRawMode,
|
||||
screen::IntoAlternateScreen,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{app::App, ui};
|
||||
|
||||
@@ -3,10 +3,13 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use ratatui::prelude::*;
|
||||
use termwiz::{
|
||||
input::{InputEvent, KeyCode},
|
||||
terminal::Terminal as TermwizTerminal,
|
||||
use ratatui::{
|
||||
backend::TermwizBackend,
|
||||
terminal::Terminal,
|
||||
termwiz::{
|
||||
input::{InputEvent, KeyCode},
|
||||
terminal::Terminal as TermwizTerminal,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{app::App, ui};
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{canvas::*, *},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
symbols,
|
||||
terminal::Frame,
|
||||
text::{self, Span},
|
||||
widgets::{
|
||||
canvas::{self, Canvas, Circle, Map, MapResolution, Rectangle},
|
||||
Axis, BarChart, Block, Cell, Chart, Dataset, Gauge, LineGauge, List, ListItem, Paragraph,
|
||||
Row, Sparkline, Table, Tabs, Wrap,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::app::App;
|
||||
@@ -76,7 +83,7 @@ fn draw_gauges(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
|
||||
let line_gauge = LineGauge::default()
|
||||
.block(Block::new().title("LineGauge:"))
|
||||
.gauge_style(Style::default().fg(Color::Magenta))
|
||||
.filled_style(Style::default().fg(Color::Magenta))
|
||||
.line_set(if app.enhanced_graphics {
|
||||
symbols::line::THICK
|
||||
} else {
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::{eyre::Context, Result};
|
||||
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
|
||||
use itertools::Itertools;
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
backend::Backend,
|
||||
buffer::Buffer,
|
||||
crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::Color,
|
||||
terminal::Terminal,
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Tabs, Widget},
|
||||
};
|
||||
use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
|
||||
|
||||
use crate::{destroy, tabs::*, term, THEME};
|
||||
use crate::{
|
||||
destroy,
|
||||
tabs::{AboutTab, EmailTab, RecipeTab, TracerouteTab, WeatherTab},
|
||||
term, THEME,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct App {
|
||||
@@ -82,14 +94,13 @@ impl App {
|
||||
}
|
||||
|
||||
fn handle_key_press(&mut self, key: KeyEvent) {
|
||||
use KeyCode::*;
|
||||
match key.code {
|
||||
Char('q') | Esc => self.mode = Mode::Quit,
|
||||
Char('h') | Left => self.prev_tab(),
|
||||
Char('l') | Right => self.next_tab(),
|
||||
Char('k') | Up => self.prev(),
|
||||
Char('j') | Down => self.next(),
|
||||
Char('d') | Delete => self.destroy(),
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.mode = Mode::Quit,
|
||||
KeyCode::Char('h') | KeyCode::Left => self.prev_tab(),
|
||||
KeyCode::Char('l') | KeyCode::Right => self.next_tab(),
|
||||
KeyCode::Char('k') | KeyCode::Up => self.prev(),
|
||||
KeyCode::Char('j') | KeyCode::Down => self.next(),
|
||||
KeyCode::Char('d') | KeyCode::Delete => self.destroy(),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,7 +20,16 @@
|
||||
//!
|
||||
//! ```rust
|
||||
//! use anyhow::Result;
|
||||
//! use ratatui::prelude::*;
|
||||
//! use ratatui::{
|
||||
//! backend::{self, Backend, CrosstermBackend},
|
||||
//! buffer::{self, Buffer},
|
||||
//! layout::{self, Alignment, Constraint, Direction, Layout, Margin, Rect},
|
||||
//! style::{self, Color, Modifier, Style, Styled, Stylize},
|
||||
//! symbols::{self, Marker},
|
||||
//! terminal::{CompletedFrame, Frame, Terminal, TerminalOptions, Viewport},
|
||||
//! text::{self, Line, Masked, Span, Text},
|
||||
//! widgets::{block::BlockExt, StatefulWidget, Widget},
|
||||
//! };
|
||||
//! use tui_big_text::{BigTextBuilder, PixelSize};
|
||||
//!
|
||||
//! fn render(frame: &mut Frame) -> Result<()> {
|
||||
@@ -50,7 +59,13 @@ use std::cmp::min;
|
||||
|
||||
use derive_builder::Builder;
|
||||
use font8x8::UnicodeFonts;
|
||||
use ratatui::{prelude::*, text::StyledGrapheme};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::Style,
|
||||
text::{Line, StyledGrapheme},
|
||||
widgets::Widget,
|
||||
};
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
|
||||
@@ -79,7 +94,16 @@ pub enum PixelSize {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::prelude::*;
|
||||
/// use ratatui::{
|
||||
/// backend::{self, Backend, CrosstermBackend},
|
||||
/// buffer::{self, Buffer},
|
||||
/// layout::{self, Alignment, Constraint, Direction, Layout, Margin, Rect},
|
||||
/// style::{self, Color, Modifier, Style, Styled, Stylize},
|
||||
/// symbols::{self, Marker},
|
||||
/// terminal::{CompletedFrame, Frame, Terminal, TerminalOptions, Viewport},
|
||||
/// text::{self, Line, Masked, Span, Text},
|
||||
/// widgets::{block::BlockExt, StatefulWidget, Widget},
|
||||
/// };
|
||||
/// use tui_big_text::{BigTextBuilder, PixelSize};
|
||||
///
|
||||
/// BigText::builder()
|
||||
@@ -276,6 +300,8 @@ fn render_glyph(glyph: [u8; 8], area: Rect, buf: &mut Buffer, pixel_size: PixelS
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ratatui::style::Stylize;
|
||||
|
||||
use super::*;
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use palette::{IntoColor, Okhsv, Srgb};
|
||||
use ratatui::prelude::*;
|
||||
use ratatui::{buffer::Buffer, layout::Rect, style::Color, widgets::Widget};
|
||||
|
||||
/// A widget that renders a color swatch of RGB colors.
|
||||
///
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
use rand::Rng;
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
use ratatui::{buffer::Cell, layout::Flex, prelude::*};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Flex, Layout, Rect},
|
||||
style::{Color, Style},
|
||||
terminal::Frame,
|
||||
widgets::Widget,
|
||||
};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::big_text::{BigTextBuilder, PixelSize};
|
||||
@@ -59,7 +65,7 @@ fn drip(frame_count: usize, area: Rect, buf: &mut Buffer) {
|
||||
if rng.gen_ratio(1, 10) {
|
||||
*dest = src;
|
||||
} else {
|
||||
*dest = Cell::default();
|
||||
dest.reset();
|
||||
}
|
||||
} else {
|
||||
// move the pixel down one row
|
||||
|
||||
@@ -14,11 +14,9 @@
|
||||
//! [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
|
||||
clippy::must_use_candidate
|
||||
)]
|
||||
|
||||
mod app;
|
||||
@@ -30,16 +28,17 @@ mod tabs;
|
||||
mod term;
|
||||
mod theme;
|
||||
|
||||
pub use app::*;
|
||||
use color_eyre::Result;
|
||||
pub use colors::*;
|
||||
pub use term::*;
|
||||
pub use theme::*;
|
||||
|
||||
pub use self::{
|
||||
colors::{color_from_oklab, RgbSwatch},
|
||||
theme::THEME,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
errors::init_hooks()?;
|
||||
let terminal = &mut term::init()?;
|
||||
App::default().run(terminal)?;
|
||||
app::run(terminal)?;
|
||||
term::restore()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
use itertools::Itertools;
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Constraint, Layout, Margin, Rect},
|
||||
widgets::{Block, Borders, Clear, Padding, Paragraph, Widget, Wrap},
|
||||
};
|
||||
|
||||
use crate::{RgbSwatch, THEME};
|
||||
|
||||
@@ -64,20 +68,16 @@ impl Widget for AboutTab {
|
||||
}
|
||||
|
||||
fn render_crate_description(area: Rect, buf: &mut Buffer) {
|
||||
let area = area.inner(
|
||||
&(Margin {
|
||||
vertical: 4,
|
||||
horizontal: 2,
|
||||
}),
|
||||
);
|
||||
let area = area.inner(Margin {
|
||||
vertical: 4,
|
||||
horizontal: 2,
|
||||
});
|
||||
Clear.render(area, buf); // clear out the color swatches
|
||||
Block::new().style(THEME.content).render(area, buf);
|
||||
let area = area.inner(
|
||||
&(Margin {
|
||||
vertical: 1,
|
||||
horizontal: 2,
|
||||
}),
|
||||
);
|
||||
let area = area.inner(Margin {
|
||||
vertical: 1,
|
||||
horizontal: 2,
|
||||
});
|
||||
let text = "- cooking up terminal user interfaces -
|
||||
|
||||
Ratatui is a Rust crate that provides widgets (e.g. Paragraph, Table) and draws them to the \
|
||||
@@ -108,7 +108,7 @@ pub fn render_logo(selected_row: usize, area: Rect, buf: &mut Buffer) {
|
||||
} else {
|
||||
THEME.logo.rat_eye_alt
|
||||
};
|
||||
let area = area.inner(&Margin {
|
||||
let area = area.inner(Margin {
|
||||
vertical: 0,
|
||||
horizontal: 2,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
use itertools::Itertools;
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Layout, Margin, Rect},
|
||||
style::{Styled, Stylize},
|
||||
text::Line,
|
||||
widgets::{
|
||||
Block, BorderType, Borders, Clear, List, ListItem, ListState, Padding, Paragraph,
|
||||
Scrollbar, ScrollbarState, StatefulWidget, Tabs, Widget,
|
||||
},
|
||||
};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{RgbSwatch, THEME};
|
||||
@@ -59,7 +68,7 @@ impl EmailTab {
|
||||
impl Widget for EmailTab {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
RgbSwatch.render(area, buf);
|
||||
let area = area.inner(&Margin {
|
||||
let area = area.inner(Margin {
|
||||
vertical: 1,
|
||||
horizontal: 2,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
use itertools::Itertools;
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Constraint, Layout, Margin, Rect},
|
||||
style::{Style, Stylize},
|
||||
text::Line,
|
||||
widgets::{
|
||||
Block, Clear, Padding, Paragraph, Row, Scrollbar, ScrollbarOrientation, ScrollbarState,
|
||||
StatefulWidget, Table, TableState, Widget, Wrap,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{RgbSwatch, THEME};
|
||||
|
||||
@@ -105,7 +114,7 @@ impl RecipeTab {
|
||||
impl Widget for RecipeTab {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
RgbSwatch.render(area, buf);
|
||||
let area = area.inner(&Margin {
|
||||
let area = area.inner(Margin {
|
||||
vertical: 1,
|
||||
horizontal: 2,
|
||||
});
|
||||
@@ -124,7 +133,7 @@ impl Widget for RecipeTab {
|
||||
};
|
||||
render_scrollbar(self.row_index, scrollbar_area, buf);
|
||||
|
||||
let area = area.inner(&Margin {
|
||||
let area = area.inner(Margin {
|
||||
horizontal: 2,
|
||||
vertical: 1,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
use itertools::Itertools;
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{canvas::*, *},
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Constraint, Layout, Margin, Rect},
|
||||
style::{Styled, Stylize},
|
||||
symbols::Marker,
|
||||
widgets::{
|
||||
canvas::{self, Canvas, Map, MapResolution, Points},
|
||||
Block, BorderType, Clear, Padding, Row, Scrollbar, ScrollbarOrientation, ScrollbarState,
|
||||
Sparkline, StatefulWidget, Table, TableState, Widget,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{RgbSwatch, THEME};
|
||||
@@ -26,7 +33,7 @@ impl TracerouteTab {
|
||||
impl Widget for TracerouteTab {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
RgbSwatch.render(area, buf);
|
||||
let area = area.inner(&Margin {
|
||||
let area = area.inner(Margin {
|
||||
vertical: 1,
|
||||
horizontal: 2,
|
||||
});
|
||||
@@ -104,7 +111,7 @@ fn render_map(selected_row: usize, area: Rect, buf: &mut Buffer) {
|
||||
let theme = THEME.traceroute.map;
|
||||
let path: Option<(&Hop, &Hop)> = HOPS.iter().tuple_windows().nth(selected_row);
|
||||
let map = Map {
|
||||
resolution: canvas::MapResolution::High,
|
||||
resolution: MapResolution::High,
|
||||
color: theme.color,
|
||||
};
|
||||
Canvas::default()
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
use itertools::Itertools;
|
||||
use palette::Okhsv;
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{calendar::CalendarEventStore, *},
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Direction, Layout, Margin, Rect},
|
||||
style::{Color, Style, Stylize},
|
||||
symbols,
|
||||
widgets::{
|
||||
calendar::{CalendarEventStore, Monthly},
|
||||
Bar, BarChart, BarGroup, Block, Clear, LineGauge, Padding, Widget,
|
||||
},
|
||||
};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
@@ -28,14 +34,14 @@ impl WeatherTab {
|
||||
impl Widget for WeatherTab {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
RgbSwatch.render(area, buf);
|
||||
let area = area.inner(&Margin {
|
||||
let area = area.inner(Margin {
|
||||
vertical: 1,
|
||||
horizontal: 2,
|
||||
});
|
||||
Clear.render(area, buf);
|
||||
Block::new().style(THEME.content).render(area, buf);
|
||||
|
||||
let area = area.inner(&Margin {
|
||||
let area = area.inner(Margin {
|
||||
horizontal: 2,
|
||||
vertical: 1,
|
||||
});
|
||||
@@ -59,7 +65,7 @@ impl Widget for WeatherTab {
|
||||
|
||||
fn render_calendar(area: Rect, buf: &mut Buffer) {
|
||||
let date = OffsetDateTime::now_utc().date();
|
||||
calendar::Monthly::new(date, CalendarEventStore::today(Style::new().red().bold()))
|
||||
Monthly::new(date, CalendarEventStore::today(Style::new().red().bold()))
|
||||
.block(Block::new().padding(Padding::new(0, 0, 2, 0)))
|
||||
.show_month_header(Style::new().bold())
|
||||
.show_weekdays_header(Style::new().italic())
|
||||
@@ -140,8 +146,8 @@ 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);
|
||||
let value = Okhsv::max_value();
|
||||
let fg = color_from_oklab(hue, Okhsv::max_saturation(), value);
|
||||
let bg = color_from_oklab(hue, Okhsv::max_saturation(), value * 0.5);
|
||||
let filled_color = color_from_oklab(hue, Okhsv::max_saturation(), value);
|
||||
let unfilled_color = color_from_oklab(hue, Okhsv::max_saturation(), value * 0.5);
|
||||
let label = if percent < 100.0 {
|
||||
format!("Downloading: {percent}%")
|
||||
} else {
|
||||
@@ -151,7 +157,8 @@ fn render_line_gauge(percent: f64, area: Rect, buf: &mut Buffer) {
|
||||
.ratio(percent / 100.0)
|
||||
.label(label)
|
||||
.style(Style::new().light_blue())
|
||||
.gauge_style(Style::new().fg(fg).bg(bg))
|
||||
.filled_style(Style::new().fg(filled_color))
|
||||
.unfilled_style(Style::new().fg(unfilled_color))
|
||||
.line_set(symbols::line::THICK)
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
@@ -4,12 +4,16 @@ use std::{
|
||||
};
|
||||
|
||||
use color_eyre::{eyre::WrapErr, Result};
|
||||
use crossterm::{
|
||||
event::{self, Event},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, Event},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
layout::Rect,
|
||||
terminal::{Terminal, TerminalOptions, Viewport},
|
||||
};
|
||||
use ratatui::prelude::*;
|
||||
|
||||
pub fn init() -> Result<Terminal<impl Backend>> {
|
||||
// this size is to match the size of the terminal when running the demo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ratatui::prelude::*;
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
|
||||
pub struct Theme {
|
||||
pub root: Style,
|
||||
|
||||
@@ -15,12 +15,17 @@
|
||||
|
||||
use std::io::{self, stdout};
|
||||
|
||||
use crossterm::{
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
backend::CrosstermBackend,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
layout::{Constraint, Layout},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
terminal::{Frame, Terminal},
|
||||
text::{Line, Span, Text},
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
};
|
||||
|
||||
@@ -56,7 +61,6 @@ fn hello_world(frame: &mut Frame) {
|
||||
);
|
||||
}
|
||||
|
||||
use crossterm::event::{self, Event, KeyCode};
|
||||
fn handle_events() -> io::Result<bool> {
|
||||
if event::poll(std::time::Duration::from_millis(50))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
|
||||
@@ -13,22 +13,30 @@
|
||||
//! [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};
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
};
|
||||
use ratatui::{
|
||||
layout::{Constraint::*, Flex},
|
||||
prelude::*,
|
||||
style::palette::tailwind,
|
||||
symbols::line,
|
||||
widgets::{block::Title, *},
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
layout::{
|
||||
Alignment,
|
||||
Constraint::{self, Fill, Length, Max, Min, Percentage, Ratio},
|
||||
Flex, Layout, Rect,
|
||||
},
|
||||
style::{palette::tailwind, Color, Modifier, Style, Stylize},
|
||||
symbols::{self, line},
|
||||
terminal::Terminal,
|
||||
text::{Line, Text},
|
||||
widgets::{
|
||||
block::Title, Block, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState,
|
||||
StatefulWidget, Tabs, Widget,
|
||||
},
|
||||
};
|
||||
use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
|
||||
|
||||
@@ -177,18 +185,17 @@ impl App {
|
||||
}
|
||||
|
||||
fn handle_events(&mut self) -> Result<()> {
|
||||
use KeyCode::*;
|
||||
match event::read()? {
|
||||
Event::Key(key) if key.kind == KeyEventKind::Press => match key.code {
|
||||
Char('q') | Esc => self.quit(),
|
||||
Char('l') | Right => self.next(),
|
||||
Char('h') | Left => self.previous(),
|
||||
Char('j') | Down => self.down(),
|
||||
Char('k') | Up => self.up(),
|
||||
Char('g') | Home => self.top(),
|
||||
Char('G') | End => self.bottom(),
|
||||
Char('+') => self.increment_spacing(),
|
||||
Char('-') => self.decrement_spacing(),
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.quit(),
|
||||
KeyCode::Char('l') | KeyCode::Right => self.next(),
|
||||
KeyCode::Char('h') | KeyCode::Left => self.previous(),
|
||||
KeyCode::Char('j') | KeyCode::Down => self.down(),
|
||||
KeyCode::Char('k') | KeyCode::Up => self.up(),
|
||||
KeyCode::Char('g') | KeyCode::Home => self.top(),
|
||||
KeyCode::Char('G') | KeyCode::End => self.bottom(),
|
||||
KeyCode::Char('+') => self.increment_spacing(),
|
||||
KeyCode::Char('-') => self.decrement_spacing(),
|
||||
_ => (),
|
||||
},
|
||||
_ => {}
|
||||
@@ -364,7 +371,7 @@ impl SelectedTab {
|
||||
|
||||
/// Convert a `SelectedTab` into a `Line` to display it by the `Tabs` widget.
|
||||
fn to_tab_title(value: Self) -> Line<'static> {
|
||||
use tailwind::*;
|
||||
use tailwind::{INDIGO, ORANGE, SKY};
|
||||
let text = value.to_string();
|
||||
let color = match value {
|
||||
Self::Legacy => ORANGE.c400,
|
||||
@@ -509,7 +516,7 @@ impl Example {
|
||||
}
|
||||
|
||||
const fn color_for_constraint(constraint: Constraint) -> Color {
|
||||
use tailwind::*;
|
||||
use tailwind::{BLUE, SLATE};
|
||||
match constraint {
|
||||
Constraint::Min(_) => BLUE.c900,
|
||||
Constraint::Max(_) => BLUE.c800,
|
||||
|
||||
@@ -13,20 +13,22 @@
|
||||
//! [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};
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
style::palette::tailwind,
|
||||
widgets::{block::Title, Block, Borders, Gauge, Padding, Paragraph},
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
layout::{Alignment, Constraint, Layout, Rect},
|
||||
style::{palette::tailwind, Color, Style, Stylize},
|
||||
terminal::Terminal,
|
||||
text::Span,
|
||||
widgets::{block::Title, Block, Borders, Gauge, Padding, Paragraph, Widget},
|
||||
};
|
||||
|
||||
const GAUGE1_COLOR: Color = tailwind::RED.c800;
|
||||
@@ -99,10 +101,9 @@ impl App {
|
||||
if event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
use KeyCode::*;
|
||||
match key.code {
|
||||
Char(' ') | Enter => self.start(),
|
||||
Char('q') | Esc => self.quit(),
|
||||
KeyCode::Char(' ') | KeyCode::Enter => self.start(),
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.quit(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -123,7 +124,7 @@ impl App {
|
||||
impl Widget for &App {
|
||||
#[allow(clippy::similar_names)]
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
use Constraint::*;
|
||||
use Constraint::{Length, Min, Ratio};
|
||||
let layout = Layout::vertical([Length(2), Min(0), Length(1)]);
|
||||
let [header_area, gauge_area, footer_area] = layout.areas(area);
|
||||
|
||||
|
||||
@@ -19,12 +19,16 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
terminal::{Frame, Terminal},
|
||||
widgets::Paragraph,
|
||||
};
|
||||
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,8 +13,6 @@
|
||||
//! [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,
|
||||
@@ -25,7 +23,16 @@ use std::{
|
||||
};
|
||||
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
layout::{Alignment, Constraint, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
symbols,
|
||||
terminal::{Frame, Terminal, Viewport},
|
||||
text::{Line, Span},
|
||||
widgets::{block, Block, Gauge, LineGauge, List, ListItem, Paragraph, Widget},
|
||||
TerminalOptions,
|
||||
};
|
||||
|
||||
const NUM_DOWNLOADS: usize = 10;
|
||||
|
||||
@@ -248,7 +255,7 @@ fn ui(f: &mut Frame, downloads: &Downloads) {
|
||||
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))
|
||||
.filled_style(Style::default().fg(Color::Blue))
|
||||
.label(format!("{done}/{NUM_DOWNLOADS}"))
|
||||
.ratio(done as f64 / NUM_DOWNLOADS as f64);
|
||||
f.render_widget(progress, progress_area);
|
||||
|
||||
@@ -13,19 +13,24 @@
|
||||
//! [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::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use ratatui::{
|
||||
layout::Constraint::*,
|
||||
prelude::*,
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
layout::{
|
||||
Constraint,
|
||||
Constraint::{Length, Max, Min, Percentage, Ratio},
|
||||
Layout, Rect,
|
||||
},
|
||||
style::{Color, Style, Stylize},
|
||||
terminal::{Frame, Terminal},
|
||||
text::Line,
|
||||
widgets::{Block, Paragraph},
|
||||
};
|
||||
|
||||
|
||||
218
examples/line_gauge.rs
Normal file
218
examples/line_gauge.rs
Normal file
@@ -0,0 +1,218 @@
|
||||
//! # [Ratatui] Line Gauge example
|
||||
//!
|
||||
//! The latest version of this example is available in the [examples] folder in the repository.
|
||||
//!
|
||||
//! Please note that the examples are designed to be run against the `main` branch of the Github
|
||||
//! repository. This means that you may not be able to compile with the latest release version on
|
||||
//! crates.io, or the one that you have installed locally.
|
||||
//!
|
||||
//! See the [examples readme] for more information on finding examples that match the version of the
|
||||
//! library you are using.
|
||||
//!
|
||||
//! [Ratatui]: https://github.com/ratatui-org/ratatui
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
use std::{io::stdout, time::Duration};
|
||||
|
||||
use color_eyre::{config::HookBuilder, Result};
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
layout::{Alignment, Constraint, Layout, Rect},
|
||||
style::{palette::tailwind, Color, Style, Stylize},
|
||||
terminal::Terminal,
|
||||
widgets::{block::Title, Block, Borders, LineGauge, Padding, Paragraph, Widget},
|
||||
};
|
||||
|
||||
const CUSTOM_LABEL_COLOR: Color = tailwind::SLATE.c200;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
struct App {
|
||||
state: AppState,
|
||||
progress_columns: u16,
|
||||
progress: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
enum AppState {
|
||||
#[default]
|
||||
Running,
|
||||
Started,
|
||||
Quitting,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
init_error_hooks()?;
|
||||
let terminal = init_terminal()?;
|
||||
App::default().run(terminal)?;
|
||||
restore_terminal()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn run(&mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
while self.state != AppState::Quitting {
|
||||
self.draw(&mut terminal)?;
|
||||
self.handle_events()?;
|
||||
self.update(terminal.size()?.width);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, terminal: &mut Terminal<impl Backend>) -> Result<()> {
|
||||
terminal.draw(|f| f.render_widget(self, f.size()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update(&mut self, terminal_width: u16) {
|
||||
if self.state != AppState::Started {
|
||||
return;
|
||||
}
|
||||
|
||||
self.progress_columns = (self.progress_columns + 1).clamp(0, terminal_width);
|
||||
self.progress = f64::from(self.progress_columns) / f64::from(terminal_width);
|
||||
}
|
||||
|
||||
fn handle_events(&mut self) -> Result<()> {
|
||||
let timeout = Duration::from_secs_f32(1.0 / 20.0);
|
||||
if event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
match key.code {
|
||||
KeyCode::Char(' ') | KeyCode::Enter => self.start(),
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.quit(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start(&mut self) {
|
||||
self.state = AppState::Started;
|
||||
}
|
||||
|
||||
fn quit(&mut self) {
|
||||
self.state = AppState::Quitting;
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &App {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
use Constraint::{Length, Min, Ratio};
|
||||
let layout = Layout::vertical([Length(2), Min(0), Length(1)]);
|
||||
let [header_area, main_area, footer_area] = layout.areas(area);
|
||||
|
||||
let layout = Layout::vertical([Ratio(1, 3); 3]);
|
||||
let [gauge1_area, gauge2_area, gauge3_area] = layout.areas(main_area);
|
||||
|
||||
header().render(header_area, buf);
|
||||
footer().render(footer_area, buf);
|
||||
|
||||
self.render_gauge1(gauge1_area, buf);
|
||||
self.render_gauge2(gauge2_area, buf);
|
||||
self.render_gauge3(gauge3_area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn header() -> impl Widget {
|
||||
Paragraph::new("Ratatui Line Gauge Example")
|
||||
.bold()
|
||||
.alignment(Alignment::Center)
|
||||
.fg(CUSTOM_LABEL_COLOR)
|
||||
}
|
||||
|
||||
fn footer() -> impl Widget {
|
||||
Paragraph::new("Press ENTER / SPACE to start")
|
||||
.alignment(Alignment::Center)
|
||||
.fg(CUSTOM_LABEL_COLOR)
|
||||
.bold()
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn render_gauge1(&self, area: Rect, buf: &mut Buffer) {
|
||||
let title = title_block("Blue / red only foreground");
|
||||
LineGauge::default()
|
||||
.block(title)
|
||||
.filled_style(Style::default().fg(Color::Blue))
|
||||
.unfilled_style(Style::default().fg(Color::Red))
|
||||
.label("Foreground:")
|
||||
.ratio(self.progress)
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn render_gauge2(&self, area: Rect, buf: &mut Buffer) {
|
||||
let title = title_block("Blue / red only background");
|
||||
LineGauge::default()
|
||||
.block(title)
|
||||
.filled_style(Style::default().fg(Color::Blue).bg(Color::Blue))
|
||||
.unfilled_style(Style::default().fg(Color::Red).bg(Color::Red))
|
||||
.label("Background:")
|
||||
.ratio(self.progress)
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn render_gauge3(&self, area: Rect, buf: &mut Buffer) {
|
||||
let title = title_block("Fully styled with background");
|
||||
LineGauge::default()
|
||||
.block(title)
|
||||
.filled_style(
|
||||
Style::default()
|
||||
.fg(tailwind::BLUE.c400)
|
||||
.bg(tailwind::BLUE.c600),
|
||||
)
|
||||
.unfilled_style(
|
||||
Style::default()
|
||||
.fg(tailwind::RED.c400)
|
||||
.bg(tailwind::RED.c800),
|
||||
)
|
||||
.label("Both:")
|
||||
.ratio(self.progress)
|
||||
.render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn title_block(title: &str) -> Block {
|
||||
let title = Title::from(title).alignment(Alignment::Center);
|
||||
Block::default()
|
||||
.title(title)
|
||||
.borders(Borders::NONE)
|
||||
.fg(CUSTOM_LABEL_COLOR)
|
||||
.padding(Padding::vertical(1))
|
||||
}
|
||||
|
||||
fn init_error_hooks() -> color_eyre::Result<()> {
|
||||
let (panic, error) = HookBuilder::default().into_hooks();
|
||||
let panic = panic.into_panic_hook();
|
||||
let error = error.into_eyre_hook();
|
||||
color_eyre::eyre::set_hook(Box::new(move |e| {
|
||||
let _ = restore_terminal();
|
||||
error(e)
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_terminal() -> color_eyre::Result<Terminal<impl Backend>> {
|
||||
enable_raw_mode()?;
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout());
|
||||
let terminal = Terminal::new(backend)?;
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
fn restore_terminal() -> color_eyre::Result<()> {
|
||||
disable_raw_mode()?;
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
101
examples/list.rs
101
examples/list.rs
@@ -13,17 +13,26 @@
|
||||
//! [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;
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
layout::{Alignment, Constraint, Layout, Rect},
|
||||
style::{palette::tailwind, Color, Modifier, Style, Stylize},
|
||||
terminal::Terminal,
|
||||
text::Line,
|
||||
widgets::{
|
||||
Block, Borders, HighlightSpacing, List, ListItem, ListState, Padding, Paragraph,
|
||||
StatefulWidget, Widget, Wrap,
|
||||
},
|
||||
};
|
||||
use ratatui::{prelude::*, style::palette::tailwind, widgets::*};
|
||||
|
||||
const TODO_HEADER_BG: Color = tailwind::BLUE.c950;
|
||||
const NORMAL_ROW_COLOR: Color = tailwind::SLATE.c950;
|
||||
@@ -38,15 +47,25 @@ enum Status {
|
||||
Completed,
|
||||
}
|
||||
|
||||
struct TodoItem<'a> {
|
||||
todo: &'a str,
|
||||
info: &'a str,
|
||||
struct TodoItem {
|
||||
todo: String,
|
||||
info: String,
|
||||
status: Status,
|
||||
}
|
||||
|
||||
struct StatefulList<'a> {
|
||||
impl TodoItem {
|
||||
fn new(todo: &str, info: &str, status: Status) -> Self {
|
||||
Self {
|
||||
todo: todo.to_string(),
|
||||
info: info.to_string(),
|
||||
status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TodoList {
|
||||
state: ListState,
|
||||
items: Vec<TodoItem<'a>>,
|
||||
items: Vec<TodoItem>,
|
||||
last_selected: Option<usize>,
|
||||
}
|
||||
|
||||
@@ -56,8 +75,8 @@ struct StatefulList<'a> {
|
||||
///
|
||||
/// Check the event handling at the bottom to see how to change the state on incoming events.
|
||||
/// Check the drawing logic for items on how to specify the highlighting style for selected items.
|
||||
struct App<'a> {
|
||||
items: StatefulList<'a>,
|
||||
struct App {
|
||||
items: TodoList,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
@@ -102,10 +121,10 @@ fn restore_terminal() -> color_eyre::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<'a> App<'a> {
|
||||
impl App {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
items: StatefulList::with_items([
|
||||
items: TodoList::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),
|
||||
("Pet your cat", "Minnak loves to be pet by you! Don't forget to pet and give some treats!", Status::Todo),
|
||||
@@ -135,22 +154,23 @@ impl<'a> App<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl App<'_> {
|
||||
impl App {
|
||||
fn run(&mut self, mut terminal: Terminal<impl Backend>) -> io::Result<()> {
|
||||
loop {
|
||||
self.draw(&mut terminal)?;
|
||||
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
use KeyCode::*;
|
||||
match key.code {
|
||||
Char('q') | Esc => return Ok(()),
|
||||
Char('h') | Left => self.items.unselect(),
|
||||
Char('j') | Down => self.items.next(),
|
||||
Char('k') | Up => self.items.previous(),
|
||||
Char('l') | Right | Enter => self.change_status(),
|
||||
Char('g') => self.go_top(),
|
||||
Char('G') => self.go_bottom(),
|
||||
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
|
||||
KeyCode::Char('h') | KeyCode::Left => self.items.unselect(),
|
||||
KeyCode::Char('j') | KeyCode::Down => self.items.next(),
|
||||
KeyCode::Char('k') | KeyCode::Up => self.items.previous(),
|
||||
KeyCode::Char('l') | KeyCode::Right | KeyCode::Enter => {
|
||||
self.change_status();
|
||||
}
|
||||
KeyCode::Char('g') => self.go_top(),
|
||||
KeyCode::Char('G') => self.go_bottom(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -164,7 +184,7 @@ impl App<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &mut App<'_> {
|
||||
impl Widget for &mut App {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
// Create a space for header, todo list and the footer.
|
||||
let vertical = Layout::vertical([
|
||||
@@ -186,7 +206,7 @@ impl Widget for &mut App<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl App<'_> {
|
||||
impl App {
|
||||
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::new()
|
||||
@@ -238,8 +258,8 @@ impl App<'_> {
|
||||
// We get the info depending on the item's state.
|
||||
let info = if let Some(i) = self.items.state.selected() {
|
||||
match self.items.items[i].status {
|
||||
Status::Completed => "✓ DONE: ".to_string() + self.items.items[i].info,
|
||||
Status::Todo => "TODO: ".to_string() + self.items.items[i].info,
|
||||
Status::Completed => format!("✓ DONE: {}", self.items.items[i].info),
|
||||
Status::Todo => format!("TODO: {}", self.items.items[i].info),
|
||||
}
|
||||
} else {
|
||||
"Nothing to see here...".to_string()
|
||||
@@ -288,11 +308,14 @@ fn render_footer(area: Rect, buf: &mut Buffer) {
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
impl StatefulList<'_> {
|
||||
fn with_items<'a>(items: [(&'a str, &'a str, Status); 6]) -> StatefulList<'a> {
|
||||
StatefulList {
|
||||
impl TodoList {
|
||||
fn with_items(items: &[(&str, &str, Status)]) -> Self {
|
||||
Self {
|
||||
state: ListState::default(),
|
||||
items: items.iter().map(TodoItem::from).collect(),
|
||||
items: items
|
||||
.iter()
|
||||
.map(|(todo, info, status)| TodoItem::new(todo, info, *status))
|
||||
.collect(),
|
||||
last_selected: None,
|
||||
}
|
||||
}
|
||||
@@ -333,7 +356,7 @@ impl StatefulList<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl TodoItem<'_> {
|
||||
impl TodoItem {
|
||||
fn to_list_item(&self, index: usize) -> ListItem {
|
||||
let bg_color = match index % 2 {
|
||||
0 => NORMAL_ROW_COLOR,
|
||||
@@ -350,13 +373,3 @@ impl TodoItem<'_> {
|
||||
ListItem::new(line).bg(bg_color)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&(&'a str, &'a str, Status)> for TodoItem<'a> {
|
||||
fn from((todo, info, status): &(&'a str, &'a str, Status)) -> Self {
|
||||
Self {
|
||||
todo,
|
||||
info,
|
||||
status: *status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,16 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
text::Text,
|
||||
Terminal,
|
||||
};
|
||||
use ratatui::{backend::CrosstermBackend, text::Text, Terminal};
|
||||
|
||||
/// This is a bare minimum example. There are many approaches to running an application loop, so
|
||||
/// this is not meant to be prescriptive. See the [examples] folder for more complete examples.
|
||||
|
||||
@@ -25,13 +25,20 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use ratatui::{prelude::*, widgets::Paragraph};
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
layout::{Constraint, Layout},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
terminal::{Frame, Terminal},
|
||||
text::Line,
|
||||
widgets::Paragraph,
|
||||
};
|
||||
|
||||
type Result<T> = result::Result<T, Box<dyn Error>>;
|
||||
|
||||
|
||||
@@ -31,12 +31,14 @@
|
||||
|
||||
use std::{error::Error, io};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
terminal::{Frame, Terminal},
|
||||
text::Line,
|
||||
widgets::{Block, Paragraph},
|
||||
};
|
||||
|
||||
|
||||
@@ -19,13 +19,17 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
layout::{Constraint, Layout},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
terminal::{Frame, Terminal},
|
||||
text::{Line, Masked, Span},
|
||||
widgets::{Block, Paragraph, Wrap},
|
||||
};
|
||||
|
||||
|
||||
@@ -18,13 +18,16 @@
|
||||
|
||||
use std::{error::Error, io};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::Stylize,
|
||||
terminal::{Frame, Terminal},
|
||||
widgets::{Block, Clear, Paragraph, Wrap},
|
||||
};
|
||||
|
||||
|
||||
@@ -19,10 +19,15 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use indoc::indoc;
|
||||
use itertools::izip;
|
||||
use ratatui::{prelude::*, widgets::Paragraph};
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::terminal::{disable_raw_mode, enable_raw_mode},
|
||||
terminal::{Terminal, Viewport},
|
||||
widgets::Paragraph,
|
||||
TerminalOptions,
|
||||
};
|
||||
|
||||
/// A fun example of using half block characters to draw a logo
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
#![warn(clippy::pedantic)]
|
||||
#![allow(clippy::wildcard_imports)]
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
@@ -22,12 +21,20 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
layout::{Alignment, Constraint, Layout, Margin},
|
||||
style::{Color, Style, Stylize},
|
||||
symbols::scrollbar,
|
||||
terminal::{Frame, Terminal},
|
||||
text::{Line, Masked, Span},
|
||||
widgets::{Block, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState},
|
||||
};
|
||||
use ratatui::{prelude::*, symbols::scrollbar, widgets::*};
|
||||
|
||||
#[derive(Default)]
|
||||
struct App {
|
||||
@@ -186,7 +193,7 @@ fn ui(f: &mut Frame, app: &mut App) {
|
||||
.begin_symbol(None)
|
||||
.track_symbol(None)
|
||||
.end_symbol(None),
|
||||
chunks[2].inner(&Margin {
|
||||
chunks[2].inner(Margin {
|
||||
vertical: 1,
|
||||
horizontal: 0,
|
||||
}),
|
||||
@@ -204,7 +211,7 @@ fn ui(f: &mut Frame, app: &mut App) {
|
||||
Scrollbar::new(ScrollbarOrientation::HorizontalBottom)
|
||||
.thumb_symbol("🬋")
|
||||
.end_symbol(None),
|
||||
chunks[3].inner(&Margin {
|
||||
chunks[3].inner(Margin {
|
||||
vertical: 0,
|
||||
horizontal: 1,
|
||||
}),
|
||||
@@ -222,7 +229,7 @@ fn ui(f: &mut Frame, app: &mut App) {
|
||||
Scrollbar::new(ScrollbarOrientation::HorizontalBottom)
|
||||
.thumb_symbol("░")
|
||||
.track_symbol(Some("─")),
|
||||
chunks[4].inner(&Margin {
|
||||
chunks[4].inner(Margin {
|
||||
vertical: 0,
|
||||
horizontal: 1,
|
||||
}),
|
||||
|
||||
@@ -19,17 +19,20 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use rand::{
|
||||
distributions::{Distribution, Uniform},
|
||||
rngs::ThreadRng,
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
layout::{Constraint, Layout},
|
||||
style::{Color, Style},
|
||||
terminal::{Frame, Terminal},
|
||||
widgets::{Block, Borders, Sparkline},
|
||||
};
|
||||
|
||||
|
||||
@@ -13,17 +13,25 @@
|
||||
//! [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::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
layout::{Constraint, Layout, Margin, Rect},
|
||||
style::{self, Color, Modifier, Style, Stylize},
|
||||
terminal::{Frame, Terminal},
|
||||
text::{Line, Text},
|
||||
widgets::{
|
||||
Block, BorderType, Cell, HighlightSpacing, Paragraph, Row, Scrollbar, ScrollbarOrientation,
|
||||
ScrollbarState, Table, TableState,
|
||||
},
|
||||
};
|
||||
use style::palette::tailwind;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
@@ -212,13 +220,12 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<(
|
||||
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
use KeyCode::*;
|
||||
match key.code {
|
||||
Char('q') | Esc => return Ok(()),
|
||||
Char('j') | Down => app.next(),
|
||||
Char('k') | Up => app.previous(),
|
||||
Char('l') | Right => app.next_color(),
|
||||
Char('h') | Left => app.previous_color(),
|
||||
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
|
||||
KeyCode::Char('j') | KeyCode::Down => app.next(),
|
||||
KeyCode::Char('k') | KeyCode::Up => app.previous(),
|
||||
KeyCode::Char('l') | KeyCode::Right => app.next_color(),
|
||||
KeyCode::Char('h') | KeyCode::Left => app.previous_color(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -318,7 +325,7 @@ fn render_scrollbar(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
.orientation(ScrollbarOrientation::VerticalRight)
|
||||
.begin_symbol(None)
|
||||
.end_symbol(None),
|
||||
area.inner(&Margin {
|
||||
area.inner(Margin {
|
||||
vertical: 1,
|
||||
horizontal: 1,
|
||||
}),
|
||||
|
||||
@@ -13,17 +13,24 @@
|
||||
//! [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, clippy::enum_glob_use)]
|
||||
|
||||
use std::io::stdout;
|
||||
|
||||
use color_eyre::{config::HookBuilder, Result};
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{palette::tailwind, Color, Stylize},
|
||||
symbols,
|
||||
terminal::Terminal,
|
||||
text::Line,
|
||||
widgets::{Block, Padding, Paragraph, Tabs, Widget},
|
||||
};
|
||||
use ratatui::{prelude::*, style::palette::tailwind, widgets::*};
|
||||
use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -77,11 +84,10 @@ impl App {
|
||||
fn handle_events(&mut self) -> std::io::Result<()> {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
use KeyCode::*;
|
||||
match key.code {
|
||||
Char('l') | Right => self.next_tab(),
|
||||
Char('h') | Left => self.previous_tab(),
|
||||
Char('q') | Esc => self.quit(),
|
||||
KeyCode::Char('l') | KeyCode::Right => self.next_tab(),
|
||||
KeyCode::Char('h') | KeyCode::Left => self.previous_tab(),
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.quit(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -120,7 +126,7 @@ impl SelectedTab {
|
||||
|
||||
impl Widget for &App {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
use Constraint::*;
|
||||
use Constraint::{Length, Min};
|
||||
let vertical = Layout::vertical([Length(1), Min(0), Length(1)]);
|
||||
let [header_area, inner_area, footer_area] = vertical.areas(area);
|
||||
|
||||
|
||||
@@ -29,13 +29,17 @@
|
||||
|
||||
use std::{error::Error, io};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
layout::{Constraint, Layout},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
terminal::{Frame, Terminal},
|
||||
text::{Line, Span, Text},
|
||||
widgets::{Block, List, ListItem, Paragraph},
|
||||
};
|
||||
|
||||
@@ -86,7 +90,7 @@ impl App {
|
||||
///
|
||||
/// Since each character in a string can be contain multiple bytes, it's necessary to calculate
|
||||
/// the byte index based on the index of the character.
|
||||
fn byte_index(&mut self) -> usize {
|
||||
fn byte_index(&self) -> usize {
|
||||
self.input
|
||||
.char_indices()
|
||||
.map(|(i, _)| i)
|
||||
|
||||
14
examples/vhs/line_gauge.tape
Normal file
14
examples/vhs/line_gauge.tape
Normal file
@@ -0,0 +1,14 @@
|
||||
# This is a vhs script. See https://github.com/charmbracelet/vhs for more info.
|
||||
# To run this script, install vhs and run `vhs ./examples/line_gauge.tape`
|
||||
Output "target/line_gauge.gif"
|
||||
Set Theme "Aardvark Blue"
|
||||
Set Width 1200
|
||||
Set Height 850
|
||||
Hide
|
||||
Type "cargo run --example=line_gauge --features=crossterm"
|
||||
Enter
|
||||
Sleep 2s
|
||||
Show
|
||||
Sleep 2s
|
||||
Enter 1
|
||||
Sleep 15s
|
||||
@@ -1,6 +1,9 @@
|
||||
# configuration for https://rust-lang.github.io/rustfmt/
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Crate"
|
||||
wrap_comments = true
|
||||
comment_width = 100
|
||||
format_code_in_doc_comments = true
|
||||
format_macro_matchers=true
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Crate"
|
||||
normalize_doc_attributes=true
|
||||
use_field_init_shorthand=true
|
||||
wrap_comments = true
|
||||
|
||||
@@ -6,19 +6,19 @@ use std::io::{self, Write};
|
||||
|
||||
#[cfg(feature = "underline-color")]
|
||||
use crossterm::style::SetUnderlineColor;
|
||||
use crossterm::{
|
||||
cursor::{Hide, MoveTo, Show},
|
||||
execute, queue,
|
||||
style::{
|
||||
Attribute as CAttribute, Attributes as CAttributes, Color as CColor, Colors, ContentStyle,
|
||||
Print, SetAttribute, SetBackgroundColor, SetColors, SetForegroundColor,
|
||||
},
|
||||
terminal::{self, Clear},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backend::{Backend, ClearType, WindowSize},
|
||||
buffer::Cell,
|
||||
crossterm::{
|
||||
cursor::{Hide, MoveTo, Show},
|
||||
execute, queue,
|
||||
style::{
|
||||
Attribute as CAttribute, Attributes as CAttributes, Color as CColor, Colors,
|
||||
ContentStyle, Print, SetAttribute, SetBackgroundColor, SetColors, SetForegroundColor,
|
||||
},
|
||||
terminal::{self, Clear},
|
||||
},
|
||||
layout::Size,
|
||||
prelude::Rect,
|
||||
style::{Color, Modifier, Style},
|
||||
@@ -45,11 +45,15 @@ use crate::{
|
||||
/// ```rust,no_run
|
||||
/// use std::io::{stderr, stdout};
|
||||
///
|
||||
/// use crossterm::{
|
||||
/// terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
/// ExecutableCommand,
|
||||
/// use ratatui::{
|
||||
/// crossterm::{
|
||||
/// terminal::{
|
||||
/// disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
/// },
|
||||
/// ExecutableCommand,
|
||||
/// },
|
||||
/// prelude::*,
|
||||
/// };
|
||||
/// use ratatui::prelude::*;
|
||||
///
|
||||
/// let mut backend = CrosstermBackend::new(stdout());
|
||||
/// // or
|
||||
|
||||
@@ -9,13 +9,12 @@ use std::{
|
||||
io::{self, Write},
|
||||
};
|
||||
|
||||
use termion::{color as tcolor, style as tstyle};
|
||||
|
||||
use crate::{
|
||||
backend::{Backend, ClearType, WindowSize},
|
||||
buffer::Cell,
|
||||
prelude::Rect,
|
||||
style::{Color, Modifier, Style},
|
||||
termion::{self, color as tcolor, color::Color as _, style as tstyle},
|
||||
};
|
||||
|
||||
/// A [`Backend`] implementation that uses [Termion] to render to the terminal.
|
||||
@@ -40,8 +39,10 @@ use crate::{
|
||||
/// ```rust,no_run
|
||||
/// use std::io::{stderr, stdout};
|
||||
///
|
||||
/// use ratatui::prelude::*;
|
||||
/// use termion::{raw::IntoRawMode, screen::IntoAlternateScreen};
|
||||
/// use ratatui::{
|
||||
/// prelude::*,
|
||||
/// termion::{raw::IntoRawMode, screen::IntoAlternateScreen},
|
||||
/// };
|
||||
///
|
||||
/// let writer = stdout().into_raw_mode()?.into_alternate_screen()?;
|
||||
/// let mut backend = TermionBackend::new(writer);
|
||||
@@ -243,7 +244,6 @@ struct ModifierDiff {
|
||||
|
||||
impl fmt::Display for Fg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use termion::color::Color as TermionColor;
|
||||
match self.0 {
|
||||
Color::Reset => termion::color::Reset.write_fg(f),
|
||||
Color::Black => termion::color::Black.write_fg(f),
|
||||
@@ -269,7 +269,6 @@ impl fmt::Display for Fg {
|
||||
}
|
||||
impl fmt::Display for Bg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use termion::color::Color as TermionColor;
|
||||
match self.0 {
|
||||
Color::Reset => termion::color::Reset.write_bg(f),
|
||||
Color::Black => termion::color::Black.write_bg(f),
|
||||
@@ -295,7 +294,7 @@ impl fmt::Display for Bg {
|
||||
}
|
||||
|
||||
macro_rules! from_termion_for_color {
|
||||
($termion_color:ident, $color: ident) => {
|
||||
($termion_color:ident, $color:ident) => {
|
||||
impl From<tcolor::$termion_color> for Color {
|
||||
fn from(_: tcolor::$termion_color) -> Self {
|
||||
Color::$color
|
||||
@@ -436,7 +435,7 @@ impl fmt::Display for ModifierDiff {
|
||||
}
|
||||
|
||||
macro_rules! from_termion_for_modifier {
|
||||
($termion_modifier:ident, $modifier: ident) => {
|
||||
($termion_modifier:ident, $modifier:ident) => {
|
||||
impl From<tstyle::$termion_modifier> for Modifier {
|
||||
fn from(_: tstyle::$termion_modifier) -> Self {
|
||||
Modifier::$modifier
|
||||
|
||||
@@ -7,20 +7,19 @@
|
||||
|
||||
use std::{error::Error, io};
|
||||
|
||||
use termwiz::{
|
||||
caps::Capabilities,
|
||||
cell::{AttributeChange, Blink, CellAttributes, Intensity, Underline},
|
||||
color::{AnsiColor, ColorAttribute, ColorSpec, LinearRgba, RgbColor, SrgbaTuple},
|
||||
surface::{Change, CursorVisibility, Position},
|
||||
terminal::{buffered::BufferedTerminal, ScreenSize, SystemTerminal, Terminal},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backend::{Backend, WindowSize},
|
||||
buffer::Cell,
|
||||
layout::Size,
|
||||
prelude::Rect,
|
||||
style::{Color, Modifier, Style},
|
||||
termwiz::{
|
||||
caps::Capabilities,
|
||||
cell::{AttributeChange, Blink, CellAttributes, Intensity, Underline},
|
||||
color::{AnsiColor, ColorAttribute, ColorSpec, LinearRgba, RgbColor, SrgbaTuple},
|
||||
surface::{Change, CursorVisibility, Position},
|
||||
terminal::{buffered::BufferedTerminal, ScreenSize, SystemTerminal, Terminal},
|
||||
},
|
||||
};
|
||||
|
||||
/// A [`Backend`] implementation that uses [Termwiz] to render to the terminal.
|
||||
|
||||
@@ -170,26 +170,29 @@ impl Backend for TestBackend {
|
||||
}
|
||||
|
||||
fn clear_region(&mut self, clear_type: super::ClearType) -> io::Result<()> {
|
||||
match clear_type {
|
||||
ClearType::All => self.clear()?,
|
||||
let region = match clear_type {
|
||||
ClearType::All => return self.clear(),
|
||||
ClearType::AfterCursor => {
|
||||
let index = self.buffer.index_of(self.pos.0, self.pos.1) + 1;
|
||||
self.buffer.content[index..].fill(Cell::default());
|
||||
&mut self.buffer.content[index..]
|
||||
}
|
||||
ClearType::BeforeCursor => {
|
||||
let index = self.buffer.index_of(self.pos.0, self.pos.1);
|
||||
self.buffer.content[..index].fill(Cell::default());
|
||||
&mut self.buffer.content[..index]
|
||||
}
|
||||
ClearType::CurrentLine => {
|
||||
let line_start_index = self.buffer.index_of(0, self.pos.1);
|
||||
let line_end_index = self.buffer.index_of(self.width - 1, self.pos.1);
|
||||
self.buffer.content[line_start_index..=line_end_index].fill(Cell::default());
|
||||
&mut self.buffer.content[line_start_index..=line_end_index]
|
||||
}
|
||||
ClearType::UntilNewLine => {
|
||||
let index = self.buffer.index_of(self.pos.0, self.pos.1);
|
||||
let line_end_index = self.buffer.index_of(self.width - 1, self.pos.1);
|
||||
self.buffer.content[index..=line_end_index].fill(Cell::default());
|
||||
&mut self.buffer.content[index..=line_end_index]
|
||||
}
|
||||
};
|
||||
for cell in region {
|
||||
cell.reset();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -326,8 +329,7 @@ mod tests {
|
||||
#[test]
|
||||
fn draw() {
|
||||
let mut backend = TestBackend::new(10, 2);
|
||||
let mut cell = Cell::default();
|
||||
cell.set_symbol("a");
|
||||
let cell = Cell::new("a");
|
||||
backend.draw([(0, 0, &cell)].into_iter()).unwrap();
|
||||
backend.draw([(0, 1, &cell)].into_iter()).unwrap();
|
||||
backend.assert_buffer_lines(["a "; 2]);
|
||||
@@ -363,8 +365,7 @@ mod tests {
|
||||
#[test]
|
||||
fn clear() {
|
||||
let mut backend = TestBackend::new(4, 2);
|
||||
let mut cell = Cell::default();
|
||||
cell.set_symbol("a");
|
||||
let cell = Cell::new("a");
|
||||
backend.draw([(0, 0, &cell)].into_iter()).unwrap();
|
||||
backend.draw([(0, 1, &cell)].into_iter()).unwrap();
|
||||
backend.clear().unwrap();
|
||||
|
||||
@@ -54,14 +54,14 @@ impl Buffer {
|
||||
/// Returns a Buffer with all cells set to the default one
|
||||
#[must_use]
|
||||
pub fn empty(area: Rect) -> Self {
|
||||
Self::filled(area, &Cell::default())
|
||||
Self::filled(area, Cell::EMPTY)
|
||||
}
|
||||
|
||||
/// Returns a Buffer with all cells initialized with the attributes of the given Cell
|
||||
#[must_use]
|
||||
pub fn filled(area: Rect, cell: &Cell) -> Self {
|
||||
pub fn filled(area: Rect, cell: Cell) -> Self {
|
||||
let size = area.area() as usize;
|
||||
let content = vec![cell.clone(); size];
|
||||
let content = vec![cell; size];
|
||||
Self { area, content }
|
||||
}
|
||||
|
||||
@@ -278,22 +278,22 @@ impl Buffer {
|
||||
if self.content.len() > length {
|
||||
self.content.truncate(length);
|
||||
} else {
|
||||
self.content.resize(length, Cell::default());
|
||||
self.content.resize(length, Cell::EMPTY);
|
||||
}
|
||||
self.area = area;
|
||||
}
|
||||
|
||||
/// Reset all cells in the buffer
|
||||
pub fn reset(&mut self) {
|
||||
for c in &mut self.content {
|
||||
c.reset();
|
||||
for cell in &mut self.content {
|
||||
cell.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge an other buffer into this one
|
||||
pub fn merge(&mut self, other: &Self) {
|
||||
let area = self.area.union(other.area);
|
||||
self.content.resize(area.area() as usize, Cell::default());
|
||||
self.content.resize(area.area() as usize, Cell::EMPTY);
|
||||
|
||||
// Move original content to the appropriate space
|
||||
let size = self.area.area() as usize;
|
||||
@@ -303,7 +303,7 @@ impl Buffer {
|
||||
let k = ((y - area.y) * area.width + x - area.x) as usize;
|
||||
if i != k {
|
||||
self.content[k] = self.content[i].clone();
|
||||
self.content[i] = Cell::default();
|
||||
self.content[i].reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,12 +453,6 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
fn cell(s: &str) -> Cell {
|
||||
let mut cell = Cell::default();
|
||||
cell.set_symbol(s);
|
||||
cell
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_empty_buffer() {
|
||||
let buffer = Buffer::empty(Rect::ZERO);
|
||||
@@ -749,14 +743,14 @@ mod tests {
|
||||
let prev = Buffer::empty(area);
|
||||
let next = Buffer::empty(area);
|
||||
let diff = prev.diff(&next);
|
||||
assert_eq!(diff, vec![]);
|
||||
assert_eq!(diff, []);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_empty_filled() {
|
||||
let area = Rect::new(0, 0, 40, 40);
|
||||
let prev = Buffer::empty(area);
|
||||
let next = Buffer::filled(area, Cell::default().set_symbol("a"));
|
||||
let next = Buffer::filled(area, Cell::new("a"));
|
||||
let diff = prev.diff(&next);
|
||||
assert_eq!(diff.len(), 40 * 40);
|
||||
}
|
||||
@@ -764,10 +758,10 @@ mod tests {
|
||||
#[test]
|
||||
fn diff_filled_filled() {
|
||||
let area = Rect::new(0, 0, 40, 40);
|
||||
let prev = Buffer::filled(area, Cell::default().set_symbol("a"));
|
||||
let next = Buffer::filled(area, Cell::default().set_symbol("a"));
|
||||
let prev = Buffer::filled(area, Cell::new("a"));
|
||||
let next = Buffer::filled(area, Cell::new("a"));
|
||||
let diff = prev.diff(&next);
|
||||
assert_eq!(diff, vec![]);
|
||||
assert_eq!(diff, []);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -789,22 +783,23 @@ mod tests {
|
||||
let diff = prev.diff(&next);
|
||||
assert_eq!(
|
||||
diff,
|
||||
vec![
|
||||
(2, 1, &cell("I")),
|
||||
(3, 1, &cell("T")),
|
||||
(4, 1, &cell("L")),
|
||||
(5, 1, &cell("E")),
|
||||
[
|
||||
(2, 1, &Cell::new("I")),
|
||||
(3, 1, &Cell::new("T")),
|
||||
(4, 1, &Cell::new("L")),
|
||||
(5, 1, &Cell::new("E")),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn diff_multi_width() {
|
||||
#[rustfmt::skip]
|
||||
let prev = Buffer::with_lines([
|
||||
"┌Title─┐ ",
|
||||
"└──────┘ ",
|
||||
]);
|
||||
#[rustfmt::skip]
|
||||
let next = Buffer::with_lines([
|
||||
"┌称号──┐ ",
|
||||
"└──────┘ ",
|
||||
@@ -812,12 +807,12 @@ mod tests {
|
||||
let diff = prev.diff(&next);
|
||||
assert_eq!(
|
||||
diff,
|
||||
vec![
|
||||
(1, 0, &cell("称")),
|
||||
[
|
||||
(1, 0, &Cell::new("称")),
|
||||
// Skipped "i"
|
||||
(3, 0, &cell("号")),
|
||||
(3, 0, &Cell::new("号")),
|
||||
// Skipped "l"
|
||||
(5, 0, &cell("─")),
|
||||
(5, 0, &Cell::new("─")),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -830,7 +825,11 @@ mod tests {
|
||||
let diff = prev.diff(&next);
|
||||
assert_eq!(
|
||||
diff,
|
||||
vec![(1, 0, &cell("─")), (2, 0, &cell("称")), (4, 0, &cell("号")),]
|
||||
[
|
||||
(1, 0, &Cell::new("─")),
|
||||
(2, 0, &Cell::new("称")),
|
||||
(4, 0, &Cell::new("号")),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -843,59 +842,25 @@ mod tests {
|
||||
}
|
||||
|
||||
let diff = prev.diff(&next);
|
||||
assert_eq!(diff, vec![(0, 0, &cell("4"))],);
|
||||
assert_eq!(diff, [(0, 0, &Cell::new("4"))],);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge() {
|
||||
let mut one = Buffer::filled(
|
||||
Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 2,
|
||||
height: 2,
|
||||
},
|
||||
Cell::default().set_symbol("1"),
|
||||
);
|
||||
let two = Buffer::filled(
|
||||
Rect {
|
||||
x: 0,
|
||||
y: 2,
|
||||
width: 2,
|
||||
height: 2,
|
||||
},
|
||||
Cell::default().set_symbol("2"),
|
||||
);
|
||||
#[rstest]
|
||||
#[case(Rect::new(0, 0, 2, 2), Rect::new(0, 2, 2, 2), ["11", "11", "22", "22"])]
|
||||
#[case(Rect::new(2, 2, 2, 2), Rect::new(0, 0, 2, 2), ["22 ", "22 ", " 11", " 11"])]
|
||||
fn merge<'line, Lines>(#[case] one: Rect, #[case] two: Rect, #[case] expected: Lines)
|
||||
where
|
||||
Lines: IntoIterator,
|
||||
Lines::Item: Into<Line<'line>>,
|
||||
{
|
||||
let mut one = Buffer::filled(one, Cell::new("1"));
|
||||
let two = Buffer::filled(two, Cell::new("2"));
|
||||
one.merge(&two);
|
||||
assert_eq!(one, Buffer::with_lines(["11", "11", "22", "22"]));
|
||||
assert_eq!(one, Buffer::with_lines(expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge2() {
|
||||
let mut one = Buffer::filled(
|
||||
Rect {
|
||||
x: 2,
|
||||
y: 2,
|
||||
width: 2,
|
||||
height: 2,
|
||||
},
|
||||
Cell::default().set_symbol("1"),
|
||||
);
|
||||
let two = Buffer::filled(
|
||||
Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 2,
|
||||
height: 2,
|
||||
},
|
||||
Cell::default().set_symbol("2"),
|
||||
);
|
||||
one.merge(&two);
|
||||
assert_eq!(one, Buffer::with_lines(["22 ", "22 ", " 11", " 11"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge3() {
|
||||
fn merge_with_offset() {
|
||||
let mut one = Buffer::filled(
|
||||
Rect {
|
||||
x: 3,
|
||||
@@ -903,7 +868,7 @@ mod tests {
|
||||
width: 2,
|
||||
height: 2,
|
||||
},
|
||||
Cell::default().set_symbol("1"),
|
||||
Cell::new("1"),
|
||||
);
|
||||
let two = Buffer::filled(
|
||||
Rect {
|
||||
@@ -912,67 +877,48 @@ mod tests {
|
||||
width: 3,
|
||||
height: 4,
|
||||
},
|
||||
Cell::default().set_symbol("2"),
|
||||
Cell::new("2"),
|
||||
);
|
||||
one.merge(&two);
|
||||
let mut merged = Buffer::with_lines(["222 ", "222 ", "2221", "2221"]);
|
||||
merged.area = Rect {
|
||||
let mut expected = Buffer::with_lines(["222 ", "222 ", "2221", "2221"]);
|
||||
expected.area = Rect {
|
||||
x: 1,
|
||||
y: 1,
|
||||
width: 4,
|
||||
height: 4,
|
||||
};
|
||||
assert_eq!(one, merged);
|
||||
assert_eq!(one, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_skip() {
|
||||
let mut one = Buffer::filled(
|
||||
Rect {
|
||||
#[rstest]
|
||||
#[case(false, true, [false, false, true, true, true, true])]
|
||||
#[case(true, false, [true, true, false, false, false, false])]
|
||||
fn merge_skip(#[case] skip_one: bool, #[case] skip_two: bool, #[case] expected: [bool; 6]) {
|
||||
let mut one = {
|
||||
let area = Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 2,
|
||||
height: 2,
|
||||
},
|
||||
Cell::default().set_symbol("1"),
|
||||
);
|
||||
let two = Buffer::filled(
|
||||
Rect {
|
||||
};
|
||||
let mut cell = Cell::new("1");
|
||||
cell.skip = skip_one;
|
||||
Buffer::filled(area, cell)
|
||||
};
|
||||
let two = {
|
||||
let area = Rect {
|
||||
x: 0,
|
||||
y: 1,
|
||||
width: 2,
|
||||
height: 2,
|
||||
},
|
||||
Cell::default().set_symbol("2").set_skip(true),
|
||||
);
|
||||
};
|
||||
let mut cell = Cell::new("2");
|
||||
cell.skip = skip_two;
|
||||
Buffer::filled(area, cell)
|
||||
};
|
||||
one.merge(&two);
|
||||
let skipped: Vec<bool> = one.content().iter().map(|c| c.skip).collect();
|
||||
assert_eq!(skipped, vec![false, false, true, true, true, true]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_skip2() {
|
||||
let mut one = Buffer::filled(
|
||||
Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 2,
|
||||
height: 2,
|
||||
},
|
||||
Cell::default().set_symbol("1").set_skip(true),
|
||||
);
|
||||
let two = Buffer::filled(
|
||||
Rect {
|
||||
x: 0,
|
||||
y: 1,
|
||||
width: 2,
|
||||
height: 2,
|
||||
},
|
||||
Cell::default().set_symbol("2"),
|
||||
);
|
||||
one.merge(&two);
|
||||
let skipped: Vec<bool> = one.content().iter().map(|c| c.skip).collect();
|
||||
assert_eq!(skipped, vec![true, true, false, false, false, false]);
|
||||
let skipped = one.content().iter().map(|c| c.skip).collect::<Vec<_>>();
|
||||
assert_eq!(skipped, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -34,7 +34,29 @@ pub struct Cell {
|
||||
}
|
||||
|
||||
impl Cell {
|
||||
/// An empty `Cell`
|
||||
pub const EMPTY: Self = Self::new(" ");
|
||||
|
||||
/// Creates a new `Cell` with the given symbol.
|
||||
///
|
||||
/// This works at compile time and puts the symbol onto the stack. Fails to build when the
|
||||
/// symbol doesnt fit onto the stack and requires to be placed on the heap. Use
|
||||
/// `Self::default().set_symbol()` in that case. See [`CompactString::new_inline`] for more
|
||||
/// details on this.
|
||||
pub const fn new(symbol: &str) -> Self {
|
||||
Self {
|
||||
symbol: CompactString::new_inline(symbol),
|
||||
fg: Color::Reset,
|
||||
bg: Color::Reset,
|
||||
#[cfg(feature = "underline-color")]
|
||||
underline_color: Color::Reset,
|
||||
modifier: Modifier::empty(),
|
||||
skip: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the symbol of the cell.
|
||||
#[must_use]
|
||||
pub fn symbol(&self) -> &str {
|
||||
self.symbol.as_str()
|
||||
}
|
||||
@@ -86,19 +108,16 @@ impl Cell {
|
||||
}
|
||||
|
||||
/// Returns the style of the cell.
|
||||
pub fn style(&self) -> Style {
|
||||
#[cfg(feature = "underline-color")]
|
||||
return Style::default()
|
||||
.fg(self.fg)
|
||||
.bg(self.bg)
|
||||
.underline_color(self.underline_color)
|
||||
.add_modifier(self.modifier);
|
||||
|
||||
#[cfg(not(feature = "underline-color"))]
|
||||
return Style::default()
|
||||
.fg(self.fg)
|
||||
.bg(self.bg)
|
||||
.add_modifier(self.modifier);
|
||||
#[must_use]
|
||||
pub const fn style(&self) -> Style {
|
||||
Style {
|
||||
fg: Some(self.fg),
|
||||
bg: Some(self.bg),
|
||||
#[cfg(feature = "underline-color")]
|
||||
underline_color: Some(self.underline_color),
|
||||
add_modifier: self.modifier,
|
||||
sub_modifier: Modifier::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the cell to be skipped when copying (diffing) the buffer to the screen.
|
||||
@@ -110,7 +129,7 @@ impl Cell {
|
||||
self
|
||||
}
|
||||
|
||||
/// Resets the cell to the default state.
|
||||
/// Resets the cell to the empty state.
|
||||
pub fn reset(&mut self) {
|
||||
self.symbol = CompactString::new_inline(" ");
|
||||
self.fg = Color::Reset;
|
||||
@@ -126,15 +145,7 @@ impl Cell {
|
||||
|
||||
impl Default for Cell {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
symbol: CompactString::new_inline(" "),
|
||||
fg: Color::Reset,
|
||||
bg: Color::Reset,
|
||||
#[cfg(feature = "underline-color")]
|
||||
underline_color: Color::Reset,
|
||||
modifier: Modifier::empty(),
|
||||
skip: false,
|
||||
}
|
||||
Self::EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,11 +155,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn symbol_field() {
|
||||
let mut cell = Cell::default();
|
||||
let mut cell = Cell::EMPTY;
|
||||
assert_eq!(cell.symbol(), " ");
|
||||
cell.set_symbol("あ"); // Multi-byte character
|
||||
assert_eq!(cell.symbol(), "あ");
|
||||
cell.set_symbol("👨👩👧👦"); // Multiple code units combined with ZWJ
|
||||
assert_eq!(cell.symbol(), "👨👩👧👦");
|
||||
|
||||
// above Cell::EMPTY is put into a mutable variable and is changed then.
|
||||
// While this looks like it might change the constant, it actually doesnt:
|
||||
assert_eq!(Cell::EMPTY.symbol(), " ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
mod alignment;
|
||||
mod constraint;
|
||||
mod corner;
|
||||
mod direction;
|
||||
mod flex;
|
||||
#[allow(clippy::module_inception)]
|
||||
@@ -14,7 +13,6 @@ mod size;
|
||||
|
||||
pub use alignment::Alignment;
|
||||
pub use constraint::Constraint;
|
||||
pub use corner::Corner;
|
||||
pub use direction::Direction;
|
||||
pub use flex::Flex;
|
||||
pub use layout::Layout;
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
#[derive(Debug, Default, Display, EnumString, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub enum Corner {
|
||||
#[default]
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomRight,
|
||||
BottomLeft,
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn corner_to_string() {
|
||||
assert_eq!(Corner::BottomLeft.to_string(), "BottomLeft");
|
||||
assert_eq!(Corner::BottomRight.to_string(), "BottomRight");
|
||||
assert_eq!(Corner::TopLeft.to_string(), "TopLeft");
|
||||
assert_eq!(Corner::TopRight.to_string(), "TopRight");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn corner_from_str() {
|
||||
assert_eq!("BottomLeft".parse::<Corner>(), Ok(Corner::BottomLeft));
|
||||
assert_eq!("BottomRight".parse::<Corner>(), Ok(Corner::BottomRight));
|
||||
assert_eq!("TopLeft".parse::<Corner>(), Ok(Corner::TopLeft));
|
||||
assert_eq!("TopRight".parse::<Corner>(), Ok(Corner::TopRight));
|
||||
assert_eq!("".parse::<Corner>(), Err(ParseError::VariantNotFound));
|
||||
}
|
||||
}
|
||||
@@ -608,7 +608,7 @@ impl Layout {
|
||||
// This is equivalent to storing the solver in `Layout` and calling `solver.reset()` here.
|
||||
let mut solver = Solver::new();
|
||||
|
||||
let inner_area = area.inner(&self.margin);
|
||||
let inner_area = area.inner(self.margin);
|
||||
let (area_start, area_end) = match self.direction {
|
||||
Direction::Horizontal => (
|
||||
f64::from(inner_area.x) * FLOAT_PRECISION_MULTIPLIER,
|
||||
|
||||
@@ -119,9 +119,8 @@ impl Rect {
|
||||
/// 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.
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)] // See PR #1008
|
||||
#[must_use = "method returns the modified value"]
|
||||
pub const fn inner(self, margin: &Margin) -> Self {
|
||||
pub const fn inner(self, margin: Margin) -> Self {
|
||||
let doubled_margin_horizontal = margin.horizontal.saturating_mul(2);
|
||||
let doubled_margin_vertical = margin.vertical.saturating_mul(2);
|
||||
|
||||
@@ -406,7 +405,7 @@ mod tests {
|
||||
#[test]
|
||||
fn inner() {
|
||||
assert_eq!(
|
||||
Rect::new(1, 2, 3, 4).inner(&Margin::new(1, 2)),
|
||||
Rect::new(1, 2, 3, 4).inner(Margin::new(1, 2)),
|
||||
Rect::new(2, 4, 1, 0)
|
||||
);
|
||||
}
|
||||
|
||||
65
src/lib.rs
65
src/lib.rs
@@ -20,10 +20,10 @@
|
||||
//!
|
||||
//! ## Installation
|
||||
//!
|
||||
//! Add `ratatui` and `crossterm` as dependencies to your cargo.toml:
|
||||
//! Add `ratatui` as a dependency to your cargo.toml:
|
||||
//!
|
||||
//! ```shell
|
||||
//! cargo add ratatui crossterm
|
||||
//! cargo add ratatui
|
||||
//! ```
|
||||
//!
|
||||
//! Ratatui uses [Crossterm] by default as it works on most platforms. See the [Installation]
|
||||
@@ -103,12 +103,17 @@
|
||||
//! ```rust,no_run
|
||||
//! use std::io::{self, stdout};
|
||||
//!
|
||||
//! use crossterm::{
|
||||
//! event::{self, Event, KeyCode},
|
||||
//! terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
//! ExecutableCommand,
|
||||
//! use ratatui::{
|
||||
//! crossterm::{
|
||||
//! event::{self, Event, KeyCode},
|
||||
//! terminal::{
|
||||
//! disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
//! },
|
||||
//! ExecutableCommand,
|
||||
//! },
|
||||
//! prelude::*,
|
||||
//! widgets::*,
|
||||
//! };
|
||||
//! use ratatui::{prelude::*, widgets::*};
|
||||
//!
|
||||
//! fn main() -> io::Result<()> {
|
||||
//! enable_raw_mode()?;
|
||||
@@ -255,22 +260,6 @@
|
||||
//! ![docsrs-styling]
|
||||
#![cfg_attr(feature = "document-features", doc = "\n## Features")]
|
||||
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
|
||||
#![cfg_attr(
|
||||
feature = "document-features",
|
||||
doc = "[`CrossTermBackend`]: backend::CrosstermBackend"
|
||||
)]
|
||||
#![cfg_attr(
|
||||
feature = "document-features",
|
||||
doc = "[`TermionBackend`]: backend::TermionBackend"
|
||||
)]
|
||||
#![cfg_attr(
|
||||
feature = "document-features",
|
||||
doc = "[`TermwizBackend`]: backend::TermwizBackend"
|
||||
)]
|
||||
#![cfg_attr(
|
||||
feature = "document-features",
|
||||
doc = "[`calendar`]: widgets::calendar::Monthly"
|
||||
)]
|
||||
//!
|
||||
//! [Ratatui Website]: https://ratatui.rs/
|
||||
//! [Installation]: https://ratatui.rs/installation/
|
||||
@@ -316,24 +305,20 @@
|
||||
//! [Termwiz]: https://crates.io/crates/termwiz
|
||||
//! [tui-rs]: https://crates.io/crates/tui
|
||||
//! [GitHub Sponsors]: https://github.com/sponsors/ratatui-org
|
||||
//! [Crate Badge]: https://img.shields.io/crates/v/ratatui?logo=rust&style=flat-square
|
||||
//! [License Badge]: https://img.shields.io/crates/l/ratatui?style=flat-square
|
||||
//! [CI Badge]:
|
||||
//! https://img.shields.io/github/actions/workflow/status/ratatui-org/ratatui/ci.yml?style=flat-square&logo=github
|
||||
//! [Crate Badge]: https://img.shields.io/crates/v/ratatui?logo=rust&style=flat-square&logoColor=E05D44&color=E05D44
|
||||
//! [License Badge]: https://img.shields.io/crates/l/ratatui?style=flat-square&color=1370D3
|
||||
//! [CI Badge]: https://img.shields.io/github/actions/workflow/status/ratatui-org/ratatui/ci.yml?style=flat-square&logo=github
|
||||
//! [CI Workflow]: https://github.com/ratatui-org/ratatui/actions/workflows/ci.yml
|
||||
//! [Codecov Badge]:
|
||||
//! https://img.shields.io/codecov/c/github/ratatui-org/ratatui?logo=codecov&style=flat-square&token=BAQ8SOKEST
|
||||
//! [Codecov Badge]: https://img.shields.io/codecov/c/github/ratatui-org/ratatui?logo=codecov&style=flat-square&token=BAQ8SOKEST&color=C43AC3&logoColor=C43AC3
|
||||
//! [Codecov]: https://app.codecov.io/gh/ratatui-org/ratatui
|
||||
//! [Deps.rs Badge]: https://deps.rs/repo/github/ratatui-org/ratatui/status.svg?style=flat-square
|
||||
//! [Deps.rs]: https://deps.rs/repo/github/ratatui-org/ratatui
|
||||
//! [Discord Badge]:
|
||||
//! https://img.shields.io/discord/1070692720437383208?label=discord&logo=discord&style=flat-square
|
||||
//! [Discord Badge]: https://img.shields.io/discord/1070692720437383208?label=discord&logo=discord&style=flat-square&color=1370D3&logoColor=1370D3
|
||||
//! [Discord Server]: https://discord.gg/pMCEU9hNEj
|
||||
//! [Docs Badge]: https://img.shields.io/docsrs/ratatui?logo=rust&style=flat-square
|
||||
//! [Matrix Badge]:
|
||||
//! https://img.shields.io/matrix/ratatui-general%3Amatrix.org?style=flat-square&logo=matrix&label=Matrix
|
||||
//! [Docs Badge]: https://img.shields.io/docsrs/ratatui?logo=rust&style=flat-square&logoColor=E05D44
|
||||
//! [Matrix Badge]: https://img.shields.io/matrix/ratatui-general%3Amatrix.org?style=flat-square&logo=matrix&label=Matrix&color=C43AC3
|
||||
//! [Matrix]: https://matrix.to/#/#ratatui:matrix.org
|
||||
//! [Sponsors Badge]: https://img.shields.io/github/sponsors/ratatui-org?logo=github&style=flat-square
|
||||
//! [Sponsors Badge]: https://img.shields.io/github/sponsors/ratatui-org?logo=github&style=flat-square&color=1370D3
|
||||
|
||||
// show the feature flags in the generated documentation
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
@@ -355,3 +340,13 @@ pub mod widgets;
|
||||
pub use self::terminal::{CompletedFrame, Frame, Terminal, TerminalOptions, Viewport};
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
/// re-export the `crossterm` crate so that users don't have to add it as a dependency
|
||||
#[cfg(feature = "crossterm")]
|
||||
pub use crossterm;
|
||||
/// re-export the `termion` crate so that users don't have to add it as a dependency
|
||||
#[cfg(feature = "termion")]
|
||||
pub use termion;
|
||||
/// re-export the `termwiz` crate so that users don't have to add it as a dependency
|
||||
#[cfg(feature = "termwiz")]
|
||||
pub use termwiz;
|
||||
|
||||
@@ -27,7 +27,7 @@ pub(crate) use crate::widgets::{StatefulWidgetRef, WidgetRef};
|
||||
pub use crate::{
|
||||
backend::{self, Backend},
|
||||
buffer::{self, Buffer},
|
||||
layout::{self, Alignment, Constraint, Corner, Direction, Layout, Margin, Rect},
|
||||
layout::{self, Alignment, Constraint, Direction, Layout, Margin, Rect},
|
||||
style::{self, Color, Modifier, Style, Styled, Stylize},
|
||||
symbols::{self, Marker},
|
||||
terminal::{CompletedFrame, Frame, Terminal, TerminalOptions, Viewport},
|
||||
|
||||
225
src/style.rs
225
src/style.rs
@@ -222,7 +222,7 @@ impl fmt::Debug for Modifier {
|
||||
/// buffer.get(0, 0).style(),
|
||||
/// );
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Style {
|
||||
pub fg: Option<Color>,
|
||||
@@ -233,12 +233,6 @@ pub struct Style {
|
||||
pub sub_modifier: Modifier,
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for Style {
|
||||
type Item = Self;
|
||||
|
||||
@@ -652,151 +646,80 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[rstest]
|
||||
#[case(Style::new().black(), Color::Black)]
|
||||
#[case(Style::new().red(), Color::Red)]
|
||||
#[case(Style::new().green(), Color::Green)]
|
||||
#[case(Style::new().yellow(), Color::Yellow)]
|
||||
#[case(Style::new().blue(), Color::Blue)]
|
||||
#[case(Style::new().magenta(), Color::Magenta)]
|
||||
#[case(Style::new().cyan(), Color::Cyan)]
|
||||
#[case(Style::new().white(), Color::White)]
|
||||
#[case(Style::new().gray(), Color::Gray)]
|
||||
#[case(Style::new().dark_gray(), Color::DarkGray)]
|
||||
#[case(Style::new().light_red(), Color::LightRed)]
|
||||
#[case(Style::new().light_green(), Color::LightGreen)]
|
||||
#[case(Style::new().light_yellow(), Color::LightYellow)]
|
||||
#[case(Style::new().light_blue(), Color::LightBlue)]
|
||||
#[case(Style::new().light_magenta(), Color::LightMagenta)]
|
||||
#[case(Style::new().light_cyan(), Color::LightCyan)]
|
||||
#[case(Style::new().white(), Color::White)]
|
||||
fn fg_can_be_stylized(#[case] stylized: Style, #[case] expected: Color) {
|
||||
assert_eq!(stylized, Style::new().fg(expected));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(Style::new().on_black(), Color::Black)]
|
||||
#[case(Style::new().on_red(), Color::Red)]
|
||||
#[case(Style::new().on_green(), Color::Green)]
|
||||
#[case(Style::new().on_yellow(), Color::Yellow)]
|
||||
#[case(Style::new().on_blue(), Color::Blue)]
|
||||
#[case(Style::new().on_magenta(), Color::Magenta)]
|
||||
#[case(Style::new().on_cyan(), Color::Cyan)]
|
||||
#[case(Style::new().on_white(), Color::White)]
|
||||
#[case(Style::new().on_gray(), Color::Gray)]
|
||||
#[case(Style::new().on_dark_gray(), Color::DarkGray)]
|
||||
#[case(Style::new().on_light_red(), Color::LightRed)]
|
||||
#[case(Style::new().on_light_green(), Color::LightGreen)]
|
||||
#[case(Style::new().on_light_yellow(), Color::LightYellow)]
|
||||
#[case(Style::new().on_light_blue(), Color::LightBlue)]
|
||||
#[case(Style::new().on_light_magenta(), Color::LightMagenta)]
|
||||
#[case(Style::new().on_light_cyan(), Color::LightCyan)]
|
||||
#[case(Style::new().on_white(), Color::White)]
|
||||
fn bg_can_be_stylized(#[case] stylized: Style, #[case] expected: Color) {
|
||||
assert_eq!(stylized, Style::new().bg(expected));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(Style::new().bold(), Modifier::BOLD)]
|
||||
#[case(Style::new().dim(), Modifier::DIM)]
|
||||
#[case(Style::new().italic(), Modifier::ITALIC)]
|
||||
#[case(Style::new().underlined(), Modifier::UNDERLINED)]
|
||||
#[case(Style::new().slow_blink(), Modifier::SLOW_BLINK)]
|
||||
#[case(Style::new().rapid_blink(), Modifier::RAPID_BLINK)]
|
||||
#[case(Style::new().reversed(), Modifier::REVERSED)]
|
||||
#[case(Style::new().hidden(), Modifier::HIDDEN)]
|
||||
#[case(Style::new().crossed_out(), Modifier::CROSSED_OUT)]
|
||||
fn add_modifier_can_be_stylized(#[case] stylized: Style, #[case] expected: Modifier) {
|
||||
assert_eq!(stylized, Style::new().add_modifier(expected));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(Style::new().not_bold(), Modifier::BOLD)]
|
||||
#[case(Style::new().not_dim(), Modifier::DIM)]
|
||||
#[case(Style::new().not_italic(), Modifier::ITALIC)]
|
||||
#[case(Style::new().not_underlined(), Modifier::UNDERLINED)]
|
||||
#[case(Style::new().not_slow_blink(), Modifier::SLOW_BLINK)]
|
||||
#[case(Style::new().not_rapid_blink(), Modifier::RAPID_BLINK)]
|
||||
#[case(Style::new().not_reversed(), Modifier::REVERSED)]
|
||||
#[case(Style::new().not_hidden(), Modifier::HIDDEN)]
|
||||
#[case(Style::new().not_crossed_out(), Modifier::CROSSED_OUT)]
|
||||
fn remove_modifier_can_be_stylized(#[case] stylized: Style, #[case] expected: Modifier) {
|
||||
assert_eq!(stylized, Style::new().remove_modifier(expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_can_be_stylized() {
|
||||
// foreground colors
|
||||
assert_eq!(Style::new().black(), Style::new().fg(Color::Black));
|
||||
assert_eq!(Style::new().red(), Style::new().fg(Color::Red));
|
||||
assert_eq!(Style::new().green(), Style::new().fg(Color::Green));
|
||||
assert_eq!(Style::new().yellow(), Style::new().fg(Color::Yellow));
|
||||
assert_eq!(Style::new().blue(), Style::new().fg(Color::Blue));
|
||||
assert_eq!(Style::new().magenta(), Style::new().fg(Color::Magenta));
|
||||
assert_eq!(Style::new().cyan(), Style::new().fg(Color::Cyan));
|
||||
assert_eq!(Style::new().white(), Style::new().fg(Color::White));
|
||||
assert_eq!(Style::new().gray(), Style::new().fg(Color::Gray));
|
||||
assert_eq!(Style::new().dark_gray(), Style::new().fg(Color::DarkGray));
|
||||
assert_eq!(Style::new().light_red(), Style::new().fg(Color::LightRed));
|
||||
assert_eq!(
|
||||
Style::new().light_green(),
|
||||
Style::new().fg(Color::LightGreen)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().light_yellow(),
|
||||
Style::new().fg(Color::LightYellow)
|
||||
);
|
||||
assert_eq!(Style::new().light_blue(), Style::new().fg(Color::LightBlue));
|
||||
assert_eq!(
|
||||
Style::new().light_magenta(),
|
||||
Style::new().fg(Color::LightMagenta)
|
||||
);
|
||||
assert_eq!(Style::new().light_cyan(), Style::new().fg(Color::LightCyan));
|
||||
assert_eq!(Style::new().white(), Style::new().fg(Color::White));
|
||||
|
||||
// Background colors
|
||||
assert_eq!(Style::new().on_black(), Style::new().bg(Color::Black));
|
||||
assert_eq!(Style::new().on_red(), Style::new().bg(Color::Red));
|
||||
assert_eq!(Style::new().on_green(), Style::new().bg(Color::Green));
|
||||
assert_eq!(Style::new().on_yellow(), Style::new().bg(Color::Yellow));
|
||||
assert_eq!(Style::new().on_blue(), Style::new().bg(Color::Blue));
|
||||
assert_eq!(Style::new().on_magenta(), Style::new().bg(Color::Magenta));
|
||||
assert_eq!(Style::new().on_cyan(), Style::new().bg(Color::Cyan));
|
||||
assert_eq!(Style::new().on_white(), Style::new().bg(Color::White));
|
||||
assert_eq!(Style::new().on_gray(), Style::new().bg(Color::Gray));
|
||||
assert_eq!(
|
||||
Style::new().on_dark_gray(),
|
||||
Style::new().bg(Color::DarkGray)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().on_light_red(),
|
||||
Style::new().bg(Color::LightRed)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().on_light_green(),
|
||||
Style::new().bg(Color::LightGreen)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().on_light_yellow(),
|
||||
Style::new().bg(Color::LightYellow)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().on_light_blue(),
|
||||
Style::new().bg(Color::LightBlue)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().on_light_magenta(),
|
||||
Style::new().bg(Color::LightMagenta)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().on_light_cyan(),
|
||||
Style::new().bg(Color::LightCyan)
|
||||
);
|
||||
assert_eq!(Style::new().on_white(), Style::new().bg(Color::White));
|
||||
|
||||
// Add Modifiers
|
||||
assert_eq!(
|
||||
Style::new().bold(),
|
||||
Style::new().add_modifier(Modifier::BOLD)
|
||||
);
|
||||
assert_eq!(Style::new().dim(), Style::new().add_modifier(Modifier::DIM));
|
||||
assert_eq!(
|
||||
Style::new().italic(),
|
||||
Style::new().add_modifier(Modifier::ITALIC)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().underlined(),
|
||||
Style::new().add_modifier(Modifier::UNDERLINED)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().slow_blink(),
|
||||
Style::new().add_modifier(Modifier::SLOW_BLINK)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().rapid_blink(),
|
||||
Style::new().add_modifier(Modifier::RAPID_BLINK)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().reversed(),
|
||||
Style::new().add_modifier(Modifier::REVERSED)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().hidden(),
|
||||
Style::new().add_modifier(Modifier::HIDDEN)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().crossed_out(),
|
||||
Style::new().add_modifier(Modifier::CROSSED_OUT)
|
||||
);
|
||||
|
||||
// Remove Modifiers
|
||||
assert_eq!(
|
||||
Style::new().not_bold(),
|
||||
Style::new().remove_modifier(Modifier::BOLD)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().not_dim(),
|
||||
Style::new().remove_modifier(Modifier::DIM)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().not_italic(),
|
||||
Style::new().remove_modifier(Modifier::ITALIC)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().not_underlined(),
|
||||
Style::new().remove_modifier(Modifier::UNDERLINED)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().not_slow_blink(),
|
||||
Style::new().remove_modifier(Modifier::SLOW_BLINK)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().not_rapid_blink(),
|
||||
Style::new().remove_modifier(Modifier::RAPID_BLINK)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().not_reversed(),
|
||||
Style::new().remove_modifier(Modifier::REVERSED)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().not_hidden(),
|
||||
Style::new().remove_modifier(Modifier::HIDDEN)
|
||||
);
|
||||
assert_eq!(
|
||||
Style::new().not_crossed_out(),
|
||||
Style::new().remove_modifier(Modifier::CROSSED_OUT)
|
||||
);
|
||||
|
||||
// reset
|
||||
fn reset_can_be_stylized() {
|
||||
assert_eq!(Style::new().reset(), Style::reset());
|
||||
}
|
||||
|
||||
|
||||
@@ -313,16 +313,7 @@ impl FromStr for Color {
|
||||
_ => {
|
||||
if let Ok(index) = s.parse::<u8>() {
|
||||
Self::Indexed(index)
|
||||
} else if let (Ok(r), Ok(g), Ok(b)) = {
|
||||
if !s.starts_with('#') || s.len() != 7 {
|
||||
return Err(ParseColorError);
|
||||
}
|
||||
(
|
||||
u8::from_str_radix(&s[1..3], 16),
|
||||
u8::from_str_radix(&s[3..5], 16),
|
||||
u8::from_str_radix(&s[5..7], 16),
|
||||
)
|
||||
} {
|
||||
} else if let Some((r, g, b)) = parse_hex_color(s) {
|
||||
Self::Rgb(r, g, b)
|
||||
} else {
|
||||
return Err(ParseColorError);
|
||||
@@ -333,6 +324,16 @@ impl FromStr for Color {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_hex_color(input: &str) -> Option<(u8, u8, u8)> {
|
||||
if !input.starts_with('#') || input.len() != 7 {
|
||||
return None;
|
||||
}
|
||||
let r = u8::from_str_radix(input.get(1..3)?, 16).ok()?;
|
||||
let g = u8::from_str_radix(input.get(3..5)?, 16).ok()?;
|
||||
let b = u8::from_str_radix(input.get(5..7)?, 16).ok()?;
|
||||
Some((r, g, b))
|
||||
}
|
||||
|
||||
impl fmt::Display for Color {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
@@ -587,6 +588,7 @@ mod tests {
|
||||
"abcdef0", // 7 chars is not a color
|
||||
" bcdefa", // doesn't start with a '#'
|
||||
"#abcdef00", // too many chars
|
||||
"#1🦀2", // len 7 but on char boundaries shouldnt panic
|
||||
"resett", // typo
|
||||
"lightblackk", // typo
|
||||
];
|
||||
|
||||
@@ -137,9 +137,9 @@ macro_rules! modifier {
|
||||
/// ```
|
||||
pub trait Stylize<'a, T>: Sized {
|
||||
#[must_use = "`bg` returns the modified style without modifying the original"]
|
||||
fn bg(self, color: Color) -> T;
|
||||
fn bg<C: Into<Color>>(self, color: C) -> T;
|
||||
#[must_use = "`fg` returns the modified style without modifying the original"]
|
||||
fn fg<S: Into<Color>>(self, color: S) -> T;
|
||||
fn fg<C: Into<Color>>(self, color: C) -> T;
|
||||
#[must_use = "`reset` returns the modified style without modifying the original"]
|
||||
fn reset(self) -> T;
|
||||
#[must_use = "`add_modifier` returns the modified style without modifying the original"]
|
||||
@@ -179,12 +179,12 @@ impl<'a, T, U> Stylize<'a, T> for U
|
||||
where
|
||||
U: Styled<Item = T>,
|
||||
{
|
||||
fn bg(self, color: Color) -> T {
|
||||
let style = self.style().bg(color);
|
||||
fn bg<C: Into<Color>>(self, color: C) -> T {
|
||||
let style = self.style().bg(color.into());
|
||||
self.set_style(style)
|
||||
}
|
||||
|
||||
fn fg<S: Into<Color>>(self, color: S) -> T {
|
||||
fn fg<C: Into<Color>>(self, color: C) -> T {
|
||||
let style = self.style().fg(color.into());
|
||||
self.set_style(style)
|
||||
}
|
||||
|
||||
@@ -1169,7 +1169,7 @@ mod tests {
|
||||
fn render_truncates_away_from_0x0(#[case] alignment: Alignment, #[case] expected: &str) {
|
||||
let line = Line::from(vec![Span::raw("a🦀b"), Span::raw("c🦀d")]).alignment(alignment);
|
||||
// Fill buffer with stuff to ensure the output is indeed padded
|
||||
let mut buf = Buffer::filled(Rect::new(0, 0, 10, 1), Cell::default().set_symbol("X"));
|
||||
let mut buf = Buffer::filled(Rect::new(0, 0, 10, 1), Cell::new("X"));
|
||||
let area = Rect::new(2, 0, 6, 1);
|
||||
line.render_ref(area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines([expected]));
|
||||
@@ -1188,7 +1188,7 @@ mod tests {
|
||||
let line = Line::from(vec![Span::raw("a🦀b"), Span::raw("c🦀d")]).right_aligned();
|
||||
let area = Rect::new(0, 0, buf_width, 1);
|
||||
// Fill buffer with stuff to ensure the output is indeed padded
|
||||
let mut buf = Buffer::filled(area, Cell::default().set_symbol("X"));
|
||||
let mut buf = Buffer::filled(area, Cell::new("X"));
|
||||
line.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
@@ -245,6 +245,11 @@ pub trait StatefulWidget {
|
||||
/// provided. This is a convenience approach to make it easier to attach child widgets to parent
|
||||
/// widgets. It allows you to render an optional widget by reference.
|
||||
///
|
||||
/// A blanket [implementation of `WidgetRef` for `Fn(Rect, &mut
|
||||
/// Buffer)`](WidgetRef#impl-WidgetRef-for-F) is provided. This allows you to treat any function or
|
||||
/// closure that takes a `Rect` and a mutable reference to a `Buffer` as a widget in situations
|
||||
/// where you don't want to create a new type for a widget.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
@@ -304,13 +309,40 @@ pub trait WidgetRef {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer);
|
||||
}
|
||||
|
||||
/// This allows you to render a widget by reference.
|
||||
// /// This allows you to render a widget by reference.
|
||||
impl<W: WidgetRef> Widget for &W {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// A blanket implementation of `WidgetRef` for `Fn(Rect, &mut Buffer)`.
|
||||
///
|
||||
/// This allows you to treat any function that takes a `Rect` and a mutable reference to a `Buffer`
|
||||
/// as a widget in situations where you don't want to create a new type for a widget.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// fn hello(area: Rect, buf: &mut Buffer) {
|
||||
/// Line::raw("Hello").render(area, buf);
|
||||
/// }
|
||||
///
|
||||
/// fn draw(mut frame: Frame) {
|
||||
/// frame.render_widget(&hello, frame.size());
|
||||
/// frame.render_widget_ref(hello, frame.size());
|
||||
/// }
|
||||
/// ```
|
||||
impl<F> WidgetRef for F
|
||||
where
|
||||
F: Fn(Rect, &mut Buffer),
|
||||
{
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
self(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// A blanket implementation of `WidgetExt` for `Option<W>` where `W` implements `WidgetRef`.
|
||||
///
|
||||
/// This is a convenience implementation that makes it easy to attach child widgets to parent
|
||||
@@ -695,4 +727,42 @@ mod tests {
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
}
|
||||
|
||||
mod function_widget {
|
||||
use super::*;
|
||||
|
||||
fn widget_function(area: Rect, buf: &mut Buffer) {
|
||||
"Hello".render(area, buf);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render(mut buf: Buffer) {
|
||||
widget_function.render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_ref(mut buf: Buffer) {
|
||||
widget_function.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_closure(mut buf: Buffer) {
|
||||
let widget = |area: Rect, buf: &mut Buffer| {
|
||||
"Hello".render(area, buf);
|
||||
};
|
||||
widget.render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_closure_ref(mut buf: Buffer) {
|
||||
let widget = |area: Rect, buf: &mut Buffer| {
|
||||
"Hello".render(area, buf);
|
||||
};
|
||||
widget.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -963,11 +963,7 @@ mod tests {
|
||||
expected.get_mut(x, 1).set_fg(Color::Yellow);
|
||||
}
|
||||
|
||||
let expected_color = if let Some(color) = bar_color {
|
||||
color
|
||||
} else {
|
||||
Color::Yellow
|
||||
};
|
||||
let expected_color = bar_color.unwrap_or(Color::Yellow);
|
||||
|
||||
// second line contains the word "label". Since the bar value is 2,
|
||||
// then the first 2 characters of "label" are italic red.
|
||||
|
||||
@@ -168,7 +168,7 @@ impl<'a> Block<'a> {
|
||||
border_style: Style::new(),
|
||||
border_set: BorderType::Plain.to_border_set(),
|
||||
style: Style::new(),
|
||||
padding: Padding::zero(),
|
||||
padding: Padding::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,6 +469,38 @@ impl<'a> Block<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the padding inside a `Block`.
|
||||
///
|
||||
/// See [`Padding`] for more information.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// This renders a `Block` with no padding (the default).
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// Block::bordered().padding(Padding::ZERO);
|
||||
/// // Renders
|
||||
/// // ┌───────┐
|
||||
/// // │content│
|
||||
/// // └───────┘
|
||||
/// ```
|
||||
///
|
||||
/// This example shows a `Block` with padding left and right ([`Padding::horizontal`]).
|
||||
/// Notice the two spaces before and after the content.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// Block::bordered().padding(Padding::horizontal(2));
|
||||
/// // Renders
|
||||
/// // ┌───────────┐
|
||||
/// // │ content │
|
||||
/// // └───────────┘
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn padding(mut self, padding: Padding) -> Self {
|
||||
self.padding = padding;
|
||||
self
|
||||
}
|
||||
|
||||
/// Compute the inner area of a block based on its border visibility rules.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -529,38 +561,6 @@ impl<'a> Block<'a> {
|
||||
.iter()
|
||||
.any(|title| title.position.unwrap_or(self.titles_position) == position)
|
||||
}
|
||||
|
||||
/// Defines the padding inside a `Block`.
|
||||
///
|
||||
/// See [`Padding`] for more information.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// This renders a `Block` with no padding (the default).
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// Block::bordered().padding(Padding::zero());
|
||||
/// // Renders
|
||||
/// // ┌───────┐
|
||||
/// // │content│
|
||||
/// // └───────┘
|
||||
/// ```
|
||||
///
|
||||
/// This example shows a `Block` with padding left and right ([`Padding::horizontal`]).
|
||||
/// Notice the two spaces before and after the content.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// Block::bordered().padding(Padding::horizontal(2));
|
||||
/// // Renders
|
||||
/// // ┌───────────┐
|
||||
/// // │ content │
|
||||
/// // └───────────┘
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn padding(mut self, padding: Padding) -> Self {
|
||||
self.padding = padding;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl BorderType {
|
||||
@@ -997,7 +997,7 @@ mod tests {
|
||||
border_style: Style::new(),
|
||||
border_set: BorderType::Plain.to_border_set(),
|
||||
style: Style::new(),
|
||||
padding: Padding::zero(),
|
||||
padding: Padding::ZERO,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,14 @@ pub struct Padding {
|
||||
}
|
||||
|
||||
impl Padding {
|
||||
/// `Padding` with all fields set to `0`
|
||||
pub const ZERO: Self = Self {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
};
|
||||
|
||||
/// Creates a new `Padding` by specifying every field individually.
|
||||
///
|
||||
/// Note: the order of the fields does not match the order of the CSS properties.
|
||||
@@ -48,13 +56,9 @@ impl Padding {
|
||||
}
|
||||
|
||||
/// Creates a `Padding` with all fields set to `0`.
|
||||
#[deprecated = "use Padding::ZERO"]
|
||||
pub const fn zero() -> Self {
|
||||
Self {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
}
|
||||
Self::ZERO
|
||||
}
|
||||
|
||||
/// Creates a `Padding` with the same value for `left` and `right`.
|
||||
@@ -173,7 +177,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn constructors() {
|
||||
assert_eq!(Padding::zero(), Padding::new(0, 0, 0, 0));
|
||||
assert_eq!(Padding::horizontal(1), Padding::new(1, 1, 0, 0));
|
||||
assert_eq!(Padding::vertical(1), Padding::new(0, 0, 1, 1));
|
||||
assert_eq!(Padding::uniform(1), Padding::new(1, 1, 1, 1));
|
||||
@@ -189,7 +192,6 @@ mod tests {
|
||||
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();
|
||||
const _HORIZONTAL: Padding = Padding::horizontal(1);
|
||||
const _VERTICAL: Padding = Padding::vertical(1);
|
||||
const _PROPORTIONAL: Padding = Padding::proportional(1);
|
||||
|
||||
@@ -203,7 +203,7 @@ impl CalendarEventStore {
|
||||
let mut res = Self::default();
|
||||
res.add(
|
||||
OffsetDateTime::now_local()
|
||||
.unwrap_or(OffsetDateTime::now_utc())
|
||||
.unwrap_or_else(|_| OffsetDateTime::now_utc())
|
||||
.date(),
|
||||
style.into(),
|
||||
);
|
||||
|
||||
@@ -817,9 +817,7 @@ mod tests {
|
||||
// results in the expected output
|
||||
fn test_marker(marker: Marker, expected: &str) {
|
||||
let area = Rect::new(0, 0, 5, 5);
|
||||
let mut cell = Cell::default();
|
||||
cell.set_char('x');
|
||||
let mut buf = Buffer::filled(area, &cell);
|
||||
let mut buf = Buffer::filled(area, Cell::new("x"));
|
||||
let horizontal_line = Line {
|
||||
x1: 0.0,
|
||||
y1: 0.0,
|
||||
|
||||
@@ -98,7 +98,7 @@ mod tests {
|
||||
"██████████",
|
||||
]);
|
||||
expected.set_style(buffer.area, Style::new().red());
|
||||
expected.set_style(buffer.area.inner(&Margin::new(1, 1)), Style::reset());
|
||||
expected.set_style(buffer.area.inner(Margin::new(1, 1)), Style::reset());
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
@@ -132,8 +132,8 @@ mod tests {
|
||||
"█▄▄▄▄▄▄▄▄█",
|
||||
]);
|
||||
expected.set_style(buffer.area, Style::new().red().on_red());
|
||||
expected.set_style(buffer.area.inner(&Margin::new(1, 0)), Style::reset().red());
|
||||
expected.set_style(buffer.area.inner(&Margin::new(1, 1)), Style::reset());
|
||||
expected.set_style(buffer.area.inner(Margin::new(1, 0)), Style::reset().red());
|
||||
expected.set_style(buffer.area.inner(Margin::new(1, 1)), Style::reset());
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
@@ -176,8 +176,8 @@ mod tests {
|
||||
"⣇⣀⣀⣀⣀⣀⣀⣀⣀⣸",
|
||||
]);
|
||||
expected.set_style(buffer.area, Style::new().red());
|
||||
expected.set_style(buffer.area.inner(&Margin::new(1, 1)), Style::new().green());
|
||||
expected.set_style(buffer.area.inner(&Margin::new(2, 2)), Style::reset());
|
||||
expected.set_style(buffer.area.inner(Margin::new(1, 1)), Style::new().green());
|
||||
expected.set_style(buffer.area.inner(Margin::new(2, 2)), Style::reset());
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ use crate::{prelude::*, widgets::Block};
|
||||
/// A `Gauge` renders a bar filled according to the value given to [`Gauge::percent`] or
|
||||
/// [`Gauge::ratio`]. The bar width and height are defined by the [`Rect`] it is
|
||||
/// [rendered](Widget::render) in.
|
||||
///
|
||||
/// The associated label is always centered horizontally and vertically. If not set with
|
||||
/// [`Gauge::label`], the label is the percentage of the bar filled.
|
||||
///
|
||||
/// You might want to have a higher precision bar using [`Gauge::use_unicode`].
|
||||
///
|
||||
/// This can be useful to indicate the progression of a task, like a download.
|
||||
@@ -31,7 +33,7 @@ use crate::{prelude::*, widgets::Block};
|
||||
///
|
||||
/// - [`LineGauge`] for a thin progress bar
|
||||
#[allow(clippy::struct_field_names)] // gauge_style needs to be differentiated to style
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct Gauge<'a> {
|
||||
block: Option<Block<'a>>,
|
||||
ratio: f64,
|
||||
@@ -41,19 +43,6 @@ pub struct Gauge<'a> {
|
||||
gauge_style: Style,
|
||||
}
|
||||
|
||||
impl<'a> Default for Gauge<'a> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
block: None,
|
||||
ratio: 0.0,
|
||||
label: None,
|
||||
use_unicode: false,
|
||||
style: Style::default(),
|
||||
gauge_style: Style::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Gauge<'a> {
|
||||
/// Surrounds the `Gauge` with a [`Block`].
|
||||
///
|
||||
@@ -235,14 +224,20 @@ fn get_unicode_block<'a>(frac: f64) -> &'a str {
|
||||
|
||||
/// A compact widget to display a progress bar over a single thin line.
|
||||
///
|
||||
/// This can be useful to indicate the progression of a task, like a download.
|
||||
///
|
||||
/// A `LineGauge` renders a thin line filled according to the value given to [`LineGauge::ratio`].
|
||||
/// Unlike [`Gauge`], only the width can be defined by the [rendering](Widget::render) [`Rect`].
|
||||
/// The height is always 1.
|
||||
/// The associated label is always left-aligned. If not set with [`LineGauge::label`], the label
|
||||
/// is the percentage of the bar filled.
|
||||
/// Unlike [`Gauge`], only the width can be defined by the [rendering](Widget::render) [`Rect`]. The
|
||||
/// height is always 1.
|
||||
///
|
||||
/// The associated label is always left-aligned. If not set with [`LineGauge::label`], the label is
|
||||
/// the percentage of the bar filled.
|
||||
///
|
||||
/// You can also set the symbols used to draw the bar with [`LineGauge::line_set`].
|
||||
///
|
||||
/// This can be useful to indicate the progression of a task, like a download.
|
||||
/// To style the gauge line use [`LineGauge::filled_style`] and [`LineGauge::unfilled_style`] which
|
||||
/// let you pick a color for foreground (i.e. line) and background of the filled and unfilled part
|
||||
/// of gauge respectively.
|
||||
///
|
||||
/// # Examples:
|
||||
///
|
||||
@@ -251,7 +246,7 @@ fn get_unicode_block<'a>(frac: f64) -> &'a str {
|
||||
///
|
||||
/// LineGauge::default()
|
||||
/// .block(Block::bordered().title("Progress"))
|
||||
/// .gauge_style(
|
||||
/// .filled_style(
|
||||
/// Style::default()
|
||||
/// .fg(Color::White)
|
||||
/// .bg(Color::Black)
|
||||
@@ -271,7 +266,8 @@ pub struct LineGauge<'a> {
|
||||
label: Option<Line<'a>>,
|
||||
line_set: symbols::line::Set,
|
||||
style: Style,
|
||||
gauge_style: Style,
|
||||
filled_style: Style,
|
||||
unfilled_style: Style,
|
||||
}
|
||||
|
||||
impl<'a> LineGauge<'a> {
|
||||
@@ -343,9 +339,40 @@ impl<'a> LineGauge<'a> {
|
||||
///
|
||||
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
#[deprecated(
|
||||
since = "0.27.0",
|
||||
note = "You should use `LineGauge::filled_style` instead."
|
||||
)]
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn gauge_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.gauge_style = style.into();
|
||||
let style: Style = style.into();
|
||||
|
||||
// maintain backward compatibility, which used the background color of the style as the
|
||||
// unfilled part of the gauge and the foreground color as the filled part of the gauge
|
||||
let filled_color = style.fg.unwrap_or(Color::Reset);
|
||||
let unfilled_color = style.bg.unwrap_or(Color::Reset);
|
||||
self.filled_style = style.fg(filled_color).bg(Color::Reset);
|
||||
self.unfilled_style = style.fg(unfilled_color).bg(Color::Reset);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of filled part of the bar.
|
||||
///
|
||||
/// `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 filled_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.filled_style = style.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the unfilled part of the bar.
|
||||
///
|
||||
/// `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 unfilled_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
self.unfilled_style = style.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -379,26 +406,12 @@ impl WidgetRef for LineGauge<'_> {
|
||||
for col in start..end {
|
||||
buf.get_mut(col, row)
|
||||
.set_symbol(self.line_set.horizontal)
|
||||
.set_style(Style {
|
||||
fg: self.gauge_style.fg,
|
||||
bg: None,
|
||||
#[cfg(feature = "underline-color")]
|
||||
underline_color: self.gauge_style.underline_color,
|
||||
add_modifier: self.gauge_style.add_modifier,
|
||||
sub_modifier: self.gauge_style.sub_modifier,
|
||||
});
|
||||
.set_style(self.filled_style);
|
||||
}
|
||||
for col in end..gauge_area.right() {
|
||||
buf.get_mut(col, row)
|
||||
.set_symbol(self.line_set.horizontal)
|
||||
.set_style(Style {
|
||||
fg: self.gauge_style.bg,
|
||||
bg: None,
|
||||
#[cfg(feature = "underline-color")]
|
||||
underline_color: self.gauge_style.underline_color,
|
||||
add_modifier: self.gauge_style.add_modifier,
|
||||
sub_modifier: self.gauge_style.sub_modifier,
|
||||
});
|
||||
.set_style(self.unfilled_style);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -478,24 +491,36 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
#[test]
|
||||
fn line_gauge_can_be_stylized_with_deprecated_gauge_style() {
|
||||
let gauge =
|
||||
LineGauge::default().gauge_style(Style::default().fg(Color::Red).bg(Color::Blue));
|
||||
|
||||
assert_eq!(
|
||||
gauge.filled_style,
|
||||
Style::default().fg(Color::Red).bg(Color::Reset)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
gauge.unfilled_style,
|
||||
Style::default().fg(Color::Blue).bg(Color::Reset)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_gauge_default() {
|
||||
// TODO: replace to `assert_eq!(LineGauge::default(), LineGauge::default())`
|
||||
// when `Eq` or `PartialEq` is implemented for `LineGauge`.
|
||||
assert_eq!(
|
||||
format!("{:?}", LineGauge::default()),
|
||||
format!(
|
||||
"{:?}",
|
||||
LineGauge {
|
||||
block: None,
|
||||
ratio: 0.0,
|
||||
label: None,
|
||||
style: Style::default(),
|
||||
line_set: symbols::line::NORMAL,
|
||||
gauge_style: Style::default(),
|
||||
}
|
||||
),
|
||||
"LineGauge::default() should have correct default values."
|
||||
LineGauge::default(),
|
||||
LineGauge {
|
||||
block: None,
|
||||
ratio: 0.0,
|
||||
label: None,
|
||||
style: Style::default(),
|
||||
line_set: symbols::line::NORMAL,
|
||||
filled_style: Style::default(),
|
||||
unfilled_style: Style::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,48 +706,6 @@ impl<'a> List<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the list direction (up or down)
|
||||
///
|
||||
/// Defines if the `List` is displayed *top to bottom* (default) or *bottom to top*. Use
|
||||
/// [`Corner::BottomLeft`] to go *bottom to top*. **Any** other variant will go *top to bottom*.
|
||||
/// If there is too few items to fill the screen, the list will stick to the starting edge.
|
||||
///
|
||||
/// This is set to [`Corner::TopLeft`] by default.
|
||||
///
|
||||
/// This is a fluent setter method which must be chained or used as it consumes self
|
||||
///
|
||||
/// ## Note
|
||||
///
|
||||
/// Despite its name, this method doesn't change the horizontal alignment, i.e. the `List`
|
||||
/// **won't** start in a corner.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Same as default, i.e. *top to bottom*. Despite the name implying otherwise.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = ["Item 1"];
|
||||
/// let list = List::new(items).start_corner(Corner::BottomRight);
|
||||
/// ```
|
||||
///
|
||||
/// Bottom to top
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = ["Item 1"];
|
||||
/// let list = List::new(items).start_corner(Corner::BottomLeft);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
#[deprecated(since = "0.25.0", note = "You should use `List::direction` instead.")]
|
||||
pub fn start_corner(self, corner: Corner) -> Self {
|
||||
if corner == Corner::BottomLeft {
|
||||
self.direction(ListDirection::BottomToTop)
|
||||
} else {
|
||||
self.direction(ListDirection::TopToBottom)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of [`ListItem`]s in the list
|
||||
pub fn len(&self) -> usize {
|
||||
self.items.len()
|
||||
@@ -1691,30 +1649,6 @@ mod tests {
|
||||
assert_eq!(buffer, Buffer::with_lines(expected));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::topleft(Corner::TopLeft, [
|
||||
"Item 0 ",
|
||||
"Item 1 ",
|
||||
"Item 2 ",
|
||||
" ",
|
||||
])]
|
||||
#[case::bottomleft(Corner::BottomLeft, [
|
||||
" ",
|
||||
"Item 2 ",
|
||||
"Item 1 ",
|
||||
"Item 0 ",
|
||||
])]
|
||||
fn start_corner<'line, Lines>(#[case] corner: Corner, #[case] expected: Lines)
|
||||
where
|
||||
Lines: IntoIterator,
|
||||
Lines::Item: Into<Line<'line>>,
|
||||
{
|
||||
#[allow(deprecated)] // For start_corner
|
||||
let list = List::new(["Item 0", "Item 1", "Item 2"]).start_corner(corner);
|
||||
let buffer = render_widget(list, 10, 4);
|
||||
assert_eq!(buffer, Buffer::with_lines(expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_truncate_items() {
|
||||
let list = List::new(["Item 0", "Item 1", "Item 2", "Item 3", "Item 4"]);
|
||||
|
||||
@@ -3,7 +3,10 @@ use unicode_width::UnicodeWidthStr;
|
||||
use crate::{
|
||||
prelude::*,
|
||||
text::StyledGrapheme,
|
||||
widgets::{reflow::*, Block},
|
||||
widgets::{
|
||||
reflow::{LineComposer, LineTruncator, WordWrapper, WrappedLine},
|
||||
Block,
|
||||
},
|
||||
};
|
||||
|
||||
const fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment) -> u16 {
|
||||
|
||||
@@ -336,6 +336,7 @@ fn trim_offset(src: &str, mut offset: usize) -> &str {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#[allow(clippy::string_slice)] // Is safe as it comes from UnicodeSegmentation
|
||||
&src[start..]
|
||||
}
|
||||
|
||||
@@ -431,17 +432,17 @@ mod test {
|
||||
let (line_truncator, _, _) = run_composer(Composer::LineTruncator, text, width as u16);
|
||||
|
||||
let wrapped = vec![
|
||||
&text[..width],
|
||||
&text[width..width * 2],
|
||||
&text[width * 2..width * 3],
|
||||
&text[width * 3..],
|
||||
text.get(..width).unwrap(),
|
||||
text.get(width..width * 2).unwrap(),
|
||||
text.get(width * 2..width * 3).unwrap(),
|
||||
text.get(width * 3..).unwrap(),
|
||||
];
|
||||
assert_eq!(
|
||||
word_wrapper, wrapped,
|
||||
"WordWrapper should detect the line cannot be broken on word boundary and \
|
||||
break it at line width limit."
|
||||
);
|
||||
assert_eq!(line_truncator, vec![&text[..width]]);
|
||||
assert_eq!(line_truncator, [text.get(..width).unwrap()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -471,7 +472,7 @@ mod test {
|
||||
assert_eq!(word_wrapper_single_space, word_wrapped);
|
||||
assert_eq!(word_wrapper_multi_space, word_wrapped);
|
||||
|
||||
assert_eq!(line_truncator, vec![&text[..width]]);
|
||||
assert_eq!(line_truncator, [text.get(..width).unwrap()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_precision_loss,
|
||||
clippy::cast_sign_loss,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::wildcard_imports
|
||||
clippy::module_name_repetitions
|
||||
)]
|
||||
|
||||
use std::iter;
|
||||
@@ -12,7 +11,10 @@ use std::iter;
|
||||
use strum::{Display, EnumString};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{prelude::*, symbols::scrollbar::*};
|
||||
use crate::{
|
||||
prelude::*,
|
||||
symbols::scrollbar::{Set, DOUBLE_HORIZONTAL, DOUBLE_VERTICAL},
|
||||
};
|
||||
|
||||
/// A widget to display a scrollbar
|
||||
///
|
||||
@@ -63,7 +65,7 @@ use crate::{prelude::*, symbols::scrollbar::*};
|
||||
/// // and the scrollbar, those are separate widgets
|
||||
/// frame.render_stateful_widget(
|
||||
/// scrollbar,
|
||||
/// area.inner(&Margin {
|
||||
/// area.inner(Margin {
|
||||
/// // using an inner vertical margin of 1 unit makes the scrollbar inside the block
|
||||
/// vertical: 1,
|
||||
/// horizontal: 0,
|
||||
@@ -132,7 +134,7 @@ pub enum ScrollbarOrientation {
|
||||
///
|
||||
/// If you don't have multi-line content, you can leave the `viewport_content_length` set to the
|
||||
/// default and it'll use the track size as a `viewport_content_length`.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ScrollbarState {
|
||||
/// The total length of the scrollable content.
|
||||
@@ -391,12 +393,6 @@ impl<'a> Scrollbar<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ScrollbarState {
|
||||
fn default() -> Self {
|
||||
Self::new(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollbarState {
|
||||
/// Constructs a new [`ScrollbarState`] with the specified content length.
|
||||
///
|
||||
|
||||
@@ -30,7 +30,7 @@ use crate::{prelude::*, widgets::Block};
|
||||
/// .direction(RenderDirection::RightToLeft)
|
||||
/// .style(Style::default().red().on_white());
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
||||
pub struct Sparkline<'a> {
|
||||
/// A block to wrap the widget in
|
||||
block: Option<Block<'a>>,
|
||||
@@ -59,19 +59,6 @@ pub enum RenderDirection {
|
||||
RightToLeft,
|
||||
}
|
||||
|
||||
impl<'a> Default for Sparkline<'a> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
block: None,
|
||||
style: Style::default(),
|
||||
data: &[],
|
||||
max: None,
|
||||
bar_set: symbols::bar::NINE_LEVELS,
|
||||
direction: RenderDirection::LeftToRight,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sparkline<'a> {
|
||||
/// Wraps the sparkline with the given `block`.
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -172,10 +159,9 @@ impl Sparkline<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
let max = match self.max {
|
||||
Some(v) => v,
|
||||
None => *self.data.iter().max().unwrap_or(&1),
|
||||
};
|
||||
let max = self
|
||||
.max
|
||||
.unwrap_or_else(|| *self.data.iter().max().unwrap_or(&1));
|
||||
let max_index = min(spark_area.width as usize, self.data.len());
|
||||
let mut data = self
|
||||
.data
|
||||
@@ -253,9 +239,7 @@ mod tests {
|
||||
// filled with x symbols to make it easier to assert on the result
|
||||
fn render(widget: Sparkline, width: u16) -> Buffer {
|
||||
let area = Rect::new(0, 0, width, 1);
|
||||
let mut cell = Cell::default();
|
||||
cell.set_symbol("x");
|
||||
let mut buffer = Buffer::filled(area, &cell);
|
||||
let mut buffer = Buffer::filled(area, Cell::new("x"));
|
||||
widget.render(area, &mut buffer);
|
||||
buffer
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::Cell;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A single row of data to be displayed in a [`Table`] widget.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::*;
|
||||
#[allow(unused_imports)] // `Cell` is used in the doc comment but not the code
|
||||
use super::Cell;
|
||||
use super::{HighlightSpacing, Row, TableState};
|
||||
use crate::{layout::Flex, prelude::*, widgets::Block};
|
||||
|
||||
/// A widget to display data in formatted columns.
|
||||
@@ -176,7 +178,11 @@ use crate::{layout::Flex, prelude::*, widgets::Block};
|
||||
/// Row::new(vec!["Row21", "Row22", "Row23"]),
|
||||
/// Row::new(vec!["Row31", "Row32", "Row33"]),
|
||||
/// ];
|
||||
/// let widths = [Constraint::Length(5), Constraint::Length(5), Constraint::Length(10)];
|
||||
/// let widths = [
|
||||
/// Constraint::Length(5),
|
||||
/// Constraint::Length(5),
|
||||
/// Constraint::Length(10),
|
||||
/// ];
|
||||
/// let table = Table::new(rows, widths)
|
||||
/// .block(Block::new().title("Table"))
|
||||
/// .highlight_style(Style::new().add_modifier(Modifier::REVERSED))
|
||||
@@ -184,6 +190,7 @@ use crate::{layout::Flex, prelude::*, widgets::Block};
|
||||
///
|
||||
/// frame.render_stateful_widget(table, area, &mut table_state);
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Table<'a> {
|
||||
/// Data to display in each row
|
||||
@@ -842,7 +849,7 @@ mod tests {
|
||||
use std::vec;
|
||||
|
||||
use super::*;
|
||||
use crate::{layout::Constraint::*, style::Style, text::Line};
|
||||
use crate::{layout::Constraint::*, style::Style, text::Line, widgets::Cell};
|
||||
|
||||
#[test]
|
||||
fn new() {
|
||||
|
||||
@@ -30,7 +30,7 @@ fn backend_termion_should_only_write_diffs() -> Result<(), Box<dyn std::error::E
|
||||
}
|
||||
|
||||
let expected = {
|
||||
use termion::{color, cursor, style};
|
||||
use ratatui::termion::{color, cursor, style};
|
||||
let mut s = String::new();
|
||||
// First draw
|
||||
write!(s, "{}", cursor::Goto(1, 1))?;
|
||||
|
||||
@@ -180,7 +180,8 @@ fn widgets_line_gauge_renders() {
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let gauge = LineGauge::default()
|
||||
.gauge_style(Style::default().fg(Color::Green).bg(Color::White))
|
||||
.filled_style(Style::default().fg(Color::Green))
|
||||
.unfilled_style(Style::default().fg(Color::White))
|
||||
.ratio(0.43);
|
||||
f.render_widget(
|
||||
gauge,
|
||||
@@ -193,7 +194,7 @@ fn widgets_line_gauge_renders() {
|
||||
);
|
||||
let gauge = LineGauge::default()
|
||||
.block(Block::bordered().title("Gauge 2"))
|
||||
.gauge_style(Style::default().fg(Color::Green))
|
||||
.filled_style(Style::default().fg(Color::Green))
|
||||
.line_set(symbols::line::THICK)
|
||||
.ratio(0.211_313_934_313_1);
|
||||
f.render_widget(
|
||||
|
||||
Reference in New Issue
Block a user