Compare commits

..

5 Commits

Author SHA1 Message Date
Josh McKinney
2fe946b3e5 fix: alternate colors more nicely 2024-09-04 12:21:10 -07:00
Josh McKinney
b5f2c0cef3 fix: use once_cell::lazy instead of LazyLock to avoid MSRV 1.80
Fix ordering of histogram lines in metrics example
Fix clippy lints
2024-09-04 00:39:11 -07:00
Josh McKinney
3b132e2768 fix: display 2024-09-03 17:35:40 -07:00
Josh McKinney
904c950c59 refactor: add start_timing() guard method
Removes nesting
2024-09-03 16:55:24 -07:00
Josh McKinney
5673bb5039 feat: add metrics (wip)
Adds a variety of metrics to the terminal and backend modules.

Run the metrics example to see the metrics in action.

```shell
cargo run --example metrics
cargo run --example metrics --release
```
2024-09-03 16:31:43 -07:00
244 changed files with 7205 additions and 17123 deletions

View File

@@ -1,2 +0,0 @@
[alias]
xtask = "run --package xtask --"

2
.github/FUNDING.yml vendored
View File

@@ -1,2 +0,0 @@
github: ratatui
open_collective: ratatui

View File

@@ -18,22 +18,43 @@ jobs:
PR_EVENT: event.json
steps:
- name: Download Benchmark Results
uses: dawidd6/action-download-artifact@v6
uses: actions/github-script@v7
with:
name: ${{ env.BENCHMARK_RESULTS }}
run_id: ${{ github.event.workflow_run.id }}
- name: Download PR Event
uses: dawidd6/action-download-artifact@v6
with:
name: ${{ env.PR_EVENT }}
run_id: ${{ github.event.workflow_run.id }}
script: |
async function downloadArtifact(artifactName) {
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == artifactName
})[0];
if (!matchArtifact) {
core.setFailed(`Failed to find artifact: ${artifactName}`);
}
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/${artifactName}.zip`, Buffer.from(download.data));
}
await downloadArtifact(process.env.BENCHMARK_RESULTS);
await downloadArtifact(process.env.PR_EVENT);
- name: Unzip Benchmark Results
run: |
unzip $BENCHMARK_RESULTS.zip
unzip $PR_EVENT.zip
- name: Export PR Event Data
uses: actions/github-script@v7
with:
script: |
let fs = require('fs');
let prEvent = JSON.parse(fs.readFileSync(process.env.PR_EVENT, {encoding: 'utf8'}));
core.exportVariable("PR_HEAD", prEvent.pull_request.head.ref);
core.exportVariable("PR_HEAD", `${prEvent.number}/merge`);
core.exportVariable("PR_BASE", prEvent.pull_request.base.ref);
core.exportVariable("PR_BASE_SHA", prEvent.pull_request.base.sha);
core.exportVariable("PR_NUMBER", prEvent.number);
@@ -43,14 +64,12 @@ jobs:
bencher run \
--project ratatui-org \
--token '${{ secrets.BENCHER_API_TOKEN }}' \
--branch "$PR_HEAD" \
--start-point "$PR_BASE" \
--start-point-hash "$PR_BASE_SHA" \
--start-point-clone-thresholds \
--start-point-reset \
--branch '${{ env.PR_HEAD }}' \
--branch-start-point '${{ env.PR_BASE }}' \
--branch-start-point-hash '${{ env.PR_BASE_SHA }}' \
--testbed ubuntu-latest \
--adapter rust_criterion \
--err \
--github-actions '${{ secrets.GITHUB_TOKEN }}' \
--ci-number "$PR_NUMBER" \
--file "$BENCHMARK_RESULTS"
--ci-number '${{ env.PR_NUMBER }}' \
--file "$BENCHMARK_RESULTS"

View File

@@ -9,6 +9,7 @@ on:
pull_request:
branches:
- main
merge_group:
# ensure that the workflow is only triggered once per PR, subsequent pushes to the PR will cancel
# and restart the workflow. See https://docs.github.com/en/actions/using-jobs/using-concurrency
@@ -20,88 +21,66 @@ concurrency:
# typos, and missing tests as early as possible. This allows us to fix these and resubmit the PR
# without having to wait for the comprehensive matrix of tests to complete.
jobs:
# Lint the formatting of the codebase.
lint-formatting:
name: Check Formatting
rustfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
with: { components: rustfmt }
- run: cargo xtask lint-formatting
with:
components: rustfmt
- run: cargo +nightly fmt --all --check
# Check for typos in the codebase.
# See <https://github.com/crate-ci/typos/>
lint-typos:
name: Check Typos
typos:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: crate-ci/typos@master
# Check for any disallowed dependencies in the codebase due to license / security issues.
# See <https://github.com/EmbarkStudios/cargo-deny>
dependencies:
name: Check Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v2
# Check for any unused dependencies in the codebase.
# See <https://github.com/bnjbvr/cargo-machete/>
cargo-machete:
name: Check Unused Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- uses: bnjbvr/cargo-machete@v0.7.0
# Run cargo clippy.
lint-clippy:
name: Check Clippy
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with: { components: clippy }
with:
components: clippy
- uses: taiki-e/install-action@cargo-make
- uses: Swatinem/rust-cache@v2
- run: cargo xtask lint-clippy
- run: cargo make clippy
# Run markdownlint on all markdown files in the repository.
lint-markdown:
name: Check Markdown
markdownlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DavidAnson/markdownlint-cli2-action@v18
- uses: DavidAnson/markdownlint-cli2-action@v16
with:
globs: |
'**/*.md'
'!target'
# Run cargo coverage. This will generate a coverage report and upload it to codecov.
# <https://app.codecov.io/gh/ratatui/ratatui>
coverage:
name: Coverage Report
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools
- uses: taiki-e/install-action@cargo-llvm-cov
- uses: taiki-e/install-action@v2
with:
tool: cargo-llvm-cov,cargo-make
- uses: Swatinem/rust-cache@v2
- run: cargo xtask coverage
- uses: codecov/codecov-action@v5
- run: cargo make coverage
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
# Run cargo check. This is a fast way to catch any obvious errors in the code.
check:
name: Check ${{ matrix.os }} ${{ matrix.toolchain }}
strategy:
fail-fast: false
matrix:
@@ -113,23 +92,13 @@ jobs:
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.toolchain }}
- uses: taiki-e/install-action@cargo-make
- uses: Swatinem/rust-cache@v2
- run: cargo xtask check
- run: cargo make check
env:
RUST_BACKTRACE: full
# Check if README.md is up-to-date with the crate's documentation.
check-readme:
name: Check README
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@cargo-rdme
- run: cargo xtask check-readme
# Run cargo rustdoc with the same options that would be used by docs.rs, taking into account the
# package.metadata.docs.rs configured in Cargo.toml. https://github.com/dtolnay/cargo-docs-rs
lint-docs:
name: Check Docs
runs-on: ubuntu-latest
env:
RUSTDOCFLAGS: -Dwarnings
@@ -138,48 +107,47 @@ jobs:
- uses: dtolnay/rust-toolchain@nightly
- uses: dtolnay/install@cargo-docs-rs
- uses: Swatinem/rust-cache@v2
- run: cargo xtask lint-docs
# Run cargo rustdoc with the same options that would be used by docs.rs, taking into account
# the package.metadata.docs.rs configured in Cargo.toml.
# https://github.com/dtolnay/cargo-docs-rs
- run: cargo +nightly docs-rs
# Run cargo test on the documentation of the crate. This will catch any code examples that don't
# compile, or any other issues in the documentation.
test-docs:
name: Test Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo xtask test-docs
# Run cargo test on the libraries of the crate.
test-libs:
name: Test Libs ${{ matrix.toolchain }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
toolchain: ["1.74.0", "stable"]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo xtask test-libs
# Run cargo test on all the backends.
test-backends:
name: Test ${{matrix.backend}} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
test-doc:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@cargo-make
- uses: Swatinem/rust-cache@v2
- run: cargo make test-doc
env:
RUST_BACKTRACE: full
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
toolchain: ["1.74.0", "stable"]
backend: [crossterm, termion, termwiz]
exclude:
# termion is not supported on windows
- os: windows-latest
backend: termion
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.toolchain }}
- uses: taiki-e/install-action@v2
with:
tool: cargo-make,nextest
- uses: Swatinem/rust-cache@v2
- run: cargo xtask test-backend ${{ matrix.backend }}
- run: cargo make test-backend ${{ matrix.backend }}
env:
RUST_BACKTRACE: full

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
target
Cargo.lock
*.log
*.rs.rustfmt
.gdb_history

View File

@@ -10,17 +10,8 @@ GitHub with a [breaking change] label.
This is a quick summary of the sections below:
- [Unreleased](#unreleased)
- The `From` impls for backend types are now replaced with more specific traits
- [v0.29.0](#v0290)
- `Sparkline::data` takes `IntoIterator<Item = SparklineBar>` instead of `&[u64]` and is no longer const
- Removed public fields from `Rect` iterators
- `Line` now implements `From<Cow<str>`
- `Table::highlight_style` is now `Table::row_highlight_style`
- `Tabs::select` now accepts `Into<Option<usize>>`
- `Color::from_hsl` is now behind the `palette` feature
- [v0.28.0](#v0280)
- `Backend::size` returns `Size` instead of `Rect`
`Backend::size` returns `Size` instead of `Rect`
- `Backend` trait migrates to `get/set_cursor_position`
- Ratatui now requires Crossterm 0.28.0
- `Axis::labels` now accepts `IntoIterator<Into<Line>>`
@@ -74,189 +65,7 @@ This is a quick summary of the sections below:
- MSRV is now 1.63.0
- `List` no longer ignores empty strings
## Unreleased (0.30.0)
### `WidgetRef` no longer has a blanket implementation of Widget
Previously there was a blanket implementation of Widget for WidgetRef. This has been reversed to
instead be a blanket implementation of WidgetRef for all &W where W: Widget. Any widgets that
previously implemented WidgetRef directly should now instead implement Widget for a reference to the
type.
```diff
-impl WidgetRef for Foo {
- fn render_ref(&self, area: Rect, buf: &mut Buffer)
+impl Widget for &Foo {
+ fn render(self, area: Rect, buf: &mut Buffer)
}
```
### The `From` impls for backend types are now replaced with more specific traits [#1464]
[#1464]: https://github.com/ratatui/ratatui/pull/1464
Crossterm gains `ratatui::backend::crossterm::{FromCrossterm, IntoCrossterm}`
Termwiz gains `ratatui::backend::termwiz::{FromTermwiz, IntoTermwiz}`
This is necessary in order to avoid the orphan rule when implementing `From` for crossterm types
once the crossterm types are moved to a separate crate.
```diff
+ use ratatui::backend::crossterm::{FromCrossterm, IntoCrossterm};
let crossterm_color = crossterm::style::Color::Black;
- let ratatui_color = crossterm_color.into();
- let ratatui_color = ratatui::style::Color::from(crossterm_color);
+ let ratatui_color = ratatui::style::Color::from_crossterm(crossterm_color);
- let crossterm_color = ratatui_color.into();
- let crossterm_color = crossterm::style::Color::from(ratatui_color);
+ let crossterm_color = ratatui_color.into_crossterm();
let crossterm_attribute = crossterm::style::types::Attribute::Bold;
- let ratatui_modifier = crossterm_attribute.into();
- let ratatui_modifier = ratatui::style::Modifier::from(crossterm_attribute);
+ let ratatui_modifier = ratatui::style::Modifier::from_crossterm(crossterm_attribute);
- let crossterm_attribute = ratatui_modifier.into();
- let crossterm_attribute = crossterm::style::types::Attribute::from(ratatui_modifier);
+ let crossterm_attribute = ratatui_modifier.into_crossterm();
```
Similar conversions for `ContentStyle` -> `Style` and `Attributes` -> `Modifier` exist for
Crossterm and the various Termion and Termwiz types as well.
### `Bar::label()` and `BarGroup::label()` now accepts `Into<Line<'a>>`. ([#1471])
[#1471]: https://github.com/ratatui/ratatui/pull/1471
Previously `Bar::label()` and `BarGroup::label()` accepted `Line<'a>`, but they now accepts `Into<Line<'a>>`.
for `Bar::label()`:
```diff
- Bar::default().label("foo".into());
+ Bar::default().label("foo");
```
for `BarGroup::label()`:
```diff
- BarGroup::default().label("bar".into());
+ BarGroup::default().label("bar");
```
### `Bar::text_value` now accepts `Into<String>` ([#1471])
Previously `Bar::text_value` accepted `String`, but now it accepts `Into<String>`.
for `Bar::text_value()`:
```diff
- Bar::default().text_value("foobar".into());
+ Bar::default().text_value("foobar");
```
## [v0.29.0](https://github.com/ratatui/ratatui/releases/tag/v0.29.0)
### `Sparkline::data` takes `IntoIterator<Item = SparklineBar>` instead of `&[u64]` and is no longer const ([#1326])
[#1326]: https://github.com/ratatui/ratatui/pull/1326
The `Sparkline::data` method has been modified to accept `IntoIterator<Item = SparklineBar>`
instead of `&[u64]`.
`SparklineBar` is a struct that contains an `Option<u64>` value, which represents an possible
_absent_ value, as distinct from a `0` value. This change allows the `Sparkline` to style
data points differently, depending on whether they are present or absent.
`SparklineBar` also contains an `Option<Style>` that will be used to apply a style the bar in
addition to any other styling applied to the `Sparkline`.
Several `From` implementations have been added to `SparklineBar` to support existing callers who
provide `&[u64]` and other types that can be converted to `SparklineBar`, such as `Option<u64>`.
If you encounter any type inference issues, you may need to provide an explicit type for the data
passed to `Sparkline::data`. For example, if you are passing a single value, you may need to use
`into()` to convert it to form that can be used as a `SparklineBar`:
```diff
let value = 1u8;
- Sparkline::default().data(&[value.into()]);
+ Sparkline::default().data(&[u64::from(value)]);
```
As a consequence of this change, the `data` method is no longer a `const fn`.
### `Color::from_hsl` is now behind the `palette` feature and accepts `palette::Hsl` ([#1418])
[#1418]: https://github.com/ratatui/ratatui/pull/1418
Previously `Color::from_hsl` accepted components as individual f64 parameters. It now accepts a
single `palette::Hsl` value and is gated behind a `palette` feature flag.
```diff
- Color::from_hsl(360.0, 100.0, 100.0)
+ Color::from_hsl(Hsl::new(360.0, 100.0, 100.0))
```
### Removed public fields from `Rect` iterators ([#1358], [#1424])
[#1358]: https://github.com/ratatui/ratatui/pull/1358
[#1424]: https://github.com/ratatui/ratatui/pull/1424
The `pub` modifier has been removed from fields on the `Columns`,`Rows`, and `Positions` iterators.
These fields were not intended to be public and should not have been accessed directly.
### `Rect::area()` now returns u32 instead of u16 ([#1378])
[#1378]: https://github.com/ratatui/ratatui/pull/1378
This is likely to impact anything which relies on `Rect::area` maxing out at u16::MAX. It can now
return up to u16::MAX * u16::MAX (2^32 - 2^17 + 1).
### `Line` now implements `From<Cow<str>` ([#1373])
[#1373]: https://github.com/ratatui/ratatui/pull/1373
As this adds an extra conversion, ambiguous inferred expressions may no longer compile.
```rust
// given:
struct Foo { ... }
impl From<Foo> for String { ... }
impl From<Foo> for Cow<str> { ... }
let foo = Foo { ... };
let line = Line::from(foo); // now fails due to now ambiguous inferred type
// replace with e.g.
let line = Line::from(String::from(foo));
```
### `Tabs::select()` now accepts `Into<Option<usize>>` ([#1413])
[#1413]: https://github.com/ratatui/ratatui/pull/1413
Previously `Tabs::select()` accepted `usize`, but it now accepts `Into<Option<usize>>`. This breaks
any code already using parameter type inference:
```diff
let selected = 1u8;
- let tabs = Tabs::new(["A", "B"]).select(selected.into())
+ let tabs = Tabs::new(["A", "B"]).select(selected as usize)
```
### `Table::highlight_style` is now `Table::row_highlight_style` ([#1331])
[#1331]: https://github.com/ratatui/ratatui/pull/1331
The `Table::highlight_style` is now deprecated in favor of `Table::row_highlight_style`.
Also, the serialized output of the `TableState` will now include the "selected_column" field.
Software that manually parse the serialized the output (with anything other than the `Serialize`
implementation on `TableState`) may have to be refactored if the "selected_column" field is not
accounted for. This does not affect users who rely on the `Deserialize`, or `Serialize`
implementation on the state.
## [v0.28.0](https://github.com/ratatui/ratatui/releases/tag/v0.28.0)
## v0.28.0
### `Backend::size` returns `Size` instead of `Rect` ([#1254])
@@ -325,7 +134,7 @@ are also named terminal, and confusion about module exports for newer Rust users
This change simplifies the trait and makes it easier to implement.
### `Frame::size` is deprecated and renamed to `Frame::area` ([#1293])
### `Frame::size` is deprecated and renamed to `Frame::area`
[#1293]: https://github.com/ratatui/ratatui/pull/1293

View File

@@ -2,556 +2,6 @@
All notable changes to this project will be documented in this file.
_"Food will come, Remy. Food always comes to those who love to cook." Gusteau_
We are excited to announce the new version of `ratatui` - a Rust library that's all about cooking up TUIs 👨‍🍳🐀
**Release highlights**: <https://ratatui.rs/highlights/v029/>
⚠️ List of breaking changes can be found [here](https://github.com/ratatui/ratatui/blob/main/BREAKING-CHANGES.md).
## [v0.29.0](https://github.com/ratatui/ratatui/releases/tag/v0.29.0) - 2024-10-21
### Features
- [3a43274](https://github.com/ratatui/ratatui/commit/3a43274881a79b4e593536c2ca915b509e557215) *(color)* Add hsluv support by @du-ob in [#1333](https://github.com/ratatui/ratatui/pull/1333)
- [4c4851c](https://github.com/ratatui/ratatui/commit/4c4851ca3d1437a50ed1f146c0849b58716b89a2) *(example)* Add drawing feature to the canvas example by @orhun in [#1429](https://github.com/ratatui/ratatui/pull/1429)
> ![rec_20241018T235208](https://github.com/user-attachments/assets/cfb2f9f8-773b-4599-9312-29625ff2ca60)
>
>
> fun fact: I had to do [35
> pushups](https://www.youtube.com/watch?v=eS92stzBYXA) for this...
>
> ---------
- [e5a7609](https://github.com/ratatui/ratatui/commit/e5a76095884a4ce792846289f56d04a4acaaa6fa) *(line)* Impl From<Cow<str>> for Line by @joshka in [#1373](https://github.com/ratatui/ratatui/pull/1373) [**breaking**]
>
> BREAKING-CHANGES:`Line` now implements `From<Cow<str>`
>
> As this adds an extra conversion, ambiguous inferred values may no longer
> compile.
>
> ```rust
> // given:
> struct Foo { ... }
> impl From<Foo> for String { ... }
> impl From<Foo> for Cow<str> { ... }
>
> let foo = Foo { ... };
> let line = Line::from(foo); // now fails due to ambiguous type inference
> // replace with
> let line = Line::from(String::from(foo));
> ```
>
> Fixes:https://github.com/ratatui/ratatui/issues/1367
>
> ---------
- [2805ddd](https://github.com/ratatui/ratatui/commit/2805dddf0527584da9c7865ff6a78a9c74731187) *(logo)* Add a Ratatui logo widget by @joshka in [#1307](https://github.com/ratatui/ratatui/pull/1307)
> This is a simple logo widget that can be used to render the Ratatui logo
> in the terminal. It is used in the `examples/ratatui-logo.rs` example,
> and may be used in your applications' help or about screens.
>
> ```rust
> use ratatui::{Frame, widgets::RatatuiLogo};
>
> fn draw(frame: &mut Frame) {
> frame.render_widget(RatatuiLogo::tiny(), frame.area());
> }
> ```
- [d72968d](https://github.com/ratatui/ratatui/commit/d72968d86b94100579feba80c5cd207c2e7e13e7) *(scrolling-regions)* Use terminal scrolling regions to stop Terminal::insert_before from flickering by @nfachan in [#1341](https://github.com/ratatui/ratatui/pull/1341) [**breaking**]
> The current implementation of Terminal::insert_before causes the
> viewport to flicker. This is described in #584 .
>
> This PR removes that flickering by using terminal scrolling regions
> (sometimes called "scroll regions"). A terminal can have its scrolling
> region set to something other than the whole screen. When a scroll ANSI
> sequence is sent to the terminal and it has a non-default scrolling
> region, the terminal will scroll just inside of that region.
>
> We use scrolling regions to implement insert_before. We create a region
> on the screen above the viewport, scroll that up to make room for the
> newly inserted lines, and then draw the new lines. We may need to repeat
> this process depending on how much space there is and how many lines we
> need to draw.
>
> When the viewport takes up the entire screen, we take a modified
> approach. We create a scrolling region of just the top line (could be
> more) of the viewport, then use that to draw the lines we want to
> output. When we're done, we scroll it up by one line, into the
> scrollback history, and then redraw the top line from the viewport.
>
> A final edge case is when the viewport hasn't yet reached the bottom of
> the screen. This case, we set up a different scrolling region, where the
> top is the top of the viewport, and the bottom is the viewport's bottom
> plus the number of lines we want to scroll by. We then scroll this
> region down to open up space above the viewport for drawing the inserted
> lines.
>
> Regardless of what we do, we need to reset the scrolling region. This PR
> takes the approach of always resetting the scrolling region after every
> operation. So the Backend gets new scroll_region_up and
> scroll_region_down methods instead of set_scrolling_region, scroll_up,
> scroll_down, and reset_scrolling_region methods. We chose that approach
> for two reasons. First, we don't want Ratatui to have to remember that
> state and then reset the scrolling region when tearing down. Second, the
> pre-Windows-10 console code doesn't support scrolling region
>
> This PR:
> - Adds a new scrolling-regions feature.
> - Adds two new Backend methods: scroll_region_up and scroll_region_down.
> - Implements those Backend methods on all backends in the codebase.
> - The crossterm and termion implementations use raw ANSI escape
> sequences. I'm trying to merge changes into those two projects
> separately to support these functions.
> - Adds code to Terminal::insert_before to choose between
> insert_before_scrolling_regions and insert_before_no_scrolling_regions.
> The latter is the old implementation.
> - Adds lots of tests to the TestBackend to for the
> scrolling-region-related Backend methods.
> - Adds versions of terminal tests that show that insert_before doesn't
> clobber the viewport. This is a change in behavior from before.
- [dc8d058](https://github.com/ratatui/ratatui/commit/dc8d0587ecfd46cde86c9e33a6fd385e2d4810a9) *(table)* Add support for selecting column and cell by @airblast-dev in [#1331](https://github.com/ratatui/ratatui/pull/1331) [**breaking**]
> Fixes https://github.com/ratatui-org/ratatui/issues/1250
>
> Adds support for selecting a column and cell in `TableState`. The
> selected column, and cells style can be set by
> `Table::column_highlight_style` and `Table::cell_highlight_style`
> respectively.
>
> The table example has also been updated to display the new
> functionality:
>
> https://github.com/user-attachments/assets/e5fd2858-4931-4ce1-a2f6-a5ea1eacbecc
>
> BREAKING CHANGE:The Serialized output of the state will now include the
> "selected_column" field. Software that manually parse the serialized the
> output (with anything other than the `Serialize` implementation on
> `TableState`) may have to be refactored if the "selected_column" field
> is not accounted for. This does not affect users who rely on the
> `Deserialize`, or `Serialize` implementation on the state.
>
> BREAKING CHANGE:The `Table::highlight_style` is now deprecated in favor
> of `Table::row_highlight_style`.
>
> ---------
- [ab6b1fe](https://github.com/ratatui/ratatui/commit/ab6b1feaec3ef0cf23bcfac219b95ec946180fa8) *(tabs)* Allow tabs to be deselected by @joshka in [#1413](https://github.com/ratatui/ratatui/pull/1413) [**breaking**]
>
> `Tabs::select()` now accepts `Into<Option<usize>>` instead of `usize`.
> This allows tabs to be deselected by passing `None`.
>
> `Tabs::default()` is now also implemented manually instead of deriving
> `Default`, and a new method `Tabs::titles()` is added to set the titles
> of the tabs.
>
> Fixes:<https://github.com/ratatui/ratatui/pull/1412>
>
> BREAKING CHANGE:`Tabs::select()` now accepts `Into<Option<usize>>`
> which breaks any code already using parameter type inference:
>
> ```diff
> let selected = 1u8;
> - let tabs = Tabs::new(["A", "B"]).select(selected.into())
> + let tabs = Tabs::new(["A", "B"]).select(selected as usize)
> ```
- [23c0d52](https://github.com/ratatui/ratatui/commit/23c0d52c29f27547d94448be44aa46e85f49fbb0) *(text)* Improve concise debug view for Span,Line,Text,Style by @joshka in [#1410](https://github.com/ratatui/ratatui/pull/1410)
> Improves https://github.com/ratatui/ratatui/pull/1383
>
> The following now round trips when formatted for debug.
> This will make it easier to use insta when testing text related views of
> widgets.
>
> ```rust
> Text::from_iter([
> Line::from("Hello, world!"),
> Line::from("How are you?").bold().left_aligned(),
> Line::from_iter([
> Span::from("I'm "),
> Span::from("doing ").italic(),
> Span::from("great!").bold(),
> ]),
> ]).on_blue().italic().centered()
> ```
- [60cc15b](https://github.com/ratatui/ratatui/commit/60cc15bbb064faa704f78ca51ae60584b5f7ca31) *(uncategorized)* Add support for empty bar style to `Sparkline` by @fujiapple852 in [#1326](https://github.com/ratatui/ratatui/pull/1326) [**breaking**]
> - distinguish between empty bars and bars with a value of 0
> - provide custom styling for empty bars
> - provide custom styling for individual bars
> - inverts the rendering algorithm to be item first
>
> Closes:#1325
>
> BREAKING CHANGE:`Sparkline::data` takes `IntoIterator<Item = SparklineBar>`
> instead of `&[u64]` and is no longer const
- [453a308](https://github.com/ratatui/ratatui/commit/453a308b46bbacba2ee7cba849cf0c19c88a1a27) *(uncategorized)* Add overlap to layout by @kdheepak in [#1398](https://github.com/ratatui/ratatui/pull/1398) [**breaking**]
> This PR adds a new feature for the existing `Layout::spacing` method,
> and introducing a `Spacing` enum.
>
> Now `Layout::spacing` is generic and can take
>
> - zero or positive numbers, e.g. `Layout::spacing(1)` (current
> functionality)
> - negative number, e.g. `Layout::spacing(-1)` (new)
> - variant of the `Spacing` (new)
>
> This allows creating layouts with a shared pixel for segments. When
> `spacing(negative_value)` is used, spacing is ignored and all segments
> will be adjacent and have pixels overlapping.
> `spacing(zero_or_positive_value)` behaves the same as before. These are
> internally converted to `Spacing::Overlap` or `Spacing::Space`.
>
> Here's an example output to illustrate the layout solve from this PR:
>
> ```rust
> #[test]
> fn test_layout() {
> use crate::layout::Constraint::*;
> let mut terminal = crate::Terminal::new(crate::backend::TestBackend::new(50, 4)).unwrap();
> terminal
> .draw(|frame| {
> let [upper, lower] = Layout::vertical([Fill(1), Fill(1)]).areas(frame.area());
>
> let (segments, spacers) = Layout::horizontal([Length(10), Length(10), Length(10)])
> .flex(Flex::Center)
> .split_with_spacers(upper);
>
> for segment in segments.iter() {
> frame.render_widget(
> crate::widgets::Block::bordered()
> .border_set(crate::symbols::border::DOUBLE),
> *segment,
> );
> }
> for spacer in spacers.iter() {
> frame.render_widget(crate::widgets::Block::bordered(), *spacer);
> }
>
> let (segments, spacers) = Layout::horizontal([Length(10), Length(10), Length(10)])
> .flex(Flex::Center)
> .spacing(-1) // new feature
> .split_with_spacers(lower);
>
> for segment in segments.iter() {
> frame.render_widget(
> crate::widgets::Block::bordered()
> .border_set(crate::symbols::border::DOUBLE),
> *segment,
> );
> }
> for spacer in spacers.iter() {
> frame.render_widget(crate::widgets::Block::bordered(), *spacer);
> }
> })
> .unwrap();
> dbg!(terminal.backend());
> }
> ```
>
>
> ```plain
> ┌────────┐╔════════╗╔════════╗╔════════╗┌────────┐
> └────────┘╚════════╝╚════════╝╚════════╝└────────┘
> ┌─────────┐╔════════╔════════╔════════╗┌─────────┐
> └─────────┘╚════════╚════════╚════════╝└─────────┘
> ```
>
> Currently drawing a border on top of an existing border overwrites it.
> Future PRs will allow for making the border drawing handle overlaps
> better.
>
> ---------
- [7bdccce](https://github.com/ratatui/ratatui/commit/7bdccce3d56052306eb4121afe6b1ff56b198796) *(uncategorized)* Add an impl of `DoubleEndedIterator` for `Columns` and `Rows` by @fujiapple852 [**breaking**]
>
> BREAKING-CHANGE:The `pub` modifier has been removed from fields on the
>
> `layout::rect::Columns` and `layout::rect::Rows` iterators. These fields
> were not intended to be public and should not have been accessed
> directly.
>
> Fixes:#1357
### Bug Fixes
- [4f5503d](https://github.com/ratatui/ratatui/commit/4f5503dbf610290904a759a3f169a15111f11392) *(color)* Hsl and hsluv are now clamped before conversion by @joshka in [#1436](https://github.com/ratatui/ratatui/pull/1436) [**breaking**]
> The `from_hsl` and `from_hsluv` functions now clamp the HSL and HSLuv
> values before converting them to RGB. This ensures that the input values
> are within the expected range before conversion.
>
> Also note that the ranges of Saturation and Lightness values have been
> aligned to be consistent with the palette crate. Saturation and Lightness
> for `from_hsl` are now in the range [0.0..1.0] while `from_hsluv` are
> in the range [0.0..100.0].
>
> Refs:- <https://github.com/Ogeon/palette/discussions/253>
> - <https://docs.rs/palette/latest/palette/struct.Hsl.html>
> - <https://docs.rs/palette/latest/palette/struct.Hsluv.html>
>
> Fixes:<https://github.com/ratatui/ratatui/issues/1433>
- [b7e4885](https://github.com/ratatui/ratatui/commit/b7e488507d23cbc91ac63d5249088ad0f4852205) *(color)* Fix doc test for from_hsl by @joshka in [#1421](https://github.com/ratatui/ratatui/pull/1421)
- [3df685e](https://github.com/ratatui/ratatui/commit/3df685e1144340935db2b1d929e2546f83c5e65f) *(rect)* Rect::area now returns u32 and Rect::new() no longer clamps area to u16::MAX by @joshka in [#1378](https://github.com/ratatui/ratatui/pull/1378) [**breaking**]
> This change fixes the unexpected behavior of the Rect::new() function to
> be more intuitive. The Rect::new() function now clamps the width and
> height of the rectangle to keep each bound within u16::MAX. The
> Rect::area() function now returns a u32 instead of a u16 to allow for
> larger areas to be calculated.
>
> Previously, the Rect::new() function would clamp the total area of the
> rectangle to u16::MAX, by preserving the aspect ratio of the rectangle.
>
> BREAKING CHANGE:Rect::area() now returns a u32 instead of a u16.
>
> Fixes:<https://github.com/ratatui/ratatui/issues/1375>
- [514d273](https://github.com/ratatui/ratatui/commit/514d2738750d792a75fde6cc7666f9220bcf6b3a) *(terminal)* Use the latest, resized area when clearing by @roberth in [#1427](https://github.com/ratatui/ratatui/pull/1427)
- [0f48239](https://github.com/ratatui/ratatui/commit/0f4823977894cef51d5ffafe6ae35ca7ad56e1ac) *(terminal)* Resize() now resizes fixed viewports by @Patryk27 in [#1353](https://github.com/ratatui/ratatui/pull/1353)
>
> `Terminal::resize()` on a fixed viewport used to do nothing due to
> an accidentally shadowed variable. This now works as intended.
- [a52ee82](https://github.com/ratatui/ratatui/commit/a52ee82fc716fafb2652b83a331c36f844104dda) *(text)* Truncate based on alignment by @Lunderberg in [#1432](https://github.com/ratatui/ratatui/pull/1432)
> This is a follow-up PR to https://github.com/ratatui/ratatui/pull/987,
> which implemented alignment-aware truncation for the `Line` widget.
> However, the truncation only checked the `Line::alignment` field, and
> any alignment inherited from a parent's `Text::alignment` field would
> not be used.
>
> This commit updates the truncation of `Line` to depend both on the
> individual `Line::alignment`, and on any alignment inherited from the
> parent's `Text::alignment`.
- [611086e](https://github.com/ratatui/ratatui/commit/611086eba4dc07dcef89502a3bedfc28015b879f) *(uncategorized)* Sparkline docs / doc tests by @joshka in [#1437](https://github.com/ratatui/ratatui/pull/1437)
- [b9653ba](https://github.com/ratatui/ratatui/commit/b9653ba05a468d3843499d8abd243158df823f82) *(uncategorized)* Prevent calender render panic when terminal height is small by @adrodgers in [#1380](https://github.com/ratatui/ratatui/pull/1380)
>
> Fixes:#1379
- [da821b4](https://github.com/ratatui/ratatui/commit/da821b431edd656973b4480d3d4f22e7eea6d369) *(uncategorized)* Clippy lints from rust 1.81.0 by @fujiapple852 in [#1356](https://github.com/ratatui/ratatui/pull/1356)
- [68886d1](https://github.com/ratatui/ratatui/commit/68886d1787b8e07d307dda4f36342d51d650345b) *(uncategorized)* Add `unstable-backend-writer` feature by @Patryk27 in [#1352](https://github.com/ratatui/ratatui/pull/1352)
>
> https://github.com/ratatui/ratatui/pull/991 created a new unstable
> feature, but forgot to add it to Cargo.toml, making it impossible to use
> on newer versions of rustc - this commit fixes it.
### Refactor
- [6db16d6](https://github.com/ratatui/ratatui/commit/6db16d67fc3cc97f1e5bd4b7df02ce9f00756a55) *(color)* Use palette types for Hsl/Hsluv conversions by @orhun in [#1418](https://github.com/ratatui/ratatui/pull/1418) [**breaking**]
>
> BREAKING-CHANGE:Previously `Color::from_hsl` accepted components
> as individual f64 parameters. It now accepts a single `palette::Hsl`
> value
> and is gated behind a `palette` feature flag.
>
> ```diff
> - Color::from_hsl(360.0, 100.0, 100.0)
> + Color::from_hsl(Hsl::new(360.0, 100.0, 100.0))
> ```
>
> Fixes:<https://github.com/ratatui/ratatui/issues/1414>
>
> ---------
- [edcdc8a](https://github.com/ratatui/ratatui/commit/edcdc8a8147a2f450d2c871b19da6d6383fd5497) *(layout)* Rename element to segment in layout by @kdheepak in [#1397](https://github.com/ratatui/ratatui/pull/1397)
> This PR renames `element` to `segment` in a couple of functions in the
> layout calculations for clarity. `element` can refer to `segment`s or
> `spacer`s and functions that take only `segment`s should use `segment`
> as the variable names.
- [1153a9e](https://github.com/ratatui/ratatui/commit/1153a9ebaf0b98c45982002a659cb718e3c1d137) *(uncategorized)* Consistent result expected in layout tests by @farmeroy in [#1406](https://github.com/ratatui/ratatui/pull/1406)
>
> Fixes #1399
> I've looked through all the `assert_eq` and made sure that they follow
> the `expected, result` pattern. I wasn't sure if it was desired to
> actually pass result and expected as variables to the assert_eq
> statements, so I've left everything that seems to have followed the
> pattern as is.
- [20c88aa](https://github.com/ratatui/ratatui/commit/20c88aaa5b9eb011a52240eab5edc1a8db23157a) *(uncategorized)* Avoid unneeded allocations by @mo8it in [#1345](https://github.com/ratatui/ratatui/pull/1345)
### Documentation
- [b13e2f9](https://github.com/ratatui/ratatui/commit/b13e2f94733afccfe02275fca263bde1dc532d2f) *(backend)* Added link to stdio FAQ by @Valentin271 in [#1349](https://github.com/ratatui/ratatui/pull/1349)
- [b88717b](https://github.com/ratatui/ratatui/commit/b88717b65f7f89276edd855c4a3f9da2eda44361) *(constraint)* Add note about percentages by @joshka in [#1368](https://github.com/ratatui/ratatui/pull/1368)
- [381ec75](https://github.com/ratatui/ratatui/commit/381ec75329866b3c1256113d1cb7716206b79fb7) *(readme)* Reduce the length by @joshka in [#1431](https://github.com/ratatui/ratatui/pull/1431)
> Motivation for this is that there's a bunch of stuff at the bottom of the Readme that we don't really keep up to date. Instead it's better to link to the places that we do keep this info.
- [4728f0e](https://github.com/ratatui/ratatui/commit/4728f0e68b41eabb7d4ebd041fd5a85a0e794287) *(uncategorized)* Tweak readme by @joshka in [#1419](https://github.com/ratatui/ratatui/pull/1419)
>
> Fixes:<https://github.com/ratatui/ratatui/issues/1417>
- [4069aa8](https://github.com/ratatui/ratatui/commit/4069aa82745585f53b4b3376af589bb1b6108427) *(uncategorized)* Fix missing breaking changes link by @joshka in [#1416](https://github.com/ratatui/ratatui/pull/1416)
- [870bc6a](https://github.com/ratatui/ratatui/commit/870bc6a64a680e9209d30e67e2e1f4e50a10a4bb) *(uncategorized)* Use `Frame::area()` instead of `size()` in examples by @hosseinnedaee in [#1361](https://github.com/ratatui/ratatui/pull/1361)
>
> `Frame::size()` is deprecated
### Performance
- [8db7a9a](https://github.com/ratatui/ratatui/commit/8db7a9a44a2358315dedaee3e7a2cb1a44ae1e58) *(uncategorized)* Implement size hints for `Rect` iterators by @airblast-dev in [#1420](https://github.com/ratatui/ratatui/pull/1420)
### Styling
- [e02947b](https://github.com/ratatui/ratatui/commit/e02947be6185643f906a97c453540676eade3f38) *(example)* Update panic message in minimal template by @orhun in [#1344](https://github.com/ratatui/ratatui/pull/1344)
### Miscellaneous Tasks
- [67c0ea2](https://github.com/ratatui/ratatui/commit/67c0ea243b5eb08159e41f922067247984902c1a) *(block)* Deprecate block::Title by @joshka in [#1372](https://github.com/ratatui/ratatui/pull/1372)
>
> `ratatui::widgets::block::Title` is deprecated in favor of using `Line`
> to represent titles.
> This removes an unnecessary layer of wrapping (string -> Span -> Line ->
> Title).
>
> This struct will be removed in a future release of Ratatui (likely
> 0.31).
> For more information see:
>
> <https://github.com/ratatui/ratatui/issues/738>
>
> To update your code:
> ```rust
>
> Block::new().title(Title::from("foo"));
> // becomes any of
>
> Block::new().title("foo");
>
> Block::new().title(Line::from("foo"));
>
> Block::new().title(Title::from("foo").position(Position::TOP));
> // becomes any of
>
> Block::new().title_top("foo");
>
> Block::new().title_top(Line::from("foo"));
>
> Block::new().title(Title::from("foo").position(Position::BOTTOM));
> // becomes any of
>
> Block::new().title_bottom("foo");
>
> Block::new().title_bottom(Line::from("foo"));
> ```
- [6515097](https://github.com/ratatui/ratatui/commit/6515097434a10c08276b58f0cd10b9301b44e9fe) *(cargo)* Check in Cargo.lock by @joshka in [#1434](https://github.com/ratatui/ratatui/pull/1434)
> When kept up to date, this makes it possible to build any git version
> with the same versions of crates that were used for any version, without
> it, you can only use the current versions. This makes bugs in semver
> compatible code difficult to detect.
>
> The Cargo.lock file is not used by downstream consumers of the crate, so
> it is safe to include it in the repository (and recommended by the Rust
> docs).
>
> See:- https://doc.rust-lang.org/cargo/faq.html#why-have-cargolock-in-version-control
> - https://blog.rust-lang.org/2023/08/29/committing-lockfiles.html
> - https://github.com/rust-lang/cargo/issues/8728
- [c777beb](https://github.com/ratatui/ratatui/commit/c777beb658ebab26890b52cbda8df5d945525221) *(ci)* Bump git-cliff-action to v4 by @orhun in [#1350](https://github.com/ratatui/ratatui/pull/1350)
>
> See:https://github.com/orhun/git-cliff-action/releases/tag/v4.0.0
- [69e0cd2](https://github.com/ratatui/ratatui/commit/69e0cd2fc4b126870b3381704260271904996c8f) *(deny)* Allow Zlib license in cargo-deny configuration by @orhun in [#1411](https://github.com/ratatui/ratatui/pull/1411)
- [bc10af5](https://github.com/ratatui/ratatui/commit/bc10af5931d1c1ec58a4181c01807ed3c52051c6) *(style)* Make Debug output for Text/Line/Span/Style more concise by @joshka in [#1383](https://github.com/ratatui/ratatui/pull/1383)
>
> Given:```rust
>
> Text::from_iter([
> Line::from("without line fields"),
> Line::from("with line fields").bold().centered(),
> Line::from_iter([
> Span::from("without span fields"),
> Span::from("with span fields")
> .green()
> .on_black()
> .italic()
> .not_dim(),
> ]),
> ])
> ```
>
> Debug:```
> Text [Line [Span("without line fields")], Line { style: Style::new().add_modifier(Modifier::BOLD), alignment: Some(Center), spans: [Span("with line fields")] }, Line [Span("without span fields"), Span { style: Style::new().green().on_black().add_modifier(Modifier::ITALIC).remove_modifier(Modifier::DIM), content: "with span fields" }]]
> ```
>
> Fixes: https://github.com/ratatui/ratatui/issues/1382
>
> ---------
- [f6f7794](https://github.com/ratatui/ratatui/commit/f6f7794dd782d20cd41875c0578ffc4331692c1e) *(uncategorized)* Remove leftover prelude refs / glob imports from example code by @joshka in [#1430](https://github.com/ratatui/ratatui/pull/1430)
>
> Fixes:<https://github.com/ratatui/ratatui/issues/1150>
- [9fd1bee](https://github.com/ratatui/ratatui/commit/9fd1beedb25938bcc9565a52f1104ed45636c2dd) *(uncategorized)* Make Positions iterator fields private by @joshka in [#1424](https://github.com/ratatui/ratatui/pull/1424) [**breaking**]
>
> BREAKING CHANGE:The Rect Positions iterator no longer has public
> fields. The `rect` and `current_position` fields have been made private
> as they were not intended to be accessed directly.
- [c32baa7](https://github.com/ratatui/ratatui/commit/c32baa7cd8a29a370a71da07ee02cf32125c9bcf) *(uncategorized)* Add benchmark for `Table` by @airblast-dev in [#1408](https://github.com/ratatui/ratatui/pull/1408)
- [5ad623c](https://github.com/ratatui/ratatui/commit/5ad623c29b8f0b50fad742448902245f353ef19e) *(uncategorized)* Remove usage of prelude by @joshka in [#1390](https://github.com/ratatui/ratatui/pull/1390)
> This helps make the doc examples more explicit about what is being used.
> It will also makes it a bit easier to do future refactoring of Ratatui,
> into several crates, as the ambiguity of where types are coming from
> will be reduced.
>
> Additionally, several doc examples have been simplified to use Stylize,
> and necessary imports are no longer hidden.
>
> This doesn't remove the prelude. Only the internal usages.
- [f4880b4](https://github.com/ratatui/ratatui/commit/cc7497532ac50e7e15e8ee8ff506f4689c396f50) *(deps)* Pin unicode-width to 0.2.0 by @orhun in [#1403](https://github.com/ratatui/ratatui/pull/1403) [**breaking**]
> We pin unicode-width to avoid breaking applications when there are breaking changes in the library.
>
> Discussion in [#1271](https://github.com/ratatui/ratatui/pull/1271)
### Continuous Integration
- [5635b93](https://github.com/ratatui/ratatui/commit/5635b930c7196ef8f12824341a7bd8b7323aabcd) *(uncategorized)* Add cargo-machete and remove unused deps by @Veetaha in [#1362](https://github.com/ratatui/ratatui/pull/1362)
>
> https://github.com/bnjbvr/cargo-machete
### New Contributors
* @roberth made their first contribution in [#1427](https://github.com/ratatui/ratatui/pull/1427)
* @du-ob made their first contribution in [#1333](https://github.com/ratatui/ratatui/pull/1333)
* @farmeroy made their first contribution in [#1406](https://github.com/ratatui/ratatui/pull/1406)
* @adrodgers made their first contribution in [#1380](https://github.com/ratatui/ratatui/pull/1380)
* @Veetaha made their first contribution in [#1362](https://github.com/ratatui/ratatui/pull/1362)
* @hosseinnedaee made their first contribution in [#1361](https://github.com/ratatui/ratatui/pull/1361)
* @Patryk27 made their first contribution in [#1352](https://github.com/ratatui/ratatui/pull/1352)
**Full Changelog**: https://github.com/ratatui/ratatui/compare/v0.28.1...v0.29.0
## [v0.28.1](https://github.com/ratatui/ratatui/releases/tag/v0.28.1) - 2024-08-25
### Features
@@ -5175,7 +4625,7 @@ Also, we created various tutorials and walkthroughs in [Ratatui Book](https://gi
```text
The `Spans` type (plural, not singular) was replaced with a more ergonomic `Line` type
in Ratatui v0.21.0 and marked deprecated but left for backwards compatibility. This is now
in Ratatui v0.21.0 and marked deprecated byt left for backwards compatibility. This is now
removed.
- `Line` replaces `Spans`

View File

@@ -31,7 +31,7 @@ guarantee that the behavior is unchanged.
### Code formatting
Run `cargo xtask format` before committing to ensure that code is consistently formatted with
Run `cargo make format` before committing to ensure that code is consistently formatted with
rustfmt. Configuration is in [`rustfmt.toml`](./rustfmt.toml).
### Search `tui-rs` for similar work
@@ -56,7 +56,7 @@ documented.
### Run CI tests before pushing a PR
Running `cargo xtask ci` before pushing will perform the same checks that we do in the CI process.
Running `cargo make ci` before pushing will perform the same checks that we do in the CI process.
It's not mandatory to do this before pushing, however it may save you time to do so instead of
waiting for GitHub to run the checks.
@@ -71,17 +71,17 @@ in GitHub docs.
### Setup
TL;DR: Clone the repo and build it using `cargo xtask`.
Clone the repo and build it using [cargo-make](https://sagiegurari.github.io/cargo-make/)
Ratatui is an ordinary Rust project where common tasks are managed with
[cargo-xtask](https://github.com/matklad/cargo-xtask). It wraps common `cargo` commands with sane
[cargo-make](https://github.com/sagiegurari/cargo-make/). It wraps common `cargo` commands with sane
defaults depending on your platform of choice. Building the project should be as easy as running
`cargo xtask build`.
`cargo make build`.
```shell
git clone https://github.com/ratatui/ratatui.git
cd ratatui
cargo xtask build
cargo make build
```
### Tests
@@ -182,7 +182,7 @@ We use GitHub Actions for the CI where we perform the following checks:
- The code should conform to the default format enforced by `rustfmt`.
- The code should not contain common style issues `clippy`.
You can also check most of those things yourself locally using `cargo xtask ci` which will offer you
You can also check most of those things yourself locally using `cargo make ci` which will offer you
a shorter feedback loop than pushing to github.
## Relationship with `tui-rs`

3953
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

154
Makefile.toml Normal file
View File

@@ -0,0 +1,154 @@
# configuration for https://github.com/sagiegurari/cargo-make
[config]
skip_core_tasks = true
[env]
# all features except the backend ones
NON_BACKEND_FEATURES = "all-widgets,macros,serde"
[tasks.default]
alias = "ci"
[tasks.ci]
description = "Run continuous integration tasks"
dependencies = ["lint", "clippy", "check", "test"]
[tasks.lint]
description = "Lint code style (formatting, typos, docs, markdown)"
dependencies = ["lint-format", "lint-typos", "lint-docs"]
[tasks.lint-format]
description = "Lint code formatting"
toolchain = "nightly"
command = "cargo"
args = ["fmt", "--all", "--check"]
[tasks.format]
description = "Fix code formatting"
toolchain = "nightly"
command = "cargo"
args = ["fmt", "--all"]
[tasks.lint-typos]
description = "Run typo checks"
install_crate = { crate_name = "typos-cli", binary = "typos", test_arg = "--version" }
command = "typos"
[tasks.lint-docs]
description = "Check documentation for errors and warnings"
toolchain = "nightly"
command = "cargo"
args = [
"rustdoc",
"--all-features",
"--",
"-Zunstable-options",
"--check",
"-Dwarnings",
]
[tasks.lint-markdown]
description = "Check markdown files for errors and warnings"
command = "markdownlint-cli2"
args = ["**/*.md", "!target"]
[tasks.check]
description = "Check code for errors and warnings"
command = "cargo"
args = ["check", "--all-targets", "--all-features"]
[tasks.build]
description = "Compile the project"
command = "cargo"
args = ["build", "--all-targets", "--all-features"]
[tasks.clippy]
description = "Run Clippy for linting"
command = "cargo"
args = [
"clippy",
"--all-targets",
"--all-features",
"--tests",
"--benches",
"--",
"-D",
"warnings",
]
[tasks.install-nextest]
description = "Install cargo-nextest"
install_crate = { crate_name = "cargo-nextest", binary = "cargo-nextest", test_arg = "--help" }
[tasks.test]
description = "Run tests"
run_task = { name = ["test-lib", "test-doc"] }
[tasks.test-lib]
description = "Run default tests"
dependencies = ["install-nextest"]
command = "cargo"
args = ["nextest", "run", "--all-targets", "--all-features"]
[tasks.test-doc]
description = "Run documentation tests"
command = "cargo"
args = ["test", "--doc", "--all-features"]
[tasks.test-backend]
# takes a command line parameter to specify the backend to test (e.g. "crossterm")
description = "Run backend-specific tests"
dependencies = ["install-nextest"]
command = "cargo"
args = [
"nextest",
"run",
"--all-targets",
"--no-default-features",
"--features",
"${NON_BACKEND_FEATURES},${@}",
]
[tasks.coverage]
description = "Generate code coverage report"
command = "cargo"
args = [
"llvm-cov",
"--lcov",
"--output-path",
"target/lcov.info",
"--all-features",
]
[tasks.run-example]
private = true
condition = { env_set = ["TUI_EXAMPLE_NAME"] }
command = "cargo"
args = [
"run",
"--release",
"--example",
"${TUI_EXAMPLE_NAME}",
"--features",
"all-widgets",
]
[tasks.build-examples]
description = "Compile project examples"
command = "cargo"
args = ["build", "--examples", "--release", "--features", "all-widgets"]
[tasks.run-examples]
description = "Run project examples"
dependencies = ["build-examples"]
script = '''
#!@duckscript
files = glob_array ./examples/*.rs
for file in ${files}
name = basename ${file}
name = substring ${name} -3
set_env TUI_EXAMPLE_NAME ${name}
cm_run_task run-example
end
'''

331
README.md
View File

@@ -2,19 +2,16 @@
<summary>Table of Contents</summary>
- [Ratatui](#ratatui)
- [Quick Start](#quickstart)
- [Other documentation](#other-documentation)
- [Installation](#installation)
- [Introduction](#introduction)
- [Other Documentation](#other-documentation)
- [Quickstart](#quickstart)
- [Initialize and restore the terminal](#initialize-and-restore-the-terminal)
- [Drawing the UI](#drawing-the-ui)
- [Handling events](#handling-events)
- [Layout](#layout)
- [Text and styling](#text-and-styling)
- [Status of this fork](#status-of-this-fork)
- [Rust version requirements](#rust-version-requirements)
- [Widgets](#widgets)
- [Built in](#built-in)
- [Third-party libraries, bootstrapping templates and widgets](#third-party-libraries-bootstrapping-templates-and-widgets)
- [Third\-party libraries, bootstrapping templates and
widgets](#third-party-libraries-bootstrapping-templates-and-widgets)
- [Apps](#apps)
- [Alternatives](#alternatives)
- [Acknowledgments](#acknowledgments)
@@ -44,42 +41,28 @@ Badge]][GitHub Sponsors]<br> [![Discord Badge]][Discord Server] [![Matrix Badge]
lightweight library that provides a set of widgets and utilities to build complex Rust TUIs.
Ratatui was forked from the [tui-rs] crate in 2023 in order to continue its development.
## Quickstart
## 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
```
Then you can create a simple "Hello World" application:
Ratatui uses [Crossterm] by default as it works on most platforms. See the [Installation]
section of the [Ratatui Website] for more details on how to use other backends ([Termion] /
[Termwiz]).
```rust
use crossterm::event::{self, Event};
use ratatui::{text::Text, Frame};
## Introduction
fn main() {
let mut terminal = ratatui::init();
loop {
terminal.draw(draw).expect("failed to draw frame");
if matches!(event::read().expect("failed to read event"), Event::Key(_)) {
break;
}
}
ratatui::restore();
}
Ratatui is based on the principle of immediate rendering with intermediate buffers. This means
that for each frame, your app must render all widgets that are supposed to be part of the UI.
This is in contrast to the retained mode style of rendering where widgets are updated and then
automatically redrawn on the next frame. See the [Rendering] section of the [Ratatui Website]
for more info.
fn draw(frame: &mut Frame) {
let text = Text::raw("Hello World!");
frame.render_widget(text, frame.area());
}
```
The full code for this example which contains a little more detail is in the [Examples]
directory. For more guidance on different ways to structure your application see the
[Application Patterns] and [Hello World tutorial] sections in the [Ratatui Website] and the
various [Examples]. There are also several starter templates available in the [templates]
repository.
You can also watch the [FOSDEM 2024 talk] about Ratatui which gives a brief introduction to
terminal user interfaces and showcases the features of Ratatui, along with a hello world demo.
## Other documentation
@@ -91,82 +74,46 @@ repository.
- [Changelog] - generated by [git-cliff] utilizing [Conventional Commits].
- [Breaking Changes] - a list of breaking changes in the library.
You can also watch the [FOSDEM 2024 talk] about Ratatui which gives a brief introduction to
terminal user interfaces and showcases the features of Ratatui, along with a hello world demo.
## Quickstart
## Introduction
Ratatui is based on the principle of immediate rendering with intermediate buffers. This means
that for each frame, your app must render all widgets that are supposed to be part of the UI.
This is in contrast to the retained mode style of rendering where widgets are updated and then
automatically redrawn on the next frame. See the [Rendering] section of the [Ratatui Website]
for more info.
Ratatui uses [Crossterm] by default as it works on most platforms. See the [Installation]
section of the [Ratatui Website] for more details on how to use other backends ([Termion] /
[Termwiz]).
The following example demonstrates the minimal amount of code necessary to setup a terminal and
render "Hello World!". The full code for this example which contains a little more detail is in
the [Examples] directory. For more guidance on different ways to structure your application see
the [Application Patterns] and [Hello World tutorial] sections in the [Ratatui Website] and the
various [Examples]. There are also several starter templates available in the [templates]
repository.
Every application built with `ratatui` needs to implement the following steps:
- Initialize the terminal
- A main loop that:
- Draws the UI
- Handles input events
- A main loop to:
- Handle input events
- Draw the UI
- Restore the terminal state
The library contains a [`prelude`] module that re-exports the most commonly used traits and
types for convenience. Most examples in the documentation will use this instead of showing the
full path of each type.
### Initialize and restore the terminal
The [`Terminal`] type is the main entry point for any Ratatui application. It is generic over a
a choice of [`Backend`] implementations that each provide functionality to draw frames, clear
the screen, hide the cursor, etc. There are backend implementations for [Crossterm], [Termion]
and [Termwiz].
The [`Terminal`] type is the main entry point for any Ratatui application. It is a light
abstraction over a choice of [`Backend`] implementations that provides functionality to draw
each frame, clear the screen, hide the cursor, etc. It is parametrized over any type that
implements the [`Backend`] trait which has implementations for [Crossterm], [Termion] and
[Termwiz].
The simplest way to initialize the terminal is to use the [`init`] function which returns a
[`DefaultTerminal`] instance with the default options, enters the Alternate Screen and Raw mode
and sets up a panic hook that restores the terminal in case of panic. This instance can then be
used to draw frames and interact with the terminal state. (The [`DefaultTerminal`] instance is a
type alias for a terminal with the [`crossterm`] backend.) The [`restore`] function restores the
terminal to its original state.
```rust
fn main() -> std::io::Result<()> {
let mut terminal = ratatui::init();
let result = run(&mut terminal);
ratatui::restore();
result
}
```
See the [`backend` module] and the [Backends] section of the [Ratatui Website] for more info on
the alternate screen and raw mode.
Most applications should enter the Alternate Screen when starting and leave it when exiting and
also enable raw mode to disable line buffering and enable reading key events. See the [`backend`
module] and the [Backends] section of the [Ratatui Website] for more info.
### Drawing the UI
Drawing the UI is done by calling the [`Terminal::draw`] method on the terminal instance. This
method takes a closure that is called with a [`Frame`] instance. 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. 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.
The closure passed to the [`Terminal::draw`] method should handle the rendering of a full frame.
```rust
use ratatui::{widgets::Paragraph, Frame};
fn run(terminal: &mut ratatui::DefaultTerminal) -> std::io::Result<()> {
loop {
terminal.draw(|frame| draw(frame))?;
if handle_events()? {
break Ok(());
}
}
}
fn draw(frame: &mut Frame) {
let text = Paragraph::new("Hello World!");
frame.render_widget(text, frame.area());
}
```
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. 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
@@ -175,23 +122,63 @@ calling backend library methods directly. See the [Handling Events] section of t
Website] for more info. For example, if you are using [Crossterm], you can use the
[`crossterm::event`] module to handle events.
```rust
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
### Example
fn handle_events() -> std::io::Result<bool> {
match event::read()? {
Event::Key(key) if key.kind == KeyEventKind::Press => match key.code {
KeyCode::Char('q') => return Ok(true),
// handle other key events
_ => {}
```rust
use std::io::{self, stdout};
use ratatui::{
backend::CrosstermBackend,
crossterm::{
event::{self, Event, KeyCode},
terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
},
// handle other events
_ => {}
ExecutableCommand,
},
widgets::{Block, Paragraph},
Frame, Terminal,
};
fn main() -> io::Result<()> {
enable_raw_mode()?;
stdout().execute(EnterAlternateScreen)?;
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
let mut should_quit = false;
while !should_quit {
terminal.draw(ui)?;
should_quit = handle_events()?;
}
disable_raw_mode()?;
stdout().execute(LeaveAlternateScreen)?;
Ok(())
}
fn handle_events() -> io::Result<bool> {
if event::poll(std::time::Duration::from_millis(50))? {
if let Event::Key(key) = event::read()? {
if key.kind == event::KeyEventKind::Press && key.code == KeyCode::Char('q') {
return Ok(true);
}
}
}
Ok(false)
}
fn ui(frame: &mut Frame) {
frame.render_widget(
Paragraph::new("Hello World!").block(Block::bordered().title("Greeting")),
frame.size(),
);
}
```
Running this example produces the following output:
![docsrs-hello]
## Layout
The library comes with a basic yet useful layout management object called [`Layout`] which
@@ -206,13 +193,16 @@ use ratatui::{
Frame,
};
fn draw(frame: &mut Frame) {
use Constraint::{Fill, Length, Min};
let vertical = Layout::vertical([Length(1), Min(0), Length(1)]);
let [title_area, main_area, status_area] = vertical.areas(frame.area());
let horizontal = Layout::horizontal([Fill(1); 2]);
let [left_area, right_area] = horizontal.areas(main_area);
fn ui(frame: &mut Frame) {
let [title_area, main_area, status_area] = Layout::vertical([
Constraint::Length(1),
Constraint::Min(0),
Constraint::Length(1),
])
.areas(frame.size());
let [left_area, right_area] =
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
.areas(main_area);
frame.render_widget(Block::bordered().title("Title Bar"), title_area);
frame.render_widget(Block::bordered().title("Status Bar"), status_area);
@@ -223,13 +213,7 @@ fn draw(frame: &mut Frame) {
Running this example produces the following output:
```text
Title Bar───────────────────────────────────
┌Left────────────────┐┌Right───────────────┐
│ ││ │
└────────────────────┘└────────────────────┘
Status Bar──────────────────────────────────
```
![docsrs-layout]
## Text and styling
@@ -252,8 +236,8 @@ use ratatui::{
Frame,
};
fn draw(frame: &mut Frame) {
let areas = Layout::vertical([Constraint::Length(1); 4]).split(frame.area());
fn ui(frame: &mut Frame) {
let areas = Layout::vertical([Constraint::Length(1); 4]).split(frame.size());
let line = Line::from(vec![
Span::raw("Hello "),
@@ -282,6 +266,10 @@ fn draw(frame: &mut Frame) {
}
```
Running this example produces the following output:
![docsrs-styling]
[Ratatui Website]: https://ratatui.rs/
[Installation]: https://ratatui.rs/installation/
[Rendering]: https://ratatui.rs/concepts/rendering/
@@ -293,7 +281,7 @@ fn draw(frame: &mut Frame) {
[Layout]: https://ratatui.rs/how-to/layout/
[Styling Text]: https://ratatui.rs/how-to/render/style-text/
[templates]: https://github.com/ratatui/templates/
[Examples]: https://github.com/ratatui/ratatui/tree/main/ratatui/examples/README.md
[Examples]: https://github.com/ratatui/ratatui/tree/main/examples/README.md
[Report a bug]: https://github.com/ratatui/ratatui/issues/new?labels=bug&projects=&template=bug_report.md
[Request a Feature]: https://github.com/ratatui/ratatui/issues/new?labels=enhancement&projects=&template=feature_request.md
[Create a Pull Request]: https://github.com/ratatui/ratatui/compare
@@ -304,6 +292,9 @@ fn draw(frame: &mut Frame) {
[Contributing]: https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md
[Breaking Changes]: https://github.com/ratatui/ratatui/blob/main/BREAKING-CHANGES.md
[FOSDEM 2024 talk]: https://www.youtube.com/watch?v=NU0q6NOLJ20
[docsrs-hello]: https://github.com/ratatui/ratatui/blob/c3c3c289b1eb8d562afb1931adb4dc719cd48490/examples/docsrs-hello.png?raw=true
[docsrs-layout]: https://github.com/ratatui/ratatui/blob/c3c3c289b1eb8d562afb1931adb4dc719cd48490/examples/docsrs-layout.png?raw=true
[docsrs-styling]: https://github.com/ratatui/ratatui/blob/c3c3c289b1eb8d562afb1931adb4dc719cd48490/examples/docsrs-styling.png?raw=true
[`Frame`]: terminal::Frame
[`render_widget`]: terminal::Frame::render_widget
[`Widget`]: widgets::Widget
@@ -342,36 +333,94 @@ fn draw(frame: &mut Frame) {
<!-- cargo-rdme end -->
## Contributing
## Status of this fork
In response to the original maintainer [**Florian Dehau**](https://github.com/fdehau)'s issue
regarding the [future of `tui-rs`](https://github.com/fdehau/tui-rs/issues/654), several members of
the community forked the project and created this crate. We look forward to continuing the work
started by Florian 🚀
In order to organize ourselves, we currently use a [Discord server](https://discord.gg/pMCEU9hNEj),
feel free to join and come chat! There is also a [Matrix](https://matrix.org/) bridge available at
[#ratatui:matrix.org](https://matrix.to/#/#ratatui:matrix.org).
We have also recently launched the [Ratatui Forum][Forum], For bugs and features, we rely on GitHub.
Please [Report a bug], [Request a Feature] or [Create a Pull Request].
While we do utilize Discord for coordinating, it's not essential for contributing. We have recently
launched the [Ratatui Forum][Forum], and our primary open-source workflow is centered around GitHub.
For bugs and features, we rely on GitHub. Please [Report a bug], [Request a Feature] or [Create a
Pull Request].
Please make sure you read the [contributing](./CONTRIBUTING.md) guidelines, especially if you are
interested in working on a PR or issue opened in the previous repository.
Please make sure you read the updated [contributing](./CONTRIBUTING.md) guidelines, especially if
you are interested in working on a PR or issue opened in the previous repository.
## Built with Ratatui
## Widgets
Ratatui has a number of built-in [widgets](https://docs.rs/ratatui/latest/ratatui/widgets/), as well
as many contributed by external contributors. Check out the [Showcase](https://ratatui.rs/showcase/)
section of the website, or the [awesome-ratatui](https://github.com/ratatui/awesome-ratatui) repo
for a curated list of awesome apps/libraries built with `ratatui`!
### Built in
The library comes with the following
[widgets](https://docs.rs/ratatui/latest/ratatui/widgets/index.html):
- [BarChart](https://docs.rs/ratatui/latest/ratatui/widgets/struct.BarChart.html)
- [Block](https://docs.rs/ratatui/latest/ratatui/widgets/block/struct.Block.html)
- [Calendar](https://docs.rs/ratatui/latest/ratatui/widgets/calendar/index.html)
- [Canvas](https://docs.rs/ratatui/latest/ratatui/widgets/canvas/struct.Canvas.html) which allows
rendering [points, lines, shapes and a world
map](https://docs.rs/ratatui/latest/ratatui/widgets/canvas/index.html)
- [Chart](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Chart.html)
- [Clear](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Clear.html)
- [Gauge](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Gauge.html)
- [List](https://docs.rs/ratatui/latest/ratatui/widgets/struct.List.html)
- [Paragraph](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Paragraph.html)
- [Scrollbar](https://docs.rs/ratatui/latest/ratatui/widgets/scrollbar/struct.Scrollbar.html)
- [Sparkline](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Sparkline.html)
- [Table](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Table.html)
- [Tabs](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Tabs.html)
Each widget has an associated example which can be found in the [Examples] folder. Run each example
with cargo (e.g. to run the gauge example `cargo run --example gauge`), and quit by pressing `q`.
You can also run all examples by running `cargo make run-examples` (requires `cargo-make` that can
be installed with `cargo install cargo-make`).
### Third-party libraries, bootstrapping templates and widgets
- [ansi-to-tui](https://github.com/uttarayan21/ansi-to-tui) — Convert ansi colored text to
`ratatui::text::Text`
- [color-to-tui](https://github.com/uttarayan21/color-to-tui) — Parse hex colors to
`ratatui::style::Color`
- [templates](https://github.com/ratatui/templates) — Starter templates for
bootstrapping a Rust TUI application with Ratatui & crossterm
- [tui-builder](https://github.com/jkelleyrtp/tui-builder) — Batteries-included MVC framework for
Tui-rs + Crossterm apps
- [tui-clap](https://github.com/kegesch/tui-clap-rs) — Use clap-rs together with Tui-rs
- [tui-log](https://github.com/kegesch/tui-log-rs) — Example of how to use logging with Tui-rs
- [tui-logger](https://github.com/gin66/tui-logger) — Logger and Widget for Tui-rs
- [tui-realm](https://github.com/veeso/tui-realm) — Tui-rs framework to build stateful applications
with a React/Elm inspired approach
- [tui-realm-treeview](https://github.com/veeso/tui-realm-treeview) — Treeview component for
Tui-realm
- [tui-rs-tree-widgets](https://github.com/EdJoPaTo/tui-rs-tree-widget) — Widget for tree data
structures.
- [tui-windows](https://github.com/markatk/tui-windows-rs) — Tui-rs abstraction to handle multiple
windows and their rendering
- [tui-textarea](https://github.com/rhysd/tui-textarea) — Simple yet powerful multi-line text editor
widget supporting several key shortcuts, undo/redo, text search, etc.
- [tui-input](https://github.com/sayanarijit/tui-input) — TUI input library supporting multiple
backends and tui-rs.
- [tui-term](https://github.com/a-kenji/tui-term) — A pseudoterminal widget library
that enables the rendering of terminal applications as ratatui widgets.
## Apps
Check out [awesome-ratatui](https://github.com/ratatui/awesome-ratatui) for a curated list of
awesome apps/libraries built with `ratatui`!
## Alternatives
You might want to checkout [Cursive](https://github.com/gyscos/Cursive) or
[iocraft](https://github.com/ccbrown/iocraft/) for an alternative solutions
You might want to checkout [Cursive](https://github.com/gyscos/Cursive) for an alternative solution
to build text user interfaces in Rust.
## Acknowledgments
None of this could be possible without [**Florian Dehau**](https://github.com/fdehau) who originally
created [tui-rs] which inspired many Rust TUIs.
Special thanks to [**Pavel Fomchenkov**](https://github.com/nawok) for his work in designing **an
awesome logo** for the ratatui project and ratatui organization.

View File

@@ -8,66 +8,58 @@
default_job = "check"
[jobs.check]
command = ["cargo", "check", "--all-features"]
command = ["cargo", "check", "--all-features", "--color", "always"]
need_stdout = false
[jobs.check-all]
command = ["cargo", "check", "--all-targets", "--all-features"]
command = ["cargo", "check", "--all-targets", "--all-features", "--color", "always"]
need_stdout = false
[jobs.check-crossterm]
command = [
"cargo",
"check",
"--all-targets",
"--no-default-features",
"--features",
"crossterm",
]
command = ["cargo", "check", "--color", "always", "--all-targets", "--no-default-features", "--features", "crossterm"]
need_stdout = false
[jobs.check-termion]
command = [
"cargo",
"check",
"--all-targets",
"--no-default-features",
"--features",
"termion",
]
command = ["cargo", "check", "--color", "always", "--all-targets", "--no-default-features", "--features", "termion"]
need_stdout = false
[jobs.check-termwiz]
command = [
"cargo",
"check",
"--all-targets",
"--no-default-features",
"--features",
"termwiz",
]
command = ["cargo", "check", "--color", "always", "--all-targets", "--no-default-features", "--features", "termwiz"]
need_stdout = false
[jobs.clippy]
command = ["cargo", "clippy", "--all-targets"]
command = [
"cargo", "clippy",
"--all-targets",
"--color", "always",
]
need_stdout = false
[jobs.test]
command = ["cargo", "test", "--all-features"]
command = [
"cargo", "test",
"--all-features",
"--color", "always",
"--", "--color", "always", # see https://github.com/Canop/bacon/issues/124
]
need_stdout = true
[jobs.test-unit]
command = ["cargo", "test", "--lib", "--all-features"]
command = [
"cargo", "test",
"--lib",
"--all-features",
"--color", "always",
"--", "--color", "always", # see https://github.com/Canop/bacon/issues/124
]
need_stdout = true
[jobs.doc]
command = [
"cargo",
"+nightly",
"doc",
"-Zunstable-options",
"-Zrustdoc-scrape-examples",
"cargo", "+nightly", "doc",
"-Zunstable-options", "-Zrustdoc-scrape-examples",
"--all-features",
"--color", "always",
"--no-deps",
]
env.RUSTDOCFLAGS = "--cfg docsrs"
@@ -77,12 +69,10 @@ need_stdout = false
# to the previous job
[jobs.doc-open]
command = [
"cargo",
"+nightly",
"doc",
"-Zunstable-options",
"-Zrustdoc-scrape-examples",
"cargo", "+nightly", "doc",
"-Zunstable-options", "-Zrustdoc-scrape-examples",
"--all-features",
"--color", "always",
"--no-deps",
"--open",
]
@@ -92,34 +82,19 @@ on_success = "job:doc" # so that we don't open the browser at each change
[jobs.coverage]
command = [
"cargo",
"llvm-cov",
"--lcov",
"--output-path",
"target/lcov.info",
"cargo", "llvm-cov",
"--lcov", "--output-path", "target/lcov.info",
"--all-features",
"--color", "always",
]
[jobs.coverage-unit-tests-only]
command = [
"cargo",
"llvm-cov",
"--lcov",
"--output-path",
"target/lcov.info",
"cargo", "llvm-cov",
"--lcov", "--output-path", "target/lcov.info",
"--lib",
"--all-features",
]
[jobs.hack]
command = [
"cargo",
"hack",
"test",
"--lib",
"--each-feature",
# "--all-targets",
"--workspace",
"--color", "always",
]
# You may define here keybindings that would be specific to
@@ -127,7 +102,7 @@ command = [
# Shortcuts to internal functions (scrolling, toggling, etc.)
# should go in your personal global prefs.toml file instead.
[keybindings]
ctrl-h = "job:hack"
# alt-m = "job:my-job"
ctrl-c = "job:check-crossterm"
ctrl-t = "job:check-termion"
ctrl-w = "job:check-termwiz"

View File

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

View File

@@ -2,7 +2,8 @@ use criterion::{criterion_group, Bencher, BenchmarkId, Criterion};
use rand::Rng;
use ratatui::{
buffer::Buffer,
layout::{Direction, Rect},
layout::Rect,
prelude::Direction,
widgets::{Bar, BarChart, BarGroup, Widget},
};
@@ -15,7 +16,7 @@ fn barchart(c: &mut Criterion) {
let data: Vec<Bar> = (0..data_count)
.map(|i| {
Bar::default()
.label(format!("B{i}"))
.label(format!("B{i}").into())
.value(rng.gen_range(0..data_count))
})
.collect();

View File

@@ -2,8 +2,10 @@ use criterion::{criterion_group, BatchSize, Bencher, Criterion};
use ratatui::{
buffer::Buffer,
layout::{Alignment, Rect},
text::Line,
widgets::{Block, Padding, Widget},
widgets::{
block::{Position, Title},
Block, Padding, Widget,
},
};
/// Benchmark for rendering a block.
@@ -30,7 +32,11 @@ fn block(c: &mut Criterion) {
&Block::bordered()
.padding(Padding::new(5, 5, 2, 2))
.title("test title")
.title_bottom(Line::from("bottom left title").alignment(Alignment::Right)),
.title(
Title::from("bottom left title")
.alignment(Alignment::Right)
.position(Position::Bottom),
),
|b, block| render(b, block, buffer_size),
);
}

View File

@@ -8,7 +8,12 @@ use ratatui::{
criterion::criterion_group!(benches, empty, filled, with_lines);
const fn rect(size: u16) -> Rect {
Rect::new(0, 0, size, size)
Rect {
x: 0,
y: 0,
width: size,
height: size,
}
}
fn empty(c: &mut Criterion) {

24
benches/main/rect.rs Normal file
View File

@@ -0,0 +1,24 @@
use criterion::{black_box, criterion_group, BenchmarkId, Criterion};
use ratatui::layout::Rect;
fn rect_rows_benchmark(c: &mut Criterion) {
let rect_sizes = vec![
Rect::new(0, 0, 1, 16),
Rect::new(0, 0, 1, 1024),
Rect::new(0, 0, 1, 65535),
];
let mut group = c.benchmark_group("rect_rows");
for rect in rect_sizes {
group.bench_with_input(BenchmarkId::new("rows", rect.height), &rect, |b, rect| {
b.iter(|| {
for row in rect.rows() {
// Perform any necessary operations on each row
black_box(row);
}
});
});
}
group.finish();
}
criterion_group!(benches, rect_rows_benchmark);

View File

@@ -30,9 +30,19 @@ body = """
{% macro commit(commit) -%}
- [{{ commit.id | truncate(length=7, end="") }}]({{ "https://github.com/ratatui/ratatui/commit/" ~ commit.id }}) \
*({{commit.scope | default(value = "uncategorized") | lower }})* {{ commit.message | upper_first | trim }}\
{% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%}\
{% if commit.remote.pr_number %} in [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}){%- endif %}\
{% if commit.github.username %} by @{{ commit.github.username }}{%- endif -%}\
{% if commit.github.pr_number %} in [#{{ commit.github.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.github.pr_number }}){%- endif %}\
{%- if commit.breaking %} [**breaking**]{% endif %}
{%- if commit.body %}\n\n{{ commit.body | indent(prefix=" > ", first=true, blank=true) }}
{%- endif %}
{%- for footer in commit.footers %}\n
{%- if footer.token != "Signed-off-by" and footer.token != "Co-authored-by" %}
>
{{ footer.token | indent(prefix=" > ", first=true, blank=true) }}
{{- footer.separator }}
{{- footer.value| indent(prefix=" > ", first=false, blank=true) }}
{%- endif %}
{%- endfor %}
{% endmacro -%}
{% for group, commits in commits | group_by(attribute="group") %}

View File

@@ -3,15 +3,9 @@ avoid-breaking-exported-api = false
# https://rust-lang.github.io/rust-clippy/master/index.html#/multiple_crate_versions
# ratatui -> bitflags v2.3
# termwiz -> wezterm-blob-leases -> mac_address -> nix -> bitflags v1.3.2
# (also, memoffset, syn, nix, strsim, windows-sys
# crossterm -> all the windows- deps https://github.com/ratatui/ratatui/pull/1064#issuecomment-2078848980
allowed-duplicate-crates = [
"bitflags",
"memoffset",
"nix",
"strsim",
"syn",
"windows-sys",
"windows-targets",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
@@ -20,5 +14,4 @@ allowed-duplicate-crates = [
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"unicode-width",
]

View File

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

View File

@@ -244,7 +244,7 @@ impl Widget for &PullRequestListWidget {
.block(block)
.highlight_spacing(HighlightSpacing::Always)
.highlight_symbol(">>")
.row_highlight_style(Style::new().on_blue());
.highlight_style(Style::new().on_blue());
StatefulWidget::render(table, area, buf, &mut state.table_state);
}

View File

@@ -189,7 +189,7 @@ impl Company {
fn vertical_revenue_bar(&self, revenue: u32) -> Bar {
let text_value = format!("{:.1}M", f64::from(revenue) / 1000.);
Bar::default()
.label(self.short_name)
.label(self.short_name.into())
.value(u64::from(revenue))
.text_value(text_value)
.style(self.color)

213
examples/block.rs Normal file
View File

@@ -0,0 +1,213 @@
//! # [Ratatui] Block example
//!
//! The latest version of this example is available in the [examples] folder in the repository.
//!
//! Please note that the examples are designed to be run against the `main` branch of the Github
//! repository. This means that you may not be able to compile with the latest release version on
//! crates.io, or the one that you have installed locally.
//!
//! See the [examples readme] for more information on finding examples that match the version of the
//! library you are using.
//!
//! [Ratatui]: https://github.com/ratatui/ratatui
//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
use color_eyre::Result;
use ratatui::{
crossterm::event::{self, Event, KeyCode, KeyEventKind},
layout::{Alignment, Constraint, Layout, Rect},
style::{Style, Stylize},
text::Line,
widgets::{
block::{Position, Title},
Block, BorderType, Borders, Padding, Paragraph, Wrap,
},
DefaultTerminal, Frame,
};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(draw)?;
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
break Ok(());
}
}
}
}
fn draw(frame: &mut Frame) {
let (title_area, layout) = calculate_layout(frame.area());
render_title(frame, title_area);
let paragraph = placeholder_paragraph();
render_borders(&paragraph, Borders::ALL, frame, layout[0][0]);
render_borders(&paragraph, Borders::NONE, frame, layout[0][1]);
render_borders(&paragraph, Borders::LEFT, frame, layout[1][0]);
render_borders(&paragraph, Borders::RIGHT, frame, layout[1][1]);
render_borders(&paragraph, Borders::TOP, frame, layout[2][0]);
render_borders(&paragraph, Borders::BOTTOM, frame, layout[2][1]);
render_border_type(&paragraph, BorderType::Plain, frame, layout[3][0]);
render_border_type(&paragraph, BorderType::Rounded, frame, layout[3][1]);
render_border_type(&paragraph, BorderType::Double, frame, layout[4][0]);
render_border_type(&paragraph, BorderType::Thick, frame, layout[4][1]);
render_styled_block(&paragraph, frame, layout[5][0]);
render_styled_borders(&paragraph, frame, layout[5][1]);
render_styled_title(&paragraph, frame, layout[6][0]);
render_styled_title_content(&paragraph, frame, layout[6][1]);
render_multiple_titles(&paragraph, frame, layout[7][0]);
render_multiple_title_positions(&paragraph, frame, layout[7][1]);
render_padding(&paragraph, frame, layout[8][0]);
render_nested_blocks(&paragraph, frame, layout[8][1]);
}
/// Calculate the layout of the UI elements.
///
/// Returns a tuple of the title area and the main areas.
fn calculate_layout(area: Rect) -> (Rect, Vec<Vec<Rect>>) {
let main_layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
let block_layout = Layout::vertical([Constraint::Max(4); 9]);
let [title_area, main_area] = main_layout.areas(area);
let main_areas = block_layout
.split(main_area)
.iter()
.map(|&area| {
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(area)
.to_vec()
})
.collect();
(title_area, main_areas)
}
fn render_title(frame: &mut Frame, area: Rect) {
frame.render_widget(
Paragraph::new("Block example. Press q to quit")
.dark_gray()
.alignment(Alignment::Center),
area,
);
}
fn placeholder_paragraph() -> Paragraph<'static> {
let text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
Paragraph::new(text.dark_gray()).wrap(Wrap { trim: true })
}
fn render_borders(paragraph: &Paragraph, border: Borders, frame: &mut Frame, area: Rect) {
let block = Block::new()
.borders(border)
.title(format!("Borders::{border:#?}"));
frame.render_widget(paragraph.clone().block(block), area);
}
fn render_border_type(
paragraph: &Paragraph,
border_type: BorderType,
frame: &mut Frame,
area: Rect,
) {
let block = Block::bordered()
.border_type(border_type)
.title(format!("BorderType::{border_type:#?}"));
frame.render_widget(paragraph.clone().block(block), area);
}
fn render_styled_borders(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
let block = Block::bordered()
.border_style(Style::new().blue().on_white().bold().italic())
.title("Styled borders");
frame.render_widget(paragraph.clone().block(block), area);
}
fn render_styled_block(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
let block = Block::bordered()
.style(Style::new().blue().on_white().bold().italic())
.title("Styled block");
frame.render_widget(paragraph.clone().block(block), area);
}
fn render_styled_title(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
let block = Block::bordered()
.title("Styled title")
.title_style(Style::new().blue().on_white().bold().italic());
frame.render_widget(paragraph.clone().block(block), area);
}
fn render_styled_title_content(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
let title = Line::from(vec![
"Styled ".blue().on_white().bold().italic(),
"title content".red().on_white().bold().italic(),
]);
let block = Block::bordered().title(title);
frame.render_widget(paragraph.clone().block(block), area);
}
fn render_multiple_titles(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
let block = Block::bordered()
.title("Multiple".blue().on_white().bold().italic())
.title("Titles".red().on_white().bold().italic());
frame.render_widget(paragraph.clone().block(block), area);
}
fn render_multiple_title_positions(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
let block = Block::bordered()
.title(
Title::from("top left")
.position(Position::Top)
.alignment(Alignment::Left),
)
.title(
Title::from("top center")
.position(Position::Top)
.alignment(Alignment::Center),
)
.title(
Title::from("top right")
.position(Position::Top)
.alignment(Alignment::Right),
)
.title(
Title::from("bottom left")
.position(Position::Bottom)
.alignment(Alignment::Left),
)
.title(
Title::from("bottom center")
.position(Position::Bottom)
.alignment(Alignment::Center),
)
.title(
Title::from("bottom right")
.position(Position::Bottom)
.alignment(Alignment::Right),
);
frame.render_widget(paragraph.clone().block(block), area);
}
fn render_padding(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
let block = Block::bordered()
.padding(Padding::new(5, 10, 1, 2))
.title("Padding");
frame.render_widget(paragraph.clone().block(block), area);
}
fn render_nested_blocks(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
let outer_block = Block::bordered().title("Outer block");
let inner_block = Block::bordered().title("Inner block");
let inner = outer_block.inner(area);
frame.render_widget(outer_block, area);
frame.render_widget(paragraph.clone().block(inner_block), inner);
}

View File

@@ -13,24 +13,16 @@
//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
use std::{
io::stdout,
time::{Duration, Instant},
};
use std::time::{Duration, Instant};
use color_eyre::Result;
use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture, KeyEventKind},
ExecutableCommand,
};
use itertools::Itertools;
use ratatui::{
crossterm::event::{self, Event, KeyCode, MouseEventKind},
layout::{Constraint, Layout, Position, Rect},
crossterm::event::{self, Event, KeyCode},
layout::{Constraint, Layout, Rect},
style::{Color, Stylize},
symbols::Marker,
widgets::{
canvas::{Canvas, Circle, Map, MapResolution, Points, Rectangle},
canvas::{Canvas, Circle, Map, MapResolution, Rectangle},
Block, Widget,
},
DefaultTerminal, Frame,
@@ -38,16 +30,13 @@ use ratatui::{
fn main() -> Result<()> {
color_eyre::install()?;
stdout().execute(EnableMouseCapture)?;
let terminal = ratatui::init();
let app_result = App::new().run(terminal);
ratatui::restore();
stdout().execute(DisableMouseCapture)?;
app_result
}
struct App {
exit: bool,
x: f64,
y: f64,
ball: Circle,
@@ -56,14 +45,11 @@ struct App {
vy: f64,
tick_count: u64,
marker: Marker,
points: Vec<Position>,
is_drawing: bool,
}
impl App {
const fn new() -> Self {
fn new() -> Self {
Self {
exit: false,
x: 0.0,
y: 0.0,
ball: Circle {
@@ -77,22 +63,25 @@ impl App {
vy: 1.0,
tick_count: 0,
marker: Marker::Dot,
points: vec![],
is_drawing: false,
}
}
pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
let tick_rate = Duration::from_millis(16);
let mut last_tick = Instant::now();
while !self.exit {
loop {
terminal.draw(|frame| self.draw(frame))?;
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
if event::poll(timeout)? {
match event::read()? {
Event::Key(key) => self.handle_key_press(key),
Event::Mouse(event) => self.handle_mouse_event(event),
_ => (),
if let Event::Key(key) = event::read()? {
match key.code {
KeyCode::Char('q') => break Ok(()),
KeyCode::Down | KeyCode::Char('j') => self.y += 1.0,
KeyCode::Up | KeyCode::Char('k') => self.y -= 1.0,
KeyCode::Right | KeyCode::Char('l') => self.x += 1.0,
KeyCode::Left | KeyCode::Char('h') => self.x -= 1.0,
_ => {}
}
}
}
@@ -101,32 +90,6 @@ impl App {
last_tick = Instant::now();
}
}
Ok(())
}
fn handle_key_press(&mut self, key: event::KeyEvent) {
if key.kind != KeyEventKind::Press {
return;
}
match key.code {
KeyCode::Char('q') => self.exit = true,
KeyCode::Down | KeyCode::Char('j') => self.y += 1.0,
KeyCode::Up | KeyCode::Char('k') => self.y -= 1.0,
KeyCode::Right | KeyCode::Char('l') => self.x += 1.0,
KeyCode::Left | KeyCode::Char('h') => self.x -= 1.0,
_ => {}
}
}
fn handle_mouse_event(&mut self, event: event::MouseEvent) {
match event.kind {
MouseEventKind::Down(_) => self.is_drawing = true,
MouseEventKind::Up(_) => self.is_drawing = false,
MouseEventKind::Drag(_) => {
self.points.push(Position::new(event.column, event.row));
}
_ => {}
}
}
fn on_tick(&mut self) {
@@ -163,12 +126,10 @@ impl App {
let horizontal =
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]);
let vertical = Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]);
let [left, right] = horizontal.areas(frame.area());
let [draw, map] = vertical.areas(left);
let [map, right] = horizontal.areas(frame.area());
let [pong, boxes] = vertical.areas(right);
frame.render_widget(self.map_canvas(), map);
frame.render_widget(self.draw_canvas(draw), draw);
frame.render_widget(self.pong_canvas(), pong);
frame.render_widget(self.boxes_canvas(boxes), boxes);
}
@@ -188,30 +149,6 @@ impl App {
.y_bounds([-90.0, 90.0])
}
fn draw_canvas(&self, area: Rect) -> impl Widget + '_ {
Canvas::default()
.block(Block::bordered().title("Draw here"))
.marker(self.marker)
.x_bounds([0.0, f64::from(area.width)])
.y_bounds([0.0, f64::from(area.height)])
.paint(move |ctx| {
let points = self
.points
.iter()
.map(|p| {
(
f64::from(p.x) - f64::from(area.left()),
f64::from(area.bottom()) - f64::from(p.y),
)
})
.collect_vec();
ctx.draw(&Points {
coords: &points,
color: Color::White,
});
})
}
fn pong_canvas(&self) -> impl Widget + '_ {
Canvas::default()
.block(Block::bordered().title("Pong"))

View File

@@ -18,11 +18,11 @@ use std::time::{Duration, Instant};
use color_eyre::Result;
use ratatui::{
crossterm::event::{self, Event, KeyCode},
layout::{Constraint, Layout, Rect},
layout::{Alignment, Constraint, Layout, Rect},
style::{Color, Modifier, Style, Stylize},
symbols::{self, Marker},
text::{Line, Span},
widgets::{Axis, Block, Chart, Dataset, GraphType, LegendPosition},
text::Span,
widgets::{block::Title, Axis, Block, Chart, Dataset, GraphType, LegendPosition},
DefaultTerminal, Frame,
};
@@ -196,7 +196,13 @@ fn render_barchart(frame: &mut Frame, bar_chart: Rect) {
]);
let chart = Chart::new(vec![dataset])
.block(Block::bordered().title_top(Line::from("Bar chart").cyan().bold().centered()))
.block(
Block::bordered().title(
Title::default()
.content("Bar chart".cyan().bold())
.alignment(Alignment::Center),
),
)
.x_axis(
Axis::default()
.style(Style::default().gray())
@@ -223,7 +229,13 @@ fn render_line_chart(frame: &mut Frame, area: Rect) {
.data(&[(1., 1.), (4., 4.)])];
let chart = Chart::new(datasets)
.block(Block::bordered().title(Line::from("Line chart").cyan().bold().centered()))
.block(
Block::bordered().title(
Title::default()
.content("Line chart".cyan().bold())
.alignment(Alignment::Center),
),
)
.x_axis(
Axis::default()
.title("X Axis")
@@ -267,7 +279,13 @@ fn render_scatter(frame: &mut Frame, area: Rect) {
];
let chart = Chart::new(datasets)
.block(Block::bordered().title(Line::from("Scatter chart").cyan().bold().centered()))
.block(
Block::bordered().title(
Title::default()
.content("Scatter chart".cyan().bold())
.alignment(Alignment::Center),
),
)
.x_axis(
Axis::default()
.title("Year")

View File

@@ -122,7 +122,7 @@ pub struct TabsState<'a> {
}
impl<'a> TabsState<'a> {
pub const fn new(titles: Vec<&'a str>) -> Self {
pub fn new(titles: Vec<&'a str>) -> Self {
Self { titles, index: 0 }
}
pub fn next(&mut self) {

View File

@@ -164,7 +164,7 @@ fn render_ingredients(selected_row: usize, area: Rect, buf: &mut Buffer) {
Table::new(rows, [Constraint::Length(7), Constraint::Length(30)])
.block(Block::new().style(theme.ingredients))
.header(Row::new(vec!["Qty", "Ingredient"]).style(theme.ingredients_header))
.row_highlight_style(Style::new().light_yellow()),
.highlight_style(Style::new().light_yellow()),
area,
buf,
&mut state,

View File

@@ -60,7 +60,7 @@ fn render_hops(selected_row: usize, area: Rect, buf: &mut Buffer) {
StatefulWidget::render(
Table::new(rows, [Constraint::Max(100), Constraint::Length(15)])
.header(Row::new(vec!["Host", "Address"]).set_style(THEME.traceroute.header))
.row_highlight_style(THEME.traceroute.selected)
.highlight_style(THEME.traceroute.selected)
.block(block),
area,
buf,
@@ -99,7 +99,7 @@ pub fn render_ping(progress: usize, area: Rect, buf: &mut Buffer) {
.title_alignment(Alignment::Center)
.border_type(BorderType::Thick),
)
.data(data)
.data(&data)
.style(THEME.traceroute.ping)
.render(area, buf);
}

View File

@@ -101,7 +101,7 @@ fn render_simple_barchart(area: Rect, buf: &mut Buffer) {
} else {
Style::new().fg(Color::DarkGray).bg(Color::Yellow).bold()
})
.label(label)
.label(label.into())
})
.collect_vec();
let group = BarGroup::default().bars(&data);
@@ -115,15 +115,15 @@ fn render_simple_barchart(area: Rect, buf: &mut Buffer) {
fn render_horizontal_barchart(area: Rect, buf: &mut Buffer) {
let bg = Color::Rgb(32, 48, 96);
let data = [
Bar::default().text_value("Winter 37-51").value(51),
Bar::default().text_value("Spring 40-65").value(65),
Bar::default().text_value("Summer 54-77").value(77),
Bar::default().text_value("Winter 37-51".into()).value(51),
Bar::default().text_value("Spring 40-65".into()).value(65),
Bar::default().text_value("Summer 54-77".into()).value(77),
Bar::default()
.text_value("Fall 41-71")
.text_value("Fall 41-71".into())
.value(71)
.value_style(Style::new().bold()), // current season
];
let group = BarGroup::default().label("GPU").bars(&data);
let group = BarGroup::default().label("GPU".into()).bars(&data);
BarChart::default()
.block(Block::new().padding(Padding::new(0, 0, 2, 0)))
.direction(Direction::Horizontal)

View File

@@ -28,8 +28,8 @@ use ratatui::{
symbols::{self, line},
text::{Line, Text},
widgets::{
Block, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget, Tabs,
Widget,
block::Title, Block, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState,
StatefulWidget, Tabs, Widget,
},
DefaultTerminal,
};
@@ -273,7 +273,7 @@ impl App {
fn tabs(self) -> impl Widget {
let tab_titles = SelectedTab::iter().map(SelectedTab::to_tab_title);
let block = Block::new()
.title("Flex Layouts ".bold())
.title(Title::from("Flex Layouts ".bold()))
.title(" Use ◄ ► to change tab, ▲ ▼ to scroll, - + to change spacing ");
Tabs::new(tab_titles)
.block(block)

View File

@@ -21,8 +21,8 @@ use ratatui::{
crossterm::event::{self, Event, KeyCode, KeyEventKind},
layout::{Alignment, Constraint, Layout, Rect},
style::{palette::tailwind, Color, Style, Stylize},
text::{Line, Span},
widgets::{Block, Borders, Gauge, Padding, Paragraph, Widget},
text::Span,
widgets::{block::Title, Block, Borders, Gauge, Padding, Paragraph, Widget},
DefaultTerminal,
};
@@ -196,7 +196,7 @@ impl App {
}
fn title_block(title: &str) -> Block {
let title = Line::from(title).centered();
let title = Title::from(title).alignment(Alignment::Center);
Block::new()
.borders(Borders::NONE)
.padding(Padding::vertical(1))

View File

@@ -25,11 +25,11 @@ use rand::distributions::{Distribution, Uniform};
use ratatui::{
backend::Backend,
crossterm::event,
layout::{Constraint, Layout, Rect},
layout::{Alignment, Constraint, Layout, Rect},
style::{Color, Modifier, Style},
symbols,
text::{Line, Span},
widgets::{Block, Gauge, LineGauge, List, ListItem, Paragraph, Widget},
widgets::{block, Block, Gauge, LineGauge, List, ListItem, Paragraph, Widget},
Frame, Terminal, TerminalOptions, Viewport,
};
@@ -231,7 +231,7 @@ fn run(
fn draw(frame: &mut Frame, downloads: &Downloads) {
let area = frame.area();
let block = Block::new().title(Line::from("Progress").centered());
let block = Block::new().title(block::Title::from("Progress").alignment(Alignment::Center));
frame.render_widget(block, area);
let vertical = Layout::vertical([Constraint::Length(2), Constraint::Length(4)]).margin(1);

View File

@@ -21,8 +21,7 @@ use ratatui::{
crossterm::event::{self, Event, KeyCode, KeyEventKind},
layout::{Alignment, Constraint, Layout, Rect},
style::{palette::tailwind, Color, Style, Stylize},
text::Line,
widgets::{Block, Borders, LineGauge, Padding, Paragraph, Widget},
widgets::{block::Title, Block, Borders, LineGauge, Padding, Paragraph, Widget},
DefaultTerminal,
};
@@ -171,8 +170,9 @@ impl App {
}
fn title_block(title: &str) -> Block {
let title = Title::from(title).alignment(Alignment::Center);
Block::default()
.title(Line::from(title).centered())
.title(title)
.borders(Borders::NONE)
.fg(CUSTOM_LABEL_COLOR)
.padding(Padding::vertical(1))

227
examples/metrics.rs Normal file
View File

@@ -0,0 +1,227 @@
use std::{
sync::{atomic::Ordering, Arc},
time::{Duration, Instant},
};
use color_eyre::Result;
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use itertools::Itertools;
use metrics::{Counter, Gauge, Histogram, Key, KeyName, Metadata, Recorder, SharedString, Unit};
use metrics_util::{
registry::{AtomicStorage, Registry},
Summary,
};
use ratatui::{
buffer::Buffer,
layout::{Constraint, Layout, Rect},
style::{palette::tailwind::SLATE, Stylize},
widgets::{Row, Table, Widget},
DefaultTerminal, Frame,
};
fn main() -> Result<()> {
color_eyre::install()?;
let recorder = MetricsRecorder::new();
let recorder_widget = recorder.widget();
recorder.install();
let terminal = ratatui::init();
let app = App::new(recorder_widget);
let result = app.run(terminal);
ratatui::restore();
result
}
#[derive(Debug)]
struct App {
should_quit: bool,
recorder_widget: RecorderWidget,
}
impl App {
const fn new(recorder_widget: RecorderWidget) -> Self {
Self {
should_quit: false,
recorder_widget,
}
}
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
let mut last_frame = Instant::now();
let frame_duration = Duration::from_secs_f64(1.0 / 60.0);
while !self.should_quit {
if last_frame.elapsed() >= frame_duration {
last_frame = Instant::now();
terminal.draw(|frame| self.draw(frame))?;
}
self.handle_events(frame_duration.saturating_sub(last_frame.elapsed()))?;
}
Ok(())
}
fn draw(&self, frame: &mut Frame) {
let [top, main] =
Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]).areas(frame.area());
let title = if cfg!(debug_assertions) {
"Metrics Example (debug)"
} else {
"Metrics Example (release)"
};
frame.render_widget(title.blue().into_centered_line(), top);
frame.render_widget(&self.recorder_widget, main);
}
fn handle_events(&mut self, timeout: Duration) -> Result<()> {
if !event::poll(timeout)? {
return Ok(());
}
match event::read()? {
Event::Key(key) if key.kind == KeyEventKind::Press => self.on_key_press(key),
_ => {}
}
Ok(())
}
fn on_key_press(&mut self, key: event::KeyEvent) {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => self.should_quit = true,
_ => {}
}
}
}
#[derive(Debug, Default)]
struct MetricsRecorder {
metrics: Arc<Metrics>,
}
impl MetricsRecorder {
fn new() -> Self {
Self::default()
}
fn widget(&self) -> RecorderWidget {
RecorderWidget {
metrics: Arc::clone(&self.metrics),
}
}
fn install(self) {
metrics::set_global_recorder(self).unwrap();
}
}
#[derive(Debug)]
struct Metrics {
registry: Registry<Key, AtomicStorage>,
}
impl Default for Metrics {
fn default() -> Self {
Self {
registry: Registry::atomic(),
}
}
}
impl Metrics {
fn counter(&self, key: &Key) -> Counter {
self.registry
.get_or_create_counter(key, |c| Counter::from_arc(c.clone()))
}
fn gauge(&self, key: &Key) -> Gauge {
self.registry
.get_or_create_gauge(key, |g| Gauge::from_arc(g.clone()))
}
fn histogram(&self, key: &Key) -> Histogram {
self.registry
.get_or_create_histogram(key, |h| Histogram::from_arc(h.clone()))
}
}
#[derive(Debug)]
struct RecorderWidget {
metrics: Arc<Metrics>,
}
impl Widget for &RecorderWidget {
fn render(self, area: Rect, buf: &mut Buffer)
where
Self: Sized,
{
let mut counters = vec![];
self.metrics.registry.visit_counters(|key, counter| {
let value = counter.load(Ordering::SeqCst);
counters.push((key.clone(), value.to_string()));
});
let mut gauges = vec![];
self.metrics.registry.visit_gauges(|key, gauge| {
let value = gauge.load(Ordering::SeqCst);
gauges.push((key.clone(), value.to_string()));
});
let mut histograms = vec![];
self.metrics.registry.visit_histograms(|key, histogram| {
let mut summary = Summary::with_defaults();
for data in histogram.data() {
summary.add(data);
}
if summary.is_empty() {
// we omit the empty histograms, but this is how you would render them
// histograms.push((key.clone(), "empty".to_string()));
} else {
let min = Duration::from_secs_f64(summary.min());
let max = Duration::from_secs_f64(summary.max());
let p50 = Duration::from_secs_f64(summary.quantile(0.5).unwrap());
let p90 = Duration::from_secs_f64(summary.quantile(0.9).unwrap());
let p99 = Duration::from_secs_f64(summary.quantile(0.99).unwrap());
let line = format!(
"min:{min:>9.2?} p50:{p50:>9.2?} p90:{p90:>9.2?} p99:{p99:>9.2?} max:{max:>9.2?}"
);
histograms.push((key.clone(), line));
}
});
counters.sort();
gauges.sort();
histograms.sort();
let lines = counters
.iter()
.chain(gauges.iter())
.chain(histograms.iter());
let row_colors = [SLATE.c950, SLATE.c900];
let rows = lines
.map(|(key, line)| Row::new([key.name(), line]))
.zip(row_colors.iter().cycle())
.map(|(row, style)| row.bg(*style))
.collect_vec();
Table::new(rows, [Constraint::Length(40), Constraint::Fill(1)]).render(area, buf);
}
}
#[allow(unused_variables)]
impl Recorder for MetricsRecorder {
fn describe_counter(&self, key: KeyName, unit: Option<Unit>, description: SharedString) {
// todo!()
}
fn describe_gauge(&self, key: KeyName, unit: Option<Unit>, description: SharedString) {
// todo!()
}
fn describe_histogram(&self, key: KeyName, unit: Option<Unit>, description: SharedString) {
// todo!()
}
fn register_counter(&self, key: &Key, metadata: &Metadata<'_>) -> Counter {
self.metrics.counter(key)
}
fn register_gauge(&self, key: &Key, metadata: &Metadata<'_>) -> Gauge {
self.metrics.gauge(key)
}
fn register_histogram(&self, key: &Key, metadata: &Metadata<'_>) -> Histogram {
self.metrics.histogram(key)
}
}

View File

@@ -4,8 +4,8 @@
//! this is not meant to be prescriptive. See the [examples] folder for more complete examples.
//! In particular, the [hello-world] example is a good starting point.
//!
//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
//! [hello-world]: https://github.com/ratatui/ratatui/blob/main/examples/hello_world.rs
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [hello-world]: https://github.com/ratatui-org/ratatui/blob/main/examples/hello_world.rs
//!
//! The latest version of this example is available in the [examples] folder in the repository.
//!
@@ -20,21 +20,21 @@
//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
use crossterm::event::{self, Event};
use ratatui::{text::Text, Frame};
use ratatui::{
crossterm::event::{self, Event},
text::Text,
Frame,
};
fn main() {
let mut terminal = ratatui::init();
loop {
terminal.draw(draw).expect("failed to draw frame");
terminal
.draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area()))
.expect("failed to draw frame");
if matches!(event::read().expect("failed to read event"), Event::Key(_)) {
break;
}
}
ratatui::restore();
}
fn draw(frame: &mut Frame) {
let text = Text::raw("Hello World!");
frame.render_widget(text, frame.area());
}

69
examples/ratatui-logo.rs Normal file
View File

@@ -0,0 +1,69 @@
//! # [Ratatui] Logo example
//!
//! The latest version of this example is available in the [examples] folder in the repository.
//!
//! Please note that the examples are designed to be run against the `main` branch of the Github
//! repository. This means that you may not be able to compile with the latest release version on
//! crates.io, or the one that you have installed locally.
//!
//! See the [examples readme] for more information on finding examples that match the version of the
//! library you are using.
//!
//! [Ratatui]: https://github.com/ratatui/ratatui
//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
use std::{
io::{self},
thread::sleep,
time::Duration,
};
use indoc::indoc;
use itertools::izip;
use ratatui::{widgets::Paragraph, TerminalOptions, Viewport};
/// A fun example of using half block characters to draw a logo
#[allow(clippy::many_single_char_names)]
fn logo() -> String {
let r = indoc! {"
▄▄▄
█▄▄▀
█ █
"};
let a = indoc! {"
▄▄
█▄▄█
█ █
"};
let t = indoc! {"
▄▄▄
"};
let u = indoc! {"
▄ ▄
█ █
▀▄▄▀
"};
let i = indoc! {"
"};
izip!(r.lines(), a.lines(), t.lines(), u.lines(), i.lines())
.map(|(r, a, t, u, i)| format!("{r:5}{a:5}{t:4}{a:5}{t:4}{u:5}{i:5}"))
.collect::<Vec<_>>()
.join("\n")
}
fn main() -> io::Result<()> {
let mut terminal = ratatui::init_with_options(TerminalOptions {
viewport: Viewport::Inline(3),
});
terminal.draw(|frame| frame.render_widget(Paragraph::new(logo()), frame.area()))?;
sleep(Duration::from_secs(5));
ratatui::restore();
println!();
Ok(())
}

View File

@@ -14,13 +14,12 @@
//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
use color_eyre::Result;
use crossterm::event::KeyModifiers;
use itertools::Itertools;
use ratatui::{
crossterm::event::{self, Event, KeyCode, KeyEventKind},
layout::{Constraint, Layout, Margin, Rect},
style::{self, Color, Modifier, Style, Stylize},
text::Text,
text::{Line, Text},
widgets::{
Block, BorderType, Cell, HighlightSpacing, Paragraph, Row, Scrollbar, ScrollbarOrientation,
ScrollbarState, Table, TableState,
@@ -36,10 +35,8 @@ const PALETTES: [tailwind::Palette; 4] = [
tailwind::INDIGO,
tailwind::RED,
];
const INFO_TEXT: [&str; 2] = [
"(Esc) quit | (↑) move up | (↓) move down | () move left | () move right",
"(Shift + →) next color | (Shift + ←) previous color",
];
const INFO_TEXT: &str =
"(Esc) quit | (↑) move up | (↓) move down | () next color | () previous color";
const ITEM_HEIGHT: usize = 4;
@@ -55,9 +52,7 @@ struct TableColors {
header_bg: Color,
header_fg: Color,
row_fg: Color,
selected_row_style_fg: Color,
selected_column_style_fg: Color,
selected_cell_style_fg: Color,
selected_style_fg: Color,
normal_row_color: Color,
alt_row_color: Color,
footer_border_color: Color,
@@ -70,9 +65,7 @@ impl TableColors {
header_bg: color.c900,
header_fg: tailwind::SLATE.c200,
row_fg: tailwind::SLATE.c200,
selected_row_style_fg: color.c400,
selected_column_style_fg: color.c400,
selected_cell_style_fg: color.c600,
selected_style_fg: color.c400,
normal_row_color: tailwind::SLATE.c950,
alt_row_color: tailwind::SLATE.c900,
footer_border_color: color.c400,
@@ -125,7 +118,8 @@ impl App {
items: data_vec,
}
}
pub fn next_row(&mut self) {
pub fn next(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i >= self.items.len() - 1 {
@@ -140,7 +134,7 @@ impl App {
self.scroll_state = self.scroll_state.position(i * ITEM_HEIGHT);
}
pub fn previous_row(&mut self) {
pub fn previous(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i == 0 {
@@ -155,14 +149,6 @@ impl App {
self.scroll_state = self.scroll_state.position(i * ITEM_HEIGHT);
}
pub fn next_column(&mut self) {
self.state.select_next_column();
}
pub fn previous_column(&mut self) {
self.state.select_previous_column();
}
pub fn next_color(&mut self) {
self.color_index = (self.color_index + 1) % PALETTES.len();
}
@@ -182,17 +168,12 @@ impl App {
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press {
let shift_pressed = key.modifiers.contains(KeyModifiers::SHIFT);
match key.code {
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
KeyCode::Char('j') | KeyCode::Down => self.next_row(),
KeyCode::Char('k') | KeyCode::Up => self.previous_row(),
KeyCode::Char('l') | KeyCode::Right if shift_pressed => self.next_color(),
KeyCode::Char('h') | KeyCode::Left if shift_pressed => {
self.previous_color();
}
KeyCode::Char('l') | KeyCode::Right => self.next_column(),
KeyCode::Char('h') | KeyCode::Left => self.previous_column(),
KeyCode::Char('j') | KeyCode::Down => self.next(),
KeyCode::Char('k') | KeyCode::Up => self.previous(),
KeyCode::Char('l') | KeyCode::Right => self.next_color(),
KeyCode::Char('h') | KeyCode::Left => self.previous_color(),
_ => {}
}
}
@@ -201,7 +182,7 @@ impl App {
}
fn draw(&mut self, frame: &mut Frame) {
let vertical = &Layout::vertical([Constraint::Min(5), Constraint::Length(4)]);
let vertical = &Layout::vertical([Constraint::Min(5), Constraint::Length(3)]);
let rects = vertical.split(frame.area());
self.set_colors();
@@ -215,13 +196,9 @@ impl App {
let header_style = Style::default()
.fg(self.colors.header_fg)
.bg(self.colors.header_bg);
let selected_row_style = Style::default()
let selected_style = Style::default()
.add_modifier(Modifier::REVERSED)
.fg(self.colors.selected_row_style_fg);
let selected_col_style = Style::default().fg(self.colors.selected_column_style_fg);
let selected_cell_style = Style::default()
.add_modifier(Modifier::REVERSED)
.fg(self.colors.selected_cell_style_fg);
.fg(self.colors.selected_style_fg);
let header = ["Name", "Address", "Email"]
.into_iter()
@@ -252,9 +229,7 @@ impl App {
],
)
.header(header)
.row_highlight_style(selected_row_style)
.column_highlight_style(selected_col_style)
.cell_highlight_style(selected_cell_style)
.highlight_style(selected_style)
.highlight_symbol(Text::from(vec![
"".into(),
bar.into(),
@@ -281,7 +256,7 @@ impl App {
}
fn render_footer(&self, frame: &mut Frame, area: Rect) {
let info_footer = Paragraph::new(Text::from_iter(INFO_TEXT))
let info_footer = Paragraph::new(Line::from(INFO_TEXT))
.style(
Style::new()
.fg(self.colors.row_fg)

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