Compare commits
5 Commits
v0.30.0-al
...
jm/metrics
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fe946b3e5 | ||
|
|
b5f2c0cef3 | ||
|
|
3b132e2768 | ||
|
|
904c950c59 | ||
|
|
5673bb5039 |
@@ -1,2 +0,0 @@
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,2 +0,0 @@
|
||||
github: ratatui
|
||||
open_collective: ratatui
|
||||
51
.github/workflows/bench_track_fork_pr.yml
vendored
51
.github/workflows/bench_track_fork_pr.yml
vendored
@@ -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"
|
||||
|
||||
138
.github/workflows/ci.yml
vendored
138
.github/workflows/ci.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
target
|
||||
Cargo.lock
|
||||
*.log
|
||||
*.rs.rustfmt
|
||||
.gdb_history
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
552
CHANGELOG.md
552
CHANGELOG.md
@@ -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)
|
||||
|
||||
> 
|
||||
>
|
||||
>
|
||||
> 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`
|
||||
|
||||
@@ -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
3953
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
407
Cargo.toml
407
Cargo.toml
@@ -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
154
Makefile.toml
Normal 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
331
README.md
@@ -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.
|
||||
|
||||
|
||||
97
bacon.toml
97
bacon.toml
@@ -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"
|
||||
|
||||
@@ -7,7 +7,6 @@ pub mod main {
|
||||
pub mod paragraph;
|
||||
pub mod rect;
|
||||
pub mod sparkline;
|
||||
pub mod table;
|
||||
}
|
||||
pub use main::*;
|
||||
|
||||
@@ -19,6 +18,5 @@ criterion::criterion_main!(
|
||||
list::benches,
|
||||
paragraph::benches,
|
||||
rect::benches,
|
||||
sparkline::benches,
|
||||
table::benches,
|
||||
sparkline::benches
|
||||
);
|
||||
@@ -2,7 +2,8 @@ use criterion::{criterion_group, Bencher, BenchmarkId, Criterion};
|
||||
use rand::Rng;
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Direction, Rect},
|
||||
layout::Rect,
|
||||
prelude::Direction,
|
||||
widgets::{Bar, BarChart, BarGroup, Widget},
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
@@ -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
24
benches/main/rect.rs
Normal 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);
|
||||
14
cliff.toml
14
cliff.toml
@@ -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") %}
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -11,7 +11,6 @@ allow = [
|
||||
"MIT",
|
||||
"Unicode-DFS-2016",
|
||||
"WTFPL",
|
||||
"Zlib",
|
||||
]
|
||||
|
||||
[advisories]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
213
examples/block.rs
Normal 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(¶graph, Borders::ALL, frame, layout[0][0]);
|
||||
render_borders(¶graph, Borders::NONE, frame, layout[0][1]);
|
||||
render_borders(¶graph, Borders::LEFT, frame, layout[1][0]);
|
||||
render_borders(¶graph, Borders::RIGHT, frame, layout[1][1]);
|
||||
render_borders(¶graph, Borders::TOP, frame, layout[2][0]);
|
||||
render_borders(¶graph, Borders::BOTTOM, frame, layout[2][1]);
|
||||
|
||||
render_border_type(¶graph, BorderType::Plain, frame, layout[3][0]);
|
||||
render_border_type(¶graph, BorderType::Rounded, frame, layout[3][1]);
|
||||
render_border_type(¶graph, BorderType::Double, frame, layout[4][0]);
|
||||
render_border_type(¶graph, BorderType::Thick, frame, layout[4][1]);
|
||||
|
||||
render_styled_block(¶graph, frame, layout[5][0]);
|
||||
render_styled_borders(¶graph, frame, layout[5][1]);
|
||||
render_styled_title(¶graph, frame, layout[6][0]);
|
||||
render_styled_title_content(¶graph, frame, layout[6][1]);
|
||||
render_multiple_titles(¶graph, frame, layout[7][0]);
|
||||
render_multiple_title_positions(¶graph, frame, layout[7][1]);
|
||||
render_padding(¶graph, frame, layout[8][0]);
|
||||
render_nested_blocks(¶graph, 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);
|
||||
}
|
||||
@@ -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"))
|
||||
@@ -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")
|
||||
@@ -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) {
|
||||
@@ -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,
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
@@ -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);
|
||||
@@ -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
227
examples/metrics.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
69
examples/ratatui-logo.rs
Normal 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(())
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user