Compare commits
3 Commits
jm/msrv-1.
...
docs/updat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6d580bc96 | ||
|
|
392b28c194 | ||
|
|
5814c7c395 |
25
.github/workflows/bench_base.yml
vendored
Normal file
25
.github/workflows/bench_base.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Run Benchmarks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
benchmark_base_branch:
|
||||
name: Continuous Benchmarking with Bencher
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- uses: bencherdev/bencher@main
|
||||
- name: Track base branch benchmarks with Bencher
|
||||
run: |
|
||||
bencher run \
|
||||
--project ratatui-org \
|
||||
--token '${{ secrets.BENCHER_API_TOKEN }}' \
|
||||
--branch main \
|
||||
--testbed ubuntu-latest \
|
||||
--adapter rust_criterion \
|
||||
--err \
|
||||
cargo bench
|
||||
25
.github/workflows/bench_run_fork_pr.yml
vendored
Normal file
25
.github/workflows/bench_run_fork_pr.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Run and Cache Benchmarks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, edited, synchronize]
|
||||
|
||||
jobs:
|
||||
benchmark_fork_pr_branch:
|
||||
name: Run Fork PR Benchmarks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Run Benchmarks
|
||||
run: cargo bench > benchmark_results.txt
|
||||
- name: Upload Benchmark Results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: benchmark_results.txt
|
||||
path: ./benchmark_results.txt
|
||||
- name: Upload GitHub Pull Request Event
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: event.json
|
||||
path: ${{ github.event_path }}
|
||||
56
.github/workflows/bench_track_fork_pr.yml
vendored
Normal file
56
.github/workflows/bench_track_fork_pr.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Track Benchmarks with Bencher
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [Run and Cache Benchmarks]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
track_fork_pr_branch:
|
||||
if: github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BENCHMARK_RESULTS: benchmark_results.txt
|
||||
PR_EVENT: event.json
|
||||
steps:
|
||||
- name: Download Benchmark Results
|
||||
uses: dawidd6/action-download-artifact@v8
|
||||
with:
|
||||
name: ${{ env.BENCHMARK_RESULTS }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
- name: Download PR Event
|
||||
uses: dawidd6/action-download-artifact@v8
|
||||
with:
|
||||
name: ${{ env.PR_EVENT }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
- 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_BASE", prEvent.pull_request.base.ref);
|
||||
core.exportVariable("PR_BASE_SHA", prEvent.pull_request.base.sha);
|
||||
core.exportVariable("PR_NUMBER", prEvent.number);
|
||||
- uses: bencherdev/bencher@main
|
||||
- name: Track Benchmarks with Bencher
|
||||
run: |
|
||||
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 \
|
||||
--testbed ubuntu-latest \
|
||||
--adapter rust_criterion \
|
||||
--err \
|
||||
--github-actions '${{ secrets.GITHUB_TOKEN }}' \
|
||||
--ci-number "$PR_NUMBER" \
|
||||
--file "$BENCHMARK_RESULTS"
|
||||
38
.github/workflows/ci.yml
vendored
38
.github/workflows/ci.yml
vendored
@@ -61,27 +61,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: bnjbvr/cargo-machete@v0.8.0
|
||||
- uses: bnjbvr/cargo-machete@v0.7.0
|
||||
|
||||
# Run cargo clippy.
|
||||
#
|
||||
# We check for clippy warnings on beta, but these are not hard failures. They should often be
|
||||
# fixed to prevent clippy failing on the next stable release, but don't block PRs on them unless
|
||||
# they are introduced by the PR.
|
||||
lint-clippy:
|
||||
name: Check Clippy
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
toolchain: ["stable", "beta"]
|
||||
continue-on-error: ${{ matrix.toolchain == 'beta' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
components: clippy
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with: { components: clippy }
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo xtask clippy
|
||||
|
||||
@@ -122,7 +111,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
toolchain: ["1.82.0", "stable"]
|
||||
toolchain: ["1.74.0", "stable"]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -132,23 +121,6 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo xtask check --all-features
|
||||
|
||||
build-no-std:
|
||||
name: Build No-Std
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: x86_64-unknown-none
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# This makes it easier to debug the exact versions of the dependencies
|
||||
- run: cargo tree --target x86_64-unknown-none -p ratatui-core
|
||||
- run: cargo tree --target x86_64-unknown-none -p ratatui-widgets
|
||||
- run: cargo tree --target x86_64-unknown-none -p ratatui --no-default-features
|
||||
- run: cargo build --target x86_64-unknown-none -p ratatui-core
|
||||
- run: cargo build --target x86_64-unknown-none -p ratatui-widgets
|
||||
- run: cargo build --target x86_64-unknown-none -p ratatui --no-default-features
|
||||
|
||||
# Check if README.md is up-to-date with the crate's documentation.
|
||||
check-readme:
|
||||
name: Check README
|
||||
@@ -191,7 +163,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
toolchain: ["1.82.0", "stable"]
|
||||
toolchain: ["1.74.0", "stable"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
3
.github/workflows/release-plz.yml
vendored
3
.github/workflows/release-plz.yml
vendored
@@ -8,14 +8,12 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
# Release unpublished packages.
|
||||
release-plz-release:
|
||||
name: Release-plz release
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'ratatui' }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -35,7 +33,6 @@ jobs:
|
||||
release-plz-pr:
|
||||
name: Release-plz PR
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'ratatui' }}
|
||||
concurrency:
|
||||
group: release-plz-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
@@ -10,17 +10,9 @@ GitHub with a [breaking change] label.
|
||||
|
||||
This is a quick summary of the sections below:
|
||||
|
||||
- [v0.30.0 Unreleased](#v0300-unreleased)
|
||||
- [Unreleased](#unreleased)
|
||||
- The `From` impls for backend types are now replaced with more specific traits
|
||||
- `FrameExt` trait for `unstable-widget-ref` feature
|
||||
- `List::highlight_symbol` now accepts `Into<Line>` instead of `&str`
|
||||
- 'layout::Alignment' is renamed to 'layout::HorizontalAlignment'
|
||||
- The MSRV is now 1.81.0
|
||||
- `Backend` now requires an associated `Error` type and `clear_region` method
|
||||
- `TestBackend` now uses `core::convert::Infallible` for error handling instead of `std::io::Error`
|
||||
- Disabling `default-features` will now disable layout cache, which can have a negative impact on performance
|
||||
- `Layout::init_cache` and `Layout::DEFAULT_CACHE_SIZE` are now only available if `layout-cache` feature is enabled
|
||||
- Disabling `default-features` suppresses the error message if `show_cursor()` fails when dropping `Terminal`
|
||||
- [v0.29.0](#v0290)
|
||||
- `Sparkline::data` takes `IntoIterator<Item = SparklineBar>` instead of `&[u64]` and is no longer const
|
||||
- Removed public fields from `Rect` iterators
|
||||
@@ -83,104 +75,7 @@ This is a quick summary of the sections below:
|
||||
- MSRV is now 1.63.0
|
||||
- `List` no longer ignores empty strings
|
||||
|
||||
## v0.30.0 Unreleased
|
||||
|
||||
### `Style` no longer implements `Styled` ([#1572])
|
||||
|
||||
[#1572]: https://github.com/ratatui/ratatui/pull/1572
|
||||
|
||||
Any calls to methods implemented by the blanket implementation of `Stylize` are now defined directly
|
||||
on `Style`. Remove the `Stylize` import if it is no longer used by your code.
|
||||
|
||||
```diff
|
||||
- use ratatui::style::Stylize;
|
||||
|
||||
let style = Style::new().red();
|
||||
```
|
||||
|
||||
The `reset()` method does not have a direct replacement, as it clashes with the existing `reset()`
|
||||
method. Use the `Style::reset()` method instead.
|
||||
|
||||
```diff
|
||||
- some_style.reset();
|
||||
+ Style::reset();
|
||||
```
|
||||
|
||||
### Disabling `default-features` suppresses the error message if `show_cursor()` fails when dropping `Terminal` ([#1794])
|
||||
|
||||
[#1794]: https://github.com/ratatui/ratatui/pull/1794
|
||||
|
||||
Since disabling `default-features` disables `std`, printing to stderr is not possible. It is
|
||||
recommended to re-enable `std` when not using Ratatui in `no_std` environment.
|
||||
|
||||
### `Layout::init_cache` and `Layout::DEFAULT_CACHE_SIZE` are now only available if `layout-cache` feature is enabled ([#1795])
|
||||
|
||||
[#1795]: https://github.com/ratatui/ratatui/pull/1795
|
||||
|
||||
Previously, `Layout::init_cache` and `Layout::DEFAULT_CACHE_SIZE` were available independently of
|
||||
enabled feature flags.
|
||||
|
||||
### Disabling `default-features` will now disable layout cache, which can have a negative impact on performance ([#1795])
|
||||
|
||||
Layout cache is now opt-in in `ratatui-core` and enabled by default in `ratatui`. If app doesn't
|
||||
make use of `no_std`-compatibility, and disables `default-feature`, it is recommended to explicitly
|
||||
re-enable layout cache. Not doing so may impact performance.
|
||||
|
||||
```diff
|
||||
- ratatui = { version = "0.29.0", default-features = false }
|
||||
+ ratatui = { version = "0.30.0", default-features = false, features = ["layout-cache"] }
|
||||
```
|
||||
|
||||
### `TestBackend` now uses `core::convert::Infallible` for error handling instead of `std::io::Error` ([#1823])
|
||||
|
||||
[#1823]: https://github.com/ratatui/ratatui/pull/1823
|
||||
|
||||
Since `TestBackend` never fails, it now uses `Infallible` as associated `Error`. This may require
|
||||
changes in test cases that use `TestBackend`.
|
||||
|
||||
### `Backend` now requires an associated `Error` type and `clear_region` method ([#1778])
|
||||
|
||||
[#1778]: https://github.com/ratatui/ratatui/pull/1778
|
||||
|
||||
Custom `Backend` implementations must now define an associated `Error` type for method `Result`s
|
||||
and implement the `clear_region` method, which no longer has a default implementation.
|
||||
|
||||
This change was made to provide greater flexibility for custom backends, particularly to remove the
|
||||
explicit dependency on `std::io` for backends that want to support `no_std` targets.
|
||||
|
||||
### The MSRV is now 1.81.0 ([#1786])
|
||||
|
||||
[#1786]: https://github.com/ratatui/ratatui/pull/1786
|
||||
|
||||
The minimum supported Rust version (MSRV) is now 1.81.0. This is due to the use of `#[expect]` in
|
||||
the codebase, which is only available in Rust 1.81.0 and later.
|
||||
|
||||
### `layout::Alignment` is renamed to `layout::HorizontalAlignment` ([#1735])
|
||||
|
||||
[#1735]: https://github.com/ratatui/ratatui/pull/1691
|
||||
|
||||
The `Alignment` enum has been renamed to `HorizontalAlignment` to better reflect its purpose. A type
|
||||
alias has been added to maintain backwards compatibility, however there are some cases where type
|
||||
aliases are not enough to maintain backwards compatibility. E.g. when using glob imports to import
|
||||
all the enum variants.
|
||||
|
||||
We don't expect to remove or deprecate the type alias in the near future, but it is recommended to
|
||||
update your imports to use the new name.
|
||||
|
||||
```diff
|
||||
- use ratatui::layout::Alignment;
|
||||
+ use ratatui::layout::HorizontalAlignment;
|
||||
|
||||
- use Alignment::*;
|
||||
+ use HorizontalAlignment::*;
|
||||
```
|
||||
|
||||
### `List::highlight_symbol` accepts `Into<Line>` ([#1595])
|
||||
|
||||
[#1595]: https://github.com/ratatui/ratatui/pull/1595
|
||||
|
||||
Previously `List::highlight_symbol` accepted `&str`. Any code that uses conversion methods will need
|
||||
to be rewritten. Since `Into::into` is not const, this function cannot be called in const context.
|
||||
## Unreleased (0.30.0)
|
||||
|
||||
### `FrameExt` trait for `unstable-widget-ref` feature ([#1530])
|
||||
|
||||
@@ -283,15 +178,6 @@ for `Bar::text_value()`:
|
||||
+ Bar::default().text_value("foobar");
|
||||
```
|
||||
|
||||
### `termwiz` is upgraded to 0.23.0 ([#1682])
|
||||
|
||||
[#1682]: https://github.com/ratatui/ratatui/pull/1682
|
||||
|
||||
The `termwiz` backend is upgraded from 0.22.0 to 0.23.0.
|
||||
|
||||
This release has a few fixes for hyperlinks and input handling, plus some dependency updates.
|
||||
See the [commits](https://github.com/wezterm/wezterm/commits/main/termwiz) for more details.
|
||||
|
||||
## [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])
|
||||
|
||||
1874
CHANGELOG.md
1874
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -32,14 +32,7 @@ guarantee that the behavior is unchanged.
|
||||
### Code formatting
|
||||
|
||||
Run `cargo xtask format` before committing to ensure that code is consistently formatted with
|
||||
rustfmt. Configuration is in [`rustfmt.toml`](./rustfmt.toml). We use some unstable formatting
|
||||
options as they lead to subjectively better formatting. These require a nightly version of Rust
|
||||
to be installed when running rustfmt. You can install the nightly version of Rust using
|
||||
[`rustup`](https://rustup.rs/):
|
||||
|
||||
```shell
|
||||
rustup install nightly
|
||||
```
|
||||
rustfmt. Configuration is in [`rustfmt.toml`](./rustfmt.toml).
|
||||
|
||||
### Search `tui-rs` for similar work
|
||||
|
||||
|
||||
1240
Cargo.lock
generated
1240
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
66
Cargo.toml
66
Cargo.toml
@@ -24,51 +24,31 @@ readme = "README.md"
|
||||
license = "MIT"
|
||||
exclude = ["assets/*", ".github", "Makefile.toml", "CONTRIBUTING.md", "*.log", "tags"]
|
||||
edition = "2021"
|
||||
rust-version = "1.82.0"
|
||||
rust-version = "1.74.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
anstyle = "1"
|
||||
bitflags = "2.9"
|
||||
color-eyre = "0.6"
|
||||
compact_str = { version = "0.9", default-features = false }
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
crossterm = "0.29"
|
||||
document-features = "0.2"
|
||||
fakeit = "1"
|
||||
futures = "0.3"
|
||||
hashbrown = "0.15"
|
||||
indoc = "2"
|
||||
instability = "0.3"
|
||||
itertools = { version = "0.14", default-features = false, features = ["use_alloc"] }
|
||||
kasuari = { version = "0.4", default-features = false }
|
||||
line-clipping = "0.3"
|
||||
lru = "0.14"
|
||||
palette = "0.7"
|
||||
pretty_assertions = "1"
|
||||
rand = "0.9"
|
||||
rand_chacha = "0.9"
|
||||
ratatui = { path = "ratatui", version = "0.30.0-alpha.3" }
|
||||
ratatui-core = { path = "ratatui-core", version = "0.1.0-alpha.4" }
|
||||
ratatui-crossterm = { path = "ratatui-crossterm", version = "0.1.0-alpha.3" }
|
||||
ratatui-macros = { path = "ratatui-macros", version = "0.7.0-alpha.2" }
|
||||
ratatui-termion = { path = "ratatui-termion", version = "0.1.0-alpha.3" }
|
||||
ratatui-termwiz = { path = "ratatui-termwiz", version = "0.1.0-alpha.3" }
|
||||
ratatui-widgets = { path = "ratatui-widgets", version = "0.3.0-alpha.3" }
|
||||
rstest = "0.25"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
strum = { version = "0.27", default-features = false, features = ["derive"] }
|
||||
termion = "4"
|
||||
termwiz = "0.23"
|
||||
thiserror = { version = "2", default-features = false }
|
||||
time = { version = "0.3", default-features = false }
|
||||
tokio = "1"
|
||||
tracing = "0.1"
|
||||
tracing-appender = "0.2"
|
||||
tracing-subscriber = "0.3"
|
||||
trybuild = "1"
|
||||
unicode-segmentation = "1"
|
||||
unicode-truncate = { version = "2", default-features = false }
|
||||
bitflags = "2.7.0"
|
||||
color-eyre = "0.6.3"
|
||||
crossterm = "0.28.1"
|
||||
document-features = "0.2.7"
|
||||
indoc = "2.0.5"
|
||||
instability = "0.3.7"
|
||||
itertools = "0.13.0"
|
||||
pretty_assertions = "1.4.1"
|
||||
ratatui = { path = "ratatui", version = "0.30.0-alpha.1" }
|
||||
ratatui-core = { path = "ratatui-core", version = "0.1.0-alpha.2" }
|
||||
ratatui-crossterm = { path = "ratatui-crossterm", version = "0.1.0-alpha.1" }
|
||||
ratatui-macros = { path = "ratatui-macros", version = "0.7.0-alpha.0" }
|
||||
ratatui-termion = { path = "ratatui-termion", version = "0.1.0-alpha.1" }
|
||||
ratatui-termwiz = { path = "ratatui-termwiz", version = "0.1.0-alpha.1" }
|
||||
ratatui-widgets = { path = "ratatui-widgets", version = "0.3.0-alpha.1" }
|
||||
rstest = "0.24.0"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
serde_json = "1.0.138"
|
||||
strum = { version = "0.26.3", features = ["derive"] }
|
||||
termion = "4.0.0"
|
||||
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"
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ actions](.github/workflows/cd.yml) and triggered by pushing a tag.
|
||||
## Alpha Releases
|
||||
|
||||
Alpha releases are automatically released every Saturday via [cd.yml](./.github/workflows/cd.yml)
|
||||
and can be manually created when necessary by triggering the [Continuous
|
||||
and can be manually be created when necessary by triggering the [Continuous
|
||||
Deployment](https://github.com/ratatui/ratatui/actions/workflows/cd.yml) workflow.
|
||||
|
||||
We automatically release an alpha release with a patch level bump + alpha.num weekly (and when we
|
||||
|
||||
23
cliff.toml
23
cliff.toml
@@ -33,16 +33,6 @@ body = """
|
||||
{% 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.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") %}
|
||||
@@ -69,9 +59,14 @@ body = """
|
||||
https://github.com/{{ remote.owner }}/{{ remote.repo }}\
|
||||
{% endmacro %}
|
||||
"""
|
||||
|
||||
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = false
|
||||
# postprocessors for the changelog body
|
||||
# changelog footer
|
||||
footer = """
|
||||
<!-- generated by git-cliff -->
|
||||
"""
|
||||
postprocessors = [
|
||||
{ pattern = '<!-- Please read CONTRIBUTING.md before submitting any pull request. -->', replace = "" },
|
||||
{ pattern = '>---+\n', replace = '' },
|
||||
@@ -92,15 +87,9 @@ commit_preprocessors = [
|
||||
{ pattern = '(Clarify README.md)', replace = "docs(readme): ${1}" },
|
||||
{ pattern = '(Update README.md)', replace = "docs(readme): ${1}" },
|
||||
{ pattern = '(fix typos|Fix typos)', replace = "fix: ${1}" },
|
||||
# a small typo that squeaked through and which would otherwise trigger the typos linter.
|
||||
# Regex obsfucation is to avoid triggering the linter in this file until there's a per file config
|
||||
# See https://github.com/crate-ci/typos/issues/724
|
||||
{ pattern = '\<[d]eatil\>', replace = "detail" },
|
||||
]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
# release-plz adds 000000 as a placeholder for release commits
|
||||
{ field = "id", pattern = "0000000", skip = true },
|
||||
{ message = "^feat", group = "<!-- 00 -->Features" },
|
||||
{ message = "^[fF]ix", group = "<!-- 01 -->Bug Fixes" },
|
||||
{ message = "^refactor", group = "<!-- 02 -->Refactor" },
|
||||
|
||||
@@ -12,12 +12,14 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Layout, Position, Rect, Size};
|
||||
use ratatui::style::{Color, Style};
|
||||
use ratatui::widgets::{Widget, WidgetRef};
|
||||
use ratatui::DefaultTerminal;
|
||||
use crossterm::event::{self, Event, KeyCode};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Layout, Position, Rect, Size},
|
||||
style::{Color, Style},
|
||||
widgets::{Widget, WidgetRef},
|
||||
DefaultTerminal,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
@@ -55,8 +57,11 @@ impl App {
|
||||
if !event::poll(timeout)? {
|
||||
return Ok(());
|
||||
}
|
||||
if event::read()?.is_key_press() {
|
||||
self.should_quit = true;
|
||||
if let Event::Key(key) = event::read()? {
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.should_quit = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "async-github"
|
||||
publish = false
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
documentation.workspace = true
|
||||
repository.workspace = true
|
||||
@@ -16,7 +16,7 @@ rust-version.workspace = true
|
||||
[dependencies]
|
||||
color-eyre = "0.6.3"
|
||||
crossterm = { workspace = true, features = ["event-stream"] }
|
||||
octocrab = "0.44.0"
|
||||
octocrab = "0.43.0"
|
||||
ratatui.workspace = true
|
||||
tokio = { version = "1.44.2", features = ["rt-multi-thread", "macros"] }
|
||||
tokio = { version = "1.43.0", features = ["rt-multi-thread", "macros"] }
|
||||
tokio-stream = "0.1.17"
|
||||
|
||||
@@ -27,20 +27,25 @@
|
||||
//! [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::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
sync::{Arc, RwLock},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{Event, EventStream, KeyCode};
|
||||
use octocrab::params::pulls::Sort;
|
||||
use octocrab::params::Direction;
|
||||
use octocrab::Page;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Style, Stylize};
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::{Block, HighlightSpacing, Row, StatefulWidget, Table, TableState, Widget};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use octocrab::{
|
||||
params::{pulls::Sort, Direction},
|
||||
Page,
|
||||
};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
crossterm::event::{Event, EventStream, KeyCode, KeyEventKind},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{Style, Stylize},
|
||||
text::Line,
|
||||
widgets::{Block, HighlightSpacing, Row, StatefulWidget, Table, TableState, Widget},
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
#[tokio::main]
|
||||
@@ -70,14 +75,14 @@ impl App {
|
||||
|
||||
while !self.should_quit {
|
||||
tokio::select! {
|
||||
_ = interval.tick() => { terminal.draw(|frame| self.render(frame))?; },
|
||||
_ = interval.tick() => { terminal.draw(|frame| self.draw(frame))?; },
|
||||
Some(Ok(event)) = events.next() => self.handle_event(&event),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render(&self, frame: &mut Frame) {
|
||||
fn draw(&self, frame: &mut Frame) {
|
||||
let vertical = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]);
|
||||
let [title_area, body_area] = vertical.areas(frame.area());
|
||||
let title = Line::from("Ratatui async example").centered().bold();
|
||||
@@ -86,12 +91,14 @@ impl App {
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, event: &Event) {
|
||||
if let Some(key) = event.as_key_press_event() {
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.should_quit = true,
|
||||
KeyCode::Char('j') | KeyCode::Down => self.pull_requests.scroll_down(),
|
||||
KeyCode::Char('k') | KeyCode::Up => self.pull_requests.scroll_up(),
|
||||
_ => {}
|
||||
if let Event::Key(key) = event {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.should_quit = true,
|
||||
KeyCode::Char('j') | KeyCode::Down => self.pull_requests.scroll_down(),
|
||||
KeyCode::Char('k') | KeyCode::Up => self.pull_requests.scroll_up(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ rust-version.workspace = true
|
||||
color-eyre.workspace = true
|
||||
crossterm.workspace = true
|
||||
ratatui.workspace = true
|
||||
time = { version = "0.3.39", features = ["formatting", "parsing"] }
|
||||
time = { version = "0.3.37", features = ["formatting", "parsing"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -11,14 +11,15 @@
|
||||
use std::fmt;
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode};
|
||||
use ratatui::layout::{Constraint, Layout, Margin, Rect};
|
||||
use ratatui::style::{Color, Modifier, Style, Stylize};
|
||||
use ratatui::text::{Line, Text};
|
||||
use ratatui::widgets::calendar::{CalendarEventStore, Monthly};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use time::ext::NumericalDuration;
|
||||
use time::{Date, Month, OffsetDateTime};
|
||||
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
|
||||
use ratatui::{
|
||||
layout::{Constraint, Layout, Margin, Rect},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
text::{Line, Text},
|
||||
widgets::calendar::{CalendarEventStore, Monthly},
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
use time::{ext::NumericalDuration, Date, Month, OffsetDateTime};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
@@ -34,17 +35,21 @@ fn run(mut terminal: DefaultTerminal) -> Result<()> {
|
||||
let mut calendar_style = StyledCalendar::Default;
|
||||
loop {
|
||||
terminal.draw(|frame| render(frame, calendar_style, selected_date))?;
|
||||
if let Some(key) = event::read()?.as_key_press_event() {
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
|
||||
KeyCode::Char('s') => calendar_style = calendar_style.next(),
|
||||
KeyCode::Char('n') | KeyCode::Tab => selected_date = next_month(selected_date),
|
||||
KeyCode::Char('p') | KeyCode::BackTab => selected_date = prev_month(selected_date),
|
||||
KeyCode::Char('h') | KeyCode::Left => selected_date -= 1.days(),
|
||||
KeyCode::Char('j') | KeyCode::Down => selected_date += 1.weeks(),
|
||||
KeyCode::Char('k') | KeyCode::Up => selected_date -= 1.weeks(),
|
||||
KeyCode::Char('l') | KeyCode::Right => selected_date += 1.days(),
|
||||
_ => {}
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
match key.code {
|
||||
KeyCode::Char('q') => break Ok(()),
|
||||
KeyCode::Char('s') => calendar_style = calendar_style.next(),
|
||||
KeyCode::Char('n') | KeyCode::Tab => selected_date = next_month(selected_date),
|
||||
KeyCode::Char('p') | KeyCode::BackTab => {
|
||||
selected_date = previous_month(selected_date);
|
||||
}
|
||||
KeyCode::Char('h') | KeyCode::Left => selected_date -= 1.days(),
|
||||
KeyCode::Char('j') | KeyCode::Down => selected_date += 1.weeks(),
|
||||
KeyCode::Char('k') | KeyCode::Up => selected_date -= 1.weeks(),
|
||||
KeyCode::Char('l') | KeyCode::Right => selected_date += 1.days(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +66,7 @@ fn next_month(date: Date) -> Date {
|
||||
}
|
||||
}
|
||||
|
||||
fn prev_month(date: Date) -> Date {
|
||||
fn previous_month(date: Date) -> Date {
|
||||
if date.month() == Month::January {
|
||||
date.replace_month(Month::December)
|
||||
.unwrap()
|
||||
@@ -72,7 +77,7 @@ fn prev_month(date: Date) -> Date {
|
||||
}
|
||||
}
|
||||
|
||||
/// Render the UI with a calendar.
|
||||
/// Draw the UI with a calendar.
|
||||
fn render(frame: &mut Frame, calendar_style: StyledCalendar, selected_date: Date) {
|
||||
let header = Text::from_iter([
|
||||
Line::from("Calendar Example".bold()),
|
||||
|
||||
@@ -14,18 +14,23 @@ use std::{
|
||||
};
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{
|
||||
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, MouseEventKind,
|
||||
use crossterm::{
|
||||
event::{DisableMouseCapture, EnableMouseCapture, KeyEventKind},
|
||||
ExecutableCommand,
|
||||
};
|
||||
use crossterm::ExecutableCommand;
|
||||
use itertools::Itertools;
|
||||
use ratatui::layout::{Constraint, Layout, Position, Rect};
|
||||
use ratatui::style::{Color, Stylize};
|
||||
use ratatui::symbols::Marker;
|
||||
use ratatui::text::Text;
|
||||
use ratatui::widgets::canvas::{Canvas, Circle, Map, MapResolution, Points, Rectangle};
|
||||
use ratatui::widgets::{Block, Widget};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use ratatui::{
|
||||
crossterm::event::{self, Event, KeyCode, MouseEventKind},
|
||||
layout::{Constraint, Layout, Position, Rect},
|
||||
style::{Color, Stylize},
|
||||
symbols::Marker,
|
||||
text::Text,
|
||||
widgets::{
|
||||
canvas::{Canvas, Circle, Map, MapResolution, Points, Rectangle},
|
||||
Block, Widget,
|
||||
},
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
@@ -75,33 +80,43 @@ impl App {
|
||||
let tick_rate = Duration::from_millis(16);
|
||||
let mut last_tick = Instant::now();
|
||||
while !self.exit {
|
||||
terminal.draw(|frame| self.render(frame))?;
|
||||
terminal.draw(|frame| self.draw(frame))?;
|
||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||
if !event::poll(timeout)? {
|
||||
if event::poll(timeout)? {
|
||||
match event::read()? {
|
||||
Event::Key(key) => self.handle_key_press(key),
|
||||
Event::Mouse(event) => self.handle_mouse_event(event),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
self.on_tick();
|
||||
last_tick = Instant::now();
|
||||
continue;
|
||||
}
|
||||
match event::read()? {
|
||||
Event::Key(key) => self.handle_key_event(key),
|
||||
Event::Mouse(event) => self.handle_mouse_event(event),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) {
|
||||
if !key.is_press() {
|
||||
fn handle_key_press(&mut self, key: event::KeyEvent) {
|
||||
if key.kind != KeyEventKind::Press {
|
||||
return;
|
||||
}
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.exit = true,
|
||||
KeyCode::Char('j') | KeyCode::Down => self.y += 1.0,
|
||||
KeyCode::Char('k') | KeyCode::Up => self.y -= 1.0,
|
||||
KeyCode::Char('l') | KeyCode::Right => self.x += 1.0,
|
||||
KeyCode::Char('h') | KeyCode::Left => self.x -= 1.0,
|
||||
KeyCode::Enter => self.cycle_marker(),
|
||||
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,
|
||||
KeyCode::Enter => {
|
||||
self.marker = match self.marker {
|
||||
Marker::Dot => Marker::Braille,
|
||||
Marker::Braille => Marker::Block,
|
||||
Marker::Block => Marker::HalfBlock,
|
||||
Marker::HalfBlock => Marker::Bar,
|
||||
Marker::Bar => Marker::Dot,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -117,16 +132,6 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
fn cycle_marker(&mut self) {
|
||||
self.marker = match self.marker {
|
||||
Marker::Dot => Marker::Braille,
|
||||
Marker::Braille => Marker::Block,
|
||||
Marker::Block => Marker::HalfBlock,
|
||||
Marker::HalfBlock => Marker::Bar,
|
||||
Marker::Bar => Marker::Dot,
|
||||
};
|
||||
}
|
||||
|
||||
fn on_tick(&mut self) {
|
||||
// bounce the ball by flipping the velocity vector
|
||||
let ball = &self.ball;
|
||||
@@ -145,7 +150,7 @@ impl App {
|
||||
self.ball.y += self.vy;
|
||||
}
|
||||
|
||||
fn render(&self, frame: &mut Frame) {
|
||||
fn draw(&self, frame: &mut Frame) {
|
||||
let header = Text::from_iter([
|
||||
"Canvas Example".bold(),
|
||||
"<q> Quit | <enter> Change Marker | <hjkl> Move".into(),
|
||||
|
||||
@@ -11,13 +11,15 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode};
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Modifier, Style, Stylize};
|
||||
use ratatui::symbols::{self, Marker};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Axis, Block, Chart, Dataset, GraphType, LegendPosition};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use ratatui::{
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
symbols::{self, Marker},
|
||||
text::{Line, Span},
|
||||
widgets::{Axis, Block, Chart, Dataset, GraphType, LegendPosition},
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
@@ -82,19 +84,19 @@ impl App {
|
||||
let tick_rate = Duration::from_millis(250);
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
terminal.draw(|frame| self.render(frame))?;
|
||||
terminal.draw(|frame| self.draw(frame))?;
|
||||
|
||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||
if !event::poll(timeout)? {
|
||||
if event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
self.on_tick();
|
||||
last_tick = Instant::now();
|
||||
continue;
|
||||
}
|
||||
if event::read()?
|
||||
.as_key_press_event()
|
||||
.is_some_and(|key| key.code == KeyCode::Char('q'))
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,7 +112,7 @@ impl App {
|
||||
self.window[1] += 1.0;
|
||||
}
|
||||
|
||||
fn render(&self, frame: &mut Frame) {
|
||||
fn draw(&self, frame: &mut Frame) {
|
||||
let [top, bottom] = Layout::vertical([Constraint::Fill(1); 2]).areas(frame.area());
|
||||
let [animated_chart, bar_chart] =
|
||||
Layout::horizontal([Constraint::Fill(1), Constraint::Length(29)]).areas(top);
|
||||
|
||||
@@ -9,13 +9,15 @@
|
||||
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event;
|
||||
use itertools::Itertools;
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Style, Stylize};
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::{Block, Borders, Paragraph};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use ratatui::{
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Alignment, Constraint, Layout, Rect},
|
||||
style::{Color, Style, Stylize},
|
||||
text::Line,
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
@@ -27,14 +29,16 @@ fn main() -> Result<()> {
|
||||
|
||||
fn run(mut terminal: DefaultTerminal) -> Result<()> {
|
||||
loop {
|
||||
terminal.draw(render)?;
|
||||
if event::read()?.is_key_press() {
|
||||
return Ok(());
|
||||
terminal.draw(draw)?;
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render(frame: &mut Frame) {
|
||||
fn draw(frame: &mut Frame) {
|
||||
let layout = Layout::vertical([
|
||||
Constraint::Length(30),
|
||||
Constraint::Length(17),
|
||||
@@ -202,7 +206,7 @@ fn title_block(title: String) -> Block<'static> {
|
||||
.borders(Borders::TOP)
|
||||
.title_alignment(Alignment::Center)
|
||||
.border_style(Style::new().dark_gray())
|
||||
.title_style(Style::reset())
|
||||
.title_style(Style::new().reset())
|
||||
.title(title)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[package]
|
||||
name = "colors-rgb"
|
||||
version = "0.1.0"
|
||||
publish = false
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
@@ -7,7 +8,6 @@ rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
color-eyre.workspace = true
|
||||
crossterm.workspace = true
|
||||
palette = "0.7.6"
|
||||
ratatui.workspace = true
|
||||
|
||||
|
||||
@@ -19,15 +19,16 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event;
|
||||
use palette::convert::FromColorUnclamped;
|
||||
use palette::{Okhsv, Srgb};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Layout, Position, Rect};
|
||||
use ratatui::style::Color;
|
||||
use ratatui::text::Text;
|
||||
use ratatui::widgets::Widget;
|
||||
use ratatui::DefaultTerminal;
|
||||
use palette::{convert::FromColorUnclamped, Okhsv, Srgb};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Constraint, Layout, Position, Rect},
|
||||
style::Color,
|
||||
text::Text,
|
||||
widgets::Widget,
|
||||
DefaultTerminal,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
@@ -104,16 +105,19 @@ impl App {
|
||||
}
|
||||
|
||||
/// Handle any events that have occurred since the last time the app was rendered.
|
||||
///
|
||||
/// Currently, this only handles the q key to quit the app.
|
||||
fn handle_events(&mut self) -> Result<()> {
|
||||
// Ensure that the app only blocks for a period that allows the app to render at
|
||||
// approximately 60 FPS (this doesn't account for the time to render the frame, and will
|
||||
// also update the app immediately any time an event occurs)
|
||||
let timeout = Duration::from_secs_f32(1.0 / 60.0);
|
||||
if !event::poll(timeout)? {
|
||||
return Ok(());
|
||||
}
|
||||
if event::read()?.is_key_press() {
|
||||
self.state = AppState::Quit;
|
||||
if event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
||||
self.state = AppState::Quit;
|
||||
};
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -171,7 +175,7 @@ impl FpsWidget {
|
||||
/// This updates the fps once a second, but only if the widget has rendered at least 2 frames
|
||||
/// since the last calculation. This avoids noise in the fps calculation when rendering on slow
|
||||
/// machines that can't render at least 2 frames per second.
|
||||
#[expect(clippy::cast_precision_loss)]
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
fn calculate_fps(&mut self) {
|
||||
self.frame_count += 1;
|
||||
let elapsed = self.last_instant.elapsed();
|
||||
@@ -213,7 +217,7 @@ impl ColorsWidget {
|
||||
///
|
||||
/// This is called once per frame to setup the colors to render. It caches the colors so that
|
||||
/// they don't need to be recalculated every frame.
|
||||
#[expect(clippy::cast_precision_loss)]
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
fn setup_colors(&mut self, size: Rect) {
|
||||
let Rect { width, height, .. } = size;
|
||||
// double the height because each screen row has two rows of half block pixels
|
||||
|
||||
@@ -9,17 +9,23 @@
|
||||
///
|
||||
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode};
|
||||
use itertools::Itertools;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Constraint::{self, Fill, Length, Max, Min, Percentage, Ratio};
|
||||
use ratatui::layout::{Flex, Layout, Rect};
|
||||
use ratatui::style::palette::tailwind::{BLUE, SKY, SLATE, STONE};
|
||||
use ratatui::style::{Color, Style, Stylize};
|
||||
use ratatui::symbols::{self, line};
|
||||
use ratatui::text::{Line, Span, Text};
|
||||
use ratatui::widgets::{Block, Paragraph, Widget, Wrap};
|
||||
use ratatui::DefaultTerminal;
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{
|
||||
Constraint::{self, Fill, Length, Max, Min, Percentage, Ratio},
|
||||
Flex, Layout, Rect,
|
||||
},
|
||||
style::{
|
||||
palette::tailwind::{BLUE, SKY, SLATE, STONE},
|
||||
Color, Style, Stylize,
|
||||
},
|
||||
symbols::{self, line},
|
||||
text::{Line, Span, Text},
|
||||
widgets::{Block, Paragraph, Widget, Wrap},
|
||||
DefaultTerminal,
|
||||
};
|
||||
use strum::{Display, EnumIter, FromRepr};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@@ -107,8 +113,8 @@ impl App {
|
||||
}
|
||||
|
||||
fn handle_events(&mut self) -> Result<()> {
|
||||
if let Some(key) = event::read()?.as_key_press_event() {
|
||||
match key.code {
|
||||
match event::read()? {
|
||||
Event::Key(key) if key.kind == KeyEventKind::Press => match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.exit(),
|
||||
KeyCode::Char('1') => self.swap_constraint(ConstraintName::Min),
|
||||
KeyCode::Char('2') => self.swap_constraint(ConstraintName::Max),
|
||||
@@ -125,7 +131,8 @@ impl App {
|
||||
KeyCode::Char('h') | KeyCode::Left => self.prev_block(),
|
||||
KeyCode::Char('l') | KeyCode::Right => self.next_block(),
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -141,7 +148,7 @@ impl App {
|
||||
| Constraint::Fill(v)
|
||||
| Constraint::Percentage(v) => *v = v.saturating_add(1),
|
||||
Constraint::Ratio(_n, d) => *d = d.saturating_add(1),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn decrement_value(&mut self) {
|
||||
@@ -155,7 +162,7 @@ impl App {
|
||||
| Constraint::Fill(v)
|
||||
| Constraint::Percentage(v) => *v = v.saturating_sub(1),
|
||||
Constraint::Ratio(_n, d) => *d = d.saturating_sub(1),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// select the next block with wrap around
|
||||
@@ -196,15 +203,15 @@ impl App {
|
||||
self.selected_index = index;
|
||||
}
|
||||
|
||||
const fn increment_spacing(&mut self) {
|
||||
fn increment_spacing(&mut self) {
|
||||
self.spacing = self.spacing.saturating_add(1);
|
||||
}
|
||||
|
||||
const fn decrement_spacing(&mut self) {
|
||||
fn decrement_spacing(&mut self) {
|
||||
self.spacing = self.spacing.saturating_sub(1);
|
||||
}
|
||||
|
||||
const fn exit(&mut self) {
|
||||
fn exit(&mut self) {
|
||||
self.mode = AppMode::Quit;
|
||||
}
|
||||
|
||||
@@ -276,7 +283,7 @@ impl App {
|
||||
}
|
||||
|
||||
fn swap_legend() -> impl Widget {
|
||||
#[expect(unstable_name_collisions)]
|
||||
#[allow(unstable_name_collisions)]
|
||||
Paragraph::new(
|
||||
Line::from(
|
||||
[
|
||||
@@ -465,7 +472,7 @@ impl ConstraintBlock {
|
||||
} else {
|
||||
main_color
|
||||
};
|
||||
if let Some(last_row) = area.rows().next_back() {
|
||||
if let Some(last_row) = area.rows().last() {
|
||||
buf.set_style(last_row, border_color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,22 @@
|
||||
///
|
||||
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Constraint::{self, Fill, Length, Max, Min, Percentage, Ratio};
|
||||
use ratatui::layout::{Layout, Rect};
|
||||
use ratatui::style::palette::tailwind;
|
||||
use ratatui::style::{Color, Modifier, Style, Stylize};
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::{
|
||||
Block, Padding, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget,
|
||||
Tabs, Widget,
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{
|
||||
Constraint::{self, Fill, Length, Max, Min, Percentage, Ratio},
|
||||
Layout, Rect,
|
||||
},
|
||||
style::{palette::tailwind, Color, Modifier, Style, Stylize},
|
||||
symbols,
|
||||
text::Line,
|
||||
widgets::{
|
||||
Block, Padding, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget,
|
||||
Tabs, Widget,
|
||||
},
|
||||
DefaultTerminal,
|
||||
};
|
||||
use ratatui::{symbols, DefaultTerminal};
|
||||
use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
|
||||
|
||||
const SPACER_HEIGHT: u16 = 0;
|
||||
@@ -81,7 +85,7 @@ impl App {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const fn update_max_scroll_offset(&mut self) {
|
||||
fn update_max_scroll_offset(&mut self) {
|
||||
self.max_scroll_offset = (self.selected_tab.get_example_count() - 1) * EXAMPLE_HEIGHT;
|
||||
}
|
||||
|
||||
@@ -90,7 +94,10 @@ impl App {
|
||||
}
|
||||
|
||||
fn handle_events(&mut self) -> Result<()> {
|
||||
if let Some(key) = event::read()?.as_key_press_event() {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind != KeyEventKind::Press {
|
||||
return Ok(());
|
||||
}
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.quit(),
|
||||
KeyCode::Char('l') | KeyCode::Right => self.next(),
|
||||
@@ -105,7 +112,7 @@ impl App {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const fn quit(&mut self) {
|
||||
fn quit(&mut self) {
|
||||
self.state = AppState::Quit;
|
||||
}
|
||||
|
||||
@@ -121,7 +128,7 @@ impl App {
|
||||
self.scroll_offset = 0;
|
||||
}
|
||||
|
||||
const fn up(&mut self) {
|
||||
fn up(&mut self) {
|
||||
self.scroll_offset = self.scroll_offset.saturating_sub(1);
|
||||
}
|
||||
|
||||
@@ -132,11 +139,11 @@ impl App {
|
||||
.min(self.max_scroll_offset);
|
||||
}
|
||||
|
||||
const fn top(&mut self) {
|
||||
fn top(&mut self) {
|
||||
self.scroll_offset = 0;
|
||||
}
|
||||
|
||||
const fn bottom(&mut self) {
|
||||
fn bottom(&mut self) {
|
||||
self.scroll_offset = self.max_scroll_offset;
|
||||
}
|
||||
}
|
||||
@@ -189,7 +196,7 @@ impl App {
|
||||
///
|
||||
/// This function renders the demo content into a separate buffer and then splices the buffer
|
||||
/// into the main buffer. This is done to make it possible to handle scrolling easily.
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn render_demo(self, area: Rect, buf: &mut Buffer) {
|
||||
// render demo content into a separate buffer so all examples fit we add an extra
|
||||
// area.height to make sure the last example is fully visible even when the scroll offset is
|
||||
@@ -244,7 +251,7 @@ impl SelectedTab {
|
||||
}
|
||||
|
||||
const fn get_example_count(self) -> u16 {
|
||||
#[expect(clippy::match_same_arms)]
|
||||
#[allow(clippy::match_same_arms)]
|
||||
match self {
|
||||
Self::Length => 4,
|
||||
Self::Percentage => 5,
|
||||
|
||||
@@ -9,17 +9,21 @@
|
||||
use std::{io::stdout, ops::ControlFlow, time::Duration};
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{
|
||||
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, MouseButton,
|
||||
MouseEvent, MouseEventKind,
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{
|
||||
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, MouseButton, MouseEvent,
|
||||
MouseEventKind,
|
||||
},
|
||||
execute,
|
||||
},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{Color, Style},
|
||||
text::Line,
|
||||
widgets::{Paragraph, Widget},
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
use crossterm::execute;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Style};
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::{Paragraph, Widget};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
@@ -99,7 +103,7 @@ impl<'a> Button<'a> {
|
||||
}
|
||||
|
||||
impl Widget for Button<'_> {
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let (background, text, shadow, highlight) = self.colors();
|
||||
buf.set_style(area, Style::new().bg(background).fg(text));
|
||||
@@ -147,12 +151,15 @@ fn run(mut terminal: DefaultTerminal) -> Result<()> {
|
||||
let mut selected_button: usize = 0;
|
||||
let mut button_states = [State::Selected, State::Normal, State::Normal];
|
||||
loop {
|
||||
terminal.draw(|frame| render(frame, button_states))?;
|
||||
terminal.draw(|frame| draw(frame, button_states))?;
|
||||
if !event::poll(Duration::from_millis(100))? {
|
||||
continue;
|
||||
}
|
||||
match event::read()? {
|
||||
Event::Key(key) => {
|
||||
if key.kind != event::KeyEventKind::Press {
|
||||
continue;
|
||||
}
|
||||
if handle_key_event(key, &mut button_states, &mut selected_button).is_break() {
|
||||
break;
|
||||
}
|
||||
@@ -166,7 +173,7 @@ fn run(mut terminal: DefaultTerminal) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render(frame: &mut Frame, states: [State; 3]) {
|
||||
fn draw(frame: &mut Frame, states: [State; 3]) {
|
||||
let vertical = Layout::vertical([
|
||||
Constraint::Length(1),
|
||||
Constraint::Max(3),
|
||||
@@ -198,13 +205,10 @@ fn render_buttons(frame: &mut Frame<'_>, area: Rect, states: [State; 3]) {
|
||||
}
|
||||
|
||||
fn handle_key_event(
|
||||
key: KeyEvent,
|
||||
key: event::KeyEvent,
|
||||
button_states: &mut [State; 3],
|
||||
selected_button: &mut usize,
|
||||
) -> ControlFlow<()> {
|
||||
if !key.is_press() {
|
||||
return ControlFlow::Continue(());
|
||||
}
|
||||
match key.code {
|
||||
KeyCode::Char('q') => return ControlFlow::Break(()),
|
||||
KeyCode::Left | KeyCode::Char('h') => {
|
||||
|
||||
@@ -7,16 +7,11 @@ rust-version.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["crossterm"]
|
||||
crossterm = ["ratatui/crossterm", "dep:crossterm"]
|
||||
termion = ["ratatui/termion", "dep:termion"]
|
||||
termwiz = ["ratatui/termwiz", "dep:termwiz"]
|
||||
crossterm = ["ratatui/crossterm"]
|
||||
termion = ["ratatui/termion"]
|
||||
termwiz = ["ratatui/termwiz"]
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.37", features = ["derive"] }
|
||||
crossterm = { workspace = true, optional = true }
|
||||
rand = "0.9.1"
|
||||
clap = { version = "4.5.27", features = ["derive"] }
|
||||
rand = "0.9.0"
|
||||
ratatui.workspace = true
|
||||
termwiz = { workspace = true, optional = true }
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
termion = { workspace = true, optional = true }
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use rand::distr::{Distribution, Uniform};
|
||||
use rand::rngs::ThreadRng;
|
||||
use rand::{
|
||||
distr::{Distribution, Uniform},
|
||||
rngs::ThreadRng,
|
||||
};
|
||||
use ratatui::widgets::ListState;
|
||||
|
||||
const TASKS: [&str; 24] = [
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crossterm::event::{self, DisableMouseCapture, EnableMouseCapture, KeyCode};
|
||||
use crossterm::execute;
|
||||
use crossterm::terminal::{
|
||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
use std::{
|
||||
error::Error,
|
||||
io,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use ratatui::backend::{Backend, CrosstermBackend};
|
||||
use ratatui::Terminal;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::ui;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
use crate::{app::App, ui};
|
||||
|
||||
pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
@@ -45,29 +48,29 @@ fn run_app<B: Backend>(
|
||||
terminal: &mut Terminal<B>,
|
||||
mut app: App,
|
||||
tick_rate: Duration,
|
||||
) -> Result<(), Box<dyn Error>>
|
||||
where
|
||||
B::Error: 'static,
|
||||
{
|
||||
) -> io::Result<()> {
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
terminal.draw(|frame| ui::draw(frame, &mut app))?;
|
||||
|
||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||
if !event::poll(timeout)? {
|
||||
if event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
match key.code {
|
||||
KeyCode::Left | KeyCode::Char('h') => app.on_left(),
|
||||
KeyCode::Up | KeyCode::Char('k') => app.on_up(),
|
||||
KeyCode::Right | KeyCode::Char('l') => app.on_right(),
|
||||
KeyCode::Down | KeyCode::Char('j') => app.on_down(),
|
||||
KeyCode::Char(c) => app.on_key(c),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
app.on_tick();
|
||||
last_tick = Instant::now();
|
||||
continue;
|
||||
}
|
||||
if let Some(key) = event::read()?.as_key_press_event() {
|
||||
match key.code {
|
||||
KeyCode::Char('h') | KeyCode::Left => app.on_left(),
|
||||
KeyCode::Char('j') | KeyCode::Down => app.on_down(),
|
||||
KeyCode::Char('k') | KeyCode::Up => app.on_up(),
|
||||
KeyCode::Char('l') | KeyCode::Right => app.on_right(),
|
||||
KeyCode::Char(c) => app.on_key(c),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if app.should_quit {
|
||||
return Ok(());
|
||||
|
||||
@@ -13,8 +13,7 @@
|
||||
//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
|
||||
|
||||
use std::error::Error;
|
||||
use std::time::Duration;
|
||||
use std::{error::Error, time::Duration};
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
#![allow(dead_code)]
|
||||
use std::error::Error;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
use std::{io, thread};
|
||||
use std::{error::Error, io, sync::mpsc, thread, time::Duration};
|
||||
|
||||
use ratatui::backend::{Backend, TermionBackend};
|
||||
use ratatui::Terminal;
|
||||
use termion::event::Key;
|
||||
use termion::input::{MouseTerminal, TermRead};
|
||||
use termion::raw::IntoRawMode;
|
||||
use termion::screen::IntoAlternateScreen;
|
||||
use ratatui::{
|
||||
backend::{Backend, TermionBackend},
|
||||
termion::{
|
||||
event::Key,
|
||||
input::{MouseTerminal, TermRead},
|
||||
raw::IntoRawMode,
|
||||
screen::IntoAlternateScreen,
|
||||
},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
use crate::app::App;
|
||||
use crate::ui;
|
||||
use crate::{app::App, ui};
|
||||
|
||||
pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
@@ -36,10 +36,7 @@ fn run_app<B: Backend>(
|
||||
terminal: &mut Terminal<B>,
|
||||
mut app: App,
|
||||
tick_rate: Duration,
|
||||
) -> Result<(), Box<dyn Error>>
|
||||
where
|
||||
B::Error: 'static,
|
||||
{
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let events = events(tick_rate);
|
||||
loop {
|
||||
terminal.draw(|frame| ui::draw(frame, &mut app))?;
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
#![allow(dead_code)]
|
||||
use std::error::Error;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{
|
||||
error::Error,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use ratatui::backend::TermwizBackend;
|
||||
use ratatui::Terminal;
|
||||
use termwiz::input::{InputEvent, KeyCode};
|
||||
use termwiz::terminal::Terminal as TermwizTerminal;
|
||||
use ratatui::{
|
||||
backend::TermwizBackend,
|
||||
termwiz::{
|
||||
input::{InputEvent, KeyCode},
|
||||
terminal::Terminal as TermwizTerminal,
|
||||
},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
use crate::app::App;
|
||||
use crate::ui;
|
||||
use crate::{app::App, ui};
|
||||
|
||||
pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<(), Box<dyn Error>> {
|
||||
let backend = TermwizBackend::new()?;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::text::{self, Span};
|
||||
use ratatui::widgets::canvas::{self, Canvas, Circle, Map, MapResolution, Rectangle};
|
||||
use ratatui::widgets::{
|
||||
Axis, BarChart, Block, Cell, Chart, Dataset, Gauge, LineGauge, List, ListItem, Paragraph, Row,
|
||||
Sparkline, Table, Tabs, Wrap,
|
||||
use ratatui::{
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
symbols,
|
||||
text::{self, Span},
|
||||
widgets::{
|
||||
canvas::{self, Canvas, Circle, Map, MapResolution, Rectangle},
|
||||
Axis, BarChart, Block, Cell, Chart, Dataset, Gauge, LineGauge, List, ListItem, Paragraph,
|
||||
Row, Sparkline, Table, Tabs, Wrap,
|
||||
},
|
||||
Frame,
|
||||
};
|
||||
use ratatui::{symbols, Frame};
|
||||
|
||||
use crate::app::App;
|
||||
|
||||
@@ -95,7 +98,7 @@ fn draw_gauges(frame: &mut Frame, app: &mut App, area: Rect) {
|
||||
frame.render_widget(line_gauge, chunks[2]);
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn draw_charts(frame: &mut Frame, app: &mut App, area: Rect) {
|
||||
let constraints = if app.show_chart {
|
||||
vec![Constraint::Percentage(50), Constraint::Percentage(50)]
|
||||
|
||||
@@ -11,9 +11,9 @@ crossterm.workspace = true
|
||||
indoc.workspace = true
|
||||
itertools.workspace = true
|
||||
palette = "0.7.6"
|
||||
rand = "0.9.1"
|
||||
rand = "0.9.0"
|
||||
rand_chacha = "0.9.0"
|
||||
ratatui = { workspace = true, features = ["all-widgets"] }
|
||||
strum.workspace = true
|
||||
time = "0.3.39"
|
||||
time = "0.3.37"
|
||||
unicode-width = "0.2.0"
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode};
|
||||
use color_eyre::{eyre::Context, Result};
|
||||
use crossterm::event;
|
||||
use itertools::Itertools;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::Color;
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Block, Tabs, Widget};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::Color,
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Tabs, Widget},
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
|
||||
|
||||
use crate::tabs::{AboutTab, EmailTab, RecipeTab, TracerouteTab, WeatherTab};
|
||||
use crate::{destroy, THEME};
|
||||
use crate::{
|
||||
destroy,
|
||||
tabs::{AboutTab, EmailTab, RecipeTab, TracerouteTab, WeatherTab},
|
||||
THEME,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct App {
|
||||
@@ -49,7 +54,7 @@ impl App {
|
||||
pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
|
||||
while self.is_running() {
|
||||
terminal
|
||||
.draw(|frame| self.render(frame))
|
||||
.draw(|frame| self.draw(frame))
|
||||
.wrap_err("terminal.draw")?;
|
||||
self.handle_events()?;
|
||||
}
|
||||
@@ -60,8 +65,8 @@ impl App {
|
||||
self.mode != Mode::Quit
|
||||
}
|
||||
|
||||
/// Render a single frame of the app.
|
||||
fn render(&self, frame: &mut Frame) {
|
||||
/// Draw a single frame of the app.
|
||||
fn draw(&self, frame: &mut Frame) {
|
||||
frame.render_widget(self, frame.area());
|
||||
if self.mode == Mode::Destroy {
|
||||
destroy::destroy(frame);
|
||||
@@ -77,20 +82,25 @@ impl App {
|
||||
if !event::poll(timeout)? {
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(key) = event::read()?.as_key_press_event() {
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.mode = Mode::Quit,
|
||||
KeyCode::Char('h') | KeyCode::Left => self.prev_tab(),
|
||||
KeyCode::Char('l') | KeyCode::Right | KeyCode::Tab => self.next_tab(),
|
||||
KeyCode::Char('k') | KeyCode::Up => self.prev(),
|
||||
KeyCode::Char('j') | KeyCode::Down => self.next(),
|
||||
KeyCode::Char('d') | KeyCode::Delete => self.destroy(),
|
||||
_ => {}
|
||||
};
|
||||
match event::read()? {
|
||||
Event::Key(key) if key.kind == KeyEventKind::Press => self.handle_key_press(key),
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_key_press(&mut self, key: KeyEvent) {
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.mode = Mode::Quit,
|
||||
KeyCode::Char('h') | KeyCode::Left => self.prev_tab(),
|
||||
KeyCode::Char('l') | KeyCode::Right => self.next_tab(),
|
||||
KeyCode::Char('k') | KeyCode::Up => self.prev(),
|
||||
KeyCode::Char('j') | KeyCode::Down => self.next(),
|
||||
KeyCode::Char('d') | KeyCode::Delete => self.destroy(),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
fn prev(&mut self) {
|
||||
match self.tab {
|
||||
Tab::About => self.about_tab.prev_row(),
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
use palette::{IntoColor, Okhsv, Srgb};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::Color;
|
||||
use ratatui::widgets::Widget;
|
||||
use ratatui::{buffer::Buffer, layout::Rect, style::Color, widgets::Widget};
|
||||
|
||||
/// A widget that renders a color swatch of RGB colors.
|
||||
///
|
||||
@@ -12,7 +9,7 @@ use ratatui::widgets::Widget;
|
||||
pub struct RgbSwatch;
|
||||
|
||||
impl Widget for RgbSwatch {
|
||||
#[expect(clippy::cast_precision_loss, clippy::similar_names)]
|
||||
#[allow(clippy::cast_precision_loss, clippy::similar_names)]
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
for (yi, y) in (area.top()..area.bottom()).enumerate() {
|
||||
let value = f32::from(area.height) - yi as f32;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use rand::Rng;
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Flex, Layout, Rect};
|
||||
use ratatui::style::{Color, Style};
|
||||
use ratatui::text::Text;
|
||||
use ratatui::widgets::Widget;
|
||||
use ratatui::Frame;
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Flex, Layout, Rect},
|
||||
style::{Color, Style},
|
||||
text::Text,
|
||||
widgets::Widget,
|
||||
Frame,
|
||||
};
|
||||
|
||||
/// delay the start of the animation so it doesn't start immediately
|
||||
const DELAY: usize = 120;
|
||||
@@ -32,7 +34,7 @@ pub fn destroy(frame: &mut Frame<'_>) {
|
||||
///
|
||||
/// Each pick some random pixels and move them each down one row. This is a very inefficient way to
|
||||
/// do this, but it works well enough for this demo.
|
||||
#[expect(
|
||||
#[allow(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_precision_loss,
|
||||
clippy::cast_sign_loss
|
||||
@@ -74,7 +76,7 @@ fn drip(frame_count: usize, area: Rect, buf: &mut Buffer) {
|
||||
}
|
||||
|
||||
/// draw some text fading in and out from black to red and back
|
||||
#[expect(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
|
||||
fn text(frame_count: usize, area: Rect, buf: &mut Buffer) {
|
||||
let sub_frame = frame_count.saturating_sub(TEXT_DELAY);
|
||||
if sub_frame == 0 {
|
||||
@@ -126,7 +128,7 @@ fn blend(mask_color: Color, cell_color: Color, percentage: f64) -> Color {
|
||||
let green = f64::from(mask_green).mul_add(percentage, f64::from(cell_green) * remain);
|
||||
let blue = f64::from(mask_blue).mul_add(percentage, f64::from(cell_blue) * remain);
|
||||
|
||||
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||
Color::Rgb(red as u8, green as u8, blue as u8)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,13 +29,16 @@ use std::io::stdout;
|
||||
|
||||
use app::App;
|
||||
use color_eyre::Result;
|
||||
use crossterm::execute;
|
||||
use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen};
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::{TerminalOptions, Viewport};
|
||||
use crossterm::{
|
||||
execute,
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{layout::Rect, TerminalOptions, Viewport};
|
||||
|
||||
pub use self::colors::{color_from_oklab, RgbSwatch};
|
||||
pub use self::theme::THEME;
|
||||
pub use self::{
|
||||
colors::{color_from_oklab, RgbSwatch},
|
||||
theme::THEME,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Margin, Rect};
|
||||
use ratatui::widgets::{
|
||||
Block, Borders, Clear, MascotEyeColor, Padding, Paragraph, RatatuiMascot, Widget, Wrap,
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Constraint, Layout, Margin, Rect},
|
||||
widgets::{
|
||||
Block, Borders, Clear, MascotEyeColor, Padding, Paragraph, RatatuiMascot, Widget, Wrap,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{RgbSwatch, THEME};
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use itertools::Itertools;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Layout, Margin, Rect};
|
||||
use ratatui::style::{Styled, Stylize};
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::{
|
||||
Block, BorderType, Borders, Clear, List, ListItem, ListState, Padding, Paragraph, Scrollbar,
|
||||
ScrollbarState, StatefulWidget, Tabs, Widget,
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Layout, Margin, Rect},
|
||||
style::{Styled, Stylize},
|
||||
text::Line,
|
||||
widgets::{
|
||||
Block, BorderType, Borders, Clear, List, ListItem, ListState, Padding, Paragraph,
|
||||
Scrollbar, ScrollbarState, StatefulWidget, Tabs, Widget,
|
||||
},
|
||||
};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use itertools::Itertools;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Margin, Rect};
|
||||
use ratatui::style::{Style, Stylize};
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::{
|
||||
Block, Clear, Padding, Paragraph, Row, Scrollbar, ScrollbarOrientation, ScrollbarState,
|
||||
StatefulWidget, Table, TableState, Widget, Wrap,
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Constraint, Layout, Margin, Rect},
|
||||
style::{Style, Stylize},
|
||||
text::Line,
|
||||
widgets::{
|
||||
Block, Clear, Padding, Paragraph, Row, Scrollbar, ScrollbarOrientation, ScrollbarState,
|
||||
StatefulWidget, Table, TableState, Widget, Wrap,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{RgbSwatch, THEME};
|
||||
@@ -17,7 +19,7 @@ struct Ingredient {
|
||||
}
|
||||
|
||||
impl Ingredient {
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn height(&self) -> u16 {
|
||||
self.name.lines().count() as u16
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use itertools::Itertools;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Margin, Rect};
|
||||
use ratatui::style::{Styled, Stylize};
|
||||
use ratatui::symbols::Marker;
|
||||
use ratatui::widgets::canvas::{self, Canvas, Map, MapResolution, Points};
|
||||
use ratatui::widgets::{
|
||||
Block, BorderType, Clear, Padding, Row, Scrollbar, ScrollbarOrientation, ScrollbarState,
|
||||
Sparkline, StatefulWidget, Table, TableState, Widget,
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Constraint, Layout, Margin, Rect},
|
||||
style::{Styled, Stylize},
|
||||
symbols::Marker,
|
||||
widgets::{
|
||||
canvas::{self, Canvas, Map, MapResolution, Points},
|
||||
Block, BorderType, Clear, Padding, Row, Scrollbar, ScrollbarOrientation, ScrollbarState,
|
||||
Sparkline, StatefulWidget, Table, TableState, Widget,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{RgbSwatch, THEME};
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use itertools::Itertools;
|
||||
use palette::Okhsv;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Direction, Layout, Margin, Rect};
|
||||
use ratatui::style::{Color, Style};
|
||||
use ratatui::symbols;
|
||||
use ratatui::widgets::calendar::{CalendarEventStore, Monthly};
|
||||
use ratatui::widgets::{Bar, BarChart, BarGroup, Block, Clear, LineGauge, Padding, Widget};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Direction, Layout, Margin, Rect},
|
||||
style::{Color, Style, Stylize},
|
||||
symbols,
|
||||
widgets::{
|
||||
calendar::{CalendarEventStore, Monthly},
|
||||
Bar, BarChart, BarGroup, Block, Clear, LineGauge, Padding, Widget,
|
||||
},
|
||||
};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{color_from_oklab, RgbSwatch, THEME};
|
||||
@@ -130,14 +134,14 @@ fn render_horizontal_barchart(area: Rect, buf: &mut Buffer) {
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
#[expect(clippy::cast_precision_loss)]
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
pub fn render_gauge(progress: usize, area: Rect, buf: &mut Buffer) {
|
||||
let percent = (progress * 3).min(100) as f64;
|
||||
|
||||
render_line_gauge(percent, area, buf);
|
||||
}
|
||||
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn render_line_gauge(percent: f64, area: Rect, buf: &mut Buffer) {
|
||||
// cycle color hue based on the percent for a neat effect yellow -> red
|
||||
let hue = 90.0 - (percent as f32 * 0.6);
|
||||
|
||||
@@ -11,18 +11,23 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Constraint::{self, Fill, Length, Max, Min, Percentage, Ratio};
|
||||
use ratatui::layout::{Alignment, Flex, Layout, Rect};
|
||||
use ratatui::style::palette::tailwind;
|
||||
use ratatui::style::{Color, Modifier, Style, Stylize};
|
||||
use ratatui::symbols::{self, line};
|
||||
use ratatui::text::{Line, Text};
|
||||
use ratatui::widgets::{
|
||||
Block, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget, Tabs, Widget,
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{
|
||||
Alignment,
|
||||
Constraint::{self, Fill, Length, Max, Min, Percentage, Ratio},
|
||||
Flex, Layout, Rect,
|
||||
},
|
||||
style::{palette::tailwind, Color, Modifier, Style, Stylize},
|
||||
symbols::{self, line},
|
||||
text::{Line, Text},
|
||||
widgets::{
|
||||
Block, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget, Tabs,
|
||||
Widget,
|
||||
},
|
||||
DefaultTerminal,
|
||||
};
|
||||
use ratatui::DefaultTerminal;
|
||||
use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@@ -168,8 +173,8 @@ impl App {
|
||||
}
|
||||
|
||||
fn handle_events(&mut self) -> Result<()> {
|
||||
if let Some(key) = event::read()?.as_key_press_event() {
|
||||
match key.code {
|
||||
match event::read()? {
|
||||
Event::Key(key) if key.kind == KeyEventKind::Press => match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.quit(),
|
||||
KeyCode::Char('l') | KeyCode::Right => self.next(),
|
||||
KeyCode::Char('h') | KeyCode::Left => self.previous(),
|
||||
@@ -180,7 +185,8 @@ impl App {
|
||||
KeyCode::Char('+') => self.increment_spacing(),
|
||||
KeyCode::Char('-') => self.decrement_spacing(),
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -193,7 +199,7 @@ impl App {
|
||||
self.selected_tab = self.selected_tab.previous();
|
||||
}
|
||||
|
||||
const fn up(&mut self) {
|
||||
fn up(&mut self) {
|
||||
self.scroll_offset = self.scroll_offset.saturating_sub(1);
|
||||
}
|
||||
|
||||
@@ -204,7 +210,7 @@ impl App {
|
||||
.min(max_scroll_offset());
|
||||
}
|
||||
|
||||
const fn top(&mut self) {
|
||||
fn top(&mut self) {
|
||||
self.scroll_offset = 0;
|
||||
}
|
||||
|
||||
@@ -212,15 +218,15 @@ impl App {
|
||||
self.scroll_offset = max_scroll_offset();
|
||||
}
|
||||
|
||||
const fn increment_spacing(&mut self) {
|
||||
fn increment_spacing(&mut self) {
|
||||
self.spacing = self.spacing.saturating_add(1);
|
||||
}
|
||||
|
||||
const fn decrement_spacing(&mut self) {
|
||||
fn decrement_spacing(&mut self) {
|
||||
self.spacing = self.spacing.saturating_sub(1);
|
||||
}
|
||||
|
||||
const fn quit(&mut self) {
|
||||
fn quit(&mut self) {
|
||||
self.state = AppState::Quit;
|
||||
}
|
||||
}
|
||||
@@ -292,7 +298,7 @@ impl App {
|
||||
/// into the main buffer. This is done to make it possible to handle scrolling easily.
|
||||
///
|
||||
/// Returns bool indicating whether scroll was needed
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn render_demo(self, area: Rect, buf: &mut Buffer) -> bool {
|
||||
// render demo content into a separate buffer so all examples fit we add an extra
|
||||
// area.height to make sure the last example is fully visible even when the scroll offset is
|
||||
@@ -509,7 +515,7 @@ const fn color_for_constraint(constraint: Constraint) -> Color {
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn get_description_height(s: &str) -> u16 {
|
||||
if s.is_empty() {
|
||||
0
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||
use ratatui::style::palette::tailwind;
|
||||
use ratatui::style::{Color, Style, Stylize};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Block, Borders, Gauge, Padding, Paragraph, Widget};
|
||||
use ratatui::DefaultTerminal;
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
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},
|
||||
DefaultTerminal,
|
||||
};
|
||||
|
||||
const GAUGE1_COLOR: Color = tailwind::RED.c800;
|
||||
const GAUGE2_COLOR: Color = tailwind::GREEN.c800;
|
||||
@@ -79,30 +80,31 @@ impl App {
|
||||
|
||||
fn handle_events(&mut self) -> Result<()> {
|
||||
let timeout = Duration::from_secs_f32(1.0 / 20.0);
|
||||
if !event::poll(timeout)? {
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(key) = event::read()?.as_key_press_event() {
|
||||
match key.code {
|
||||
KeyCode::Char(' ') | KeyCode::Enter => self.start(),
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.quit(),
|
||||
_ => {}
|
||||
if event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
match key.code {
|
||||
KeyCode::Char(' ') | KeyCode::Enter => self.start(),
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.quit(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const fn start(&mut self) {
|
||||
fn start(&mut self) {
|
||||
self.state = AppState::Started;
|
||||
}
|
||||
|
||||
const fn quit(&mut self) {
|
||||
fn quit(&mut self) {
|
||||
self.state = AppState::Quitting;
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &App {
|
||||
#[expect(clippy::similar_names)]
|
||||
#[allow(clippy::similar_names)]
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
use Constraint::{Length, Min, Ratio};
|
||||
let layout = Layout::vertical([Length(2), Min(0), Length(1)]);
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode};
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use color_eyre::{eyre::Context, Result};
|
||||
use ratatui::{
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
widgets::Paragraph,
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
|
||||
/// This is a bare minimum example. There are many approaches to running an application loop, so
|
||||
/// this is not meant to be prescriptive. It is only meant to demonstrate the basic setup and
|
||||
@@ -33,7 +34,7 @@ fn main() -> Result<()> {
|
||||
/// on events, or you could have a single application state and update it based on events.
|
||||
fn run(mut terminal: DefaultTerminal) -> Result<()> {
|
||||
loop {
|
||||
terminal.draw(render)?;
|
||||
terminal.draw(draw)?;
|
||||
if should_quit()? {
|
||||
break;
|
||||
}
|
||||
@@ -43,7 +44,7 @@ fn run(mut terminal: DefaultTerminal) -> Result<()> {
|
||||
|
||||
/// Render the application. This is where you would draw the application UI. This example draws a
|
||||
/// greeting.
|
||||
fn render(frame: &mut Frame) {
|
||||
fn draw(frame: &mut Frame) {
|
||||
let greeting = Paragraph::new("Hello World! (press 'q' to quit)");
|
||||
frame.render_widget(greeting, frame.area());
|
||||
}
|
||||
@@ -55,11 +56,9 @@ fn render(frame: &mut Frame) {
|
||||
/// updating the application state, without blocking the event loop for too long.
|
||||
fn should_quit() -> Result<bool> {
|
||||
if event::poll(Duration::from_millis(250)).context("event poll failed")? {
|
||||
let q_pressed = event::read()
|
||||
.context("event read failed")?
|
||||
.as_key_press_event()
|
||||
.is_some_and(|key| key.code == KeyCode::Char('q'));
|
||||
return Ok(q_pressed);
|
||||
if let Event::Key(key) = event::read().context("event read failed")? {
|
||||
return Ok(KeyCode::Char('q') == key.code);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
@@ -7,14 +7,16 @@
|
||||
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
|
||||
/// [OSC 8]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode};
|
||||
use itertools::Itertools;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::Stylize;
|
||||
use ratatui::text::{Line, Text};
|
||||
use ratatui::widgets::Widget;
|
||||
use ratatui::DefaultTerminal;
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
layout::Rect,
|
||||
style::Stylize,
|
||||
text::{Line, Text},
|
||||
widgets::Widget,
|
||||
DefaultTerminal,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
@@ -38,11 +40,10 @@ impl App {
|
||||
fn run(self, mut terminal: DefaultTerminal) -> Result<()> {
|
||||
loop {
|
||||
terminal.draw(|frame| frame.render_widget(&self.hyperlink, frame.area()))?;
|
||||
if event::read()?
|
||||
.as_key_press_event()
|
||||
.is_some_and(|key| matches!(key.code, KeyCode::Char('q') | KeyCode::Esc))
|
||||
{
|
||||
break;
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if matches!(key.code, KeyCode::Char('q') | KeyCode::Esc) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -8,7 +8,7 @@ rust-version.workspace = true
|
||||
[dependencies]
|
||||
color-eyre.workspace = true
|
||||
crossterm.workspace = true
|
||||
rand = "0.9.1"
|
||||
rand = "0.9.0"
|
||||
ratatui.workspace = true
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -15,14 +15,17 @@ use std::{
|
||||
};
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event;
|
||||
use rand::distr::{Distribution, Uniform};
|
||||
use ratatui::backend::Backend;
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Block, Gauge, LineGauge, List, ListItem, Paragraph, Widget};
|
||||
use ratatui::{symbols, Frame, Terminal, TerminalOptions, Viewport};
|
||||
use ratatui::{
|
||||
backend::Backend,
|
||||
crossterm::event,
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
symbols,
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Gauge, LineGauge, List, ListItem, Paragraph, Widget},
|
||||
Frame, Terminal, TerminalOptions, Viewport,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
@@ -107,7 +110,7 @@ fn input_handling(tx: mpsc::Sender<Event>) {
|
||||
event::Event::Key(key) => tx.send(Event::Input(key)).unwrap(),
|
||||
event::Event::Resize(_, _) => tx.send(Event::Resize).unwrap(),
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
}
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
tx.send(Event::Tick).unwrap();
|
||||
@@ -117,7 +120,7 @@ fn input_handling(tx: mpsc::Sender<Event>) {
|
||||
});
|
||||
}
|
||||
|
||||
#[expect(clippy::cast_precision_loss, clippy::needless_pass_by_value)]
|
||||
#[allow(clippy::cast_precision_loss, clippy::needless_pass_by_value)]
|
||||
fn workers(tx: mpsc::Sender<Event>) -> Vec<Worker> {
|
||||
(0..4)
|
||||
.map(|id| {
|
||||
@@ -157,20 +160,17 @@ fn downloads() -> Downloads {
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::needless_pass_by_value)]
|
||||
fn run<B: Backend>(
|
||||
terminal: &mut Terminal<B>,
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn run(
|
||||
terminal: &mut Terminal<impl Backend>,
|
||||
workers: Vec<Worker>,
|
||||
mut downloads: Downloads,
|
||||
rx: mpsc::Receiver<Event>,
|
||||
) -> Result<()>
|
||||
where
|
||||
B::Error: Send + Sync + 'static,
|
||||
{
|
||||
) -> Result<()> {
|
||||
let mut redraw = true;
|
||||
loop {
|
||||
if redraw {
|
||||
terminal.draw(|frame| render(frame, &downloads))?;
|
||||
terminal.draw(|frame| draw(frame, &downloads))?;
|
||||
}
|
||||
redraw = true;
|
||||
|
||||
@@ -215,14 +215,14 @@ where
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render(frame: &mut Frame, downloads: &Downloads) {
|
||||
fn draw(frame: &mut Frame, downloads: &Downloads) {
|
||||
let area = frame.area();
|
||||
|
||||
let block = Block::new().title(Line::from("Progress").centered());
|
||||
@@ -235,7 +235,7 @@ fn render(frame: &mut Frame, downloads: &Downloads) {
|
||||
|
||||
// total progress
|
||||
let done = NUM_DOWNLOADS - downloads.pending.len() - downloads.in_progress.len();
|
||||
#[expect(clippy::cast_precision_loss)]
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
let progress = LineGauge::default()
|
||||
.filled_style(Style::default().fg(Color::Blue))
|
||||
.label(format!("{done}/{NUM_DOWNLOADS}"))
|
||||
@@ -265,7 +265,7 @@ fn render(frame: &mut Frame, downloads: &Downloads) {
|
||||
let list = List::new(items);
|
||||
frame.render_widget(list, list_area);
|
||||
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
for (i, (_, download)) in downloads.in_progress.iter().enumerate() {
|
||||
let gauge = Gauge::default()
|
||||
.gauge_style(Style::default().fg(Color::Yellow))
|
||||
|
||||
@@ -15,13 +15,15 @@
|
||||
//! [`tui-textarea`]: https://crates.io/crates/tui-textarea
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode, KeyEvent};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Layout, Offset, Rect};
|
||||
use ratatui::style::Stylize;
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::Widget;
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Layout, Offset, Rect},
|
||||
style::Stylize,
|
||||
text::Line,
|
||||
widgets::Widget,
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@@ -71,12 +73,13 @@ impl App {
|
||||
}
|
||||
|
||||
fn handle_events(&mut self) -> Result<()> {
|
||||
if let Some(key) = event::read()?.as_key_press_event() {
|
||||
match key.code {
|
||||
match event::read()? {
|
||||
Event::Key(event) if event.kind == KeyEventKind::Press => match event.code {
|
||||
KeyCode::Esc => self.state = AppState::Cancelled,
|
||||
KeyCode::Enter => self.state = AppState::Submitted,
|
||||
_ => self.form.on_key_press(key),
|
||||
}
|
||||
_ => self.form.on_key_press(event),
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -235,14 +238,14 @@ impl AgeField {
|
||||
KeyCode::Up | KeyCode::Char('k') => self.increment(),
|
||||
KeyCode::Down | KeyCode::Char('j') => self.decrement(),
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn increment(&mut self) {
|
||||
self.value = self.value.saturating_add(1).min(Self::MAX);
|
||||
}
|
||||
|
||||
const fn decrement(&mut self) {
|
||||
fn decrement(&mut self) {
|
||||
self.value = self.value.saturating_sub(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,18 +11,21 @@
|
||||
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
|
||||
/// [examples]: https://github.com/ratatui/ratatui/blob/main/examples
|
||||
/// [hello-world]: https://github.com/ratatui/ratatui/blob/main/examples/apps/hello-world
|
||||
use crossterm::event;
|
||||
use ratatui::text::Text;
|
||||
use crossterm::event::{self, Event};
|
||||
use ratatui::{text::Text, Frame};
|
||||
|
||||
fn main() {
|
||||
let mut terminal = ratatui::init();
|
||||
loop {
|
||||
terminal
|
||||
.draw(|frame| frame.render_widget(Text::raw("Hello World!"), frame.area()))
|
||||
.expect("failed to draw frame");
|
||||
if event::read().expect("failed to read event").is_key_press() {
|
||||
terminal.draw(draw).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());
|
||||
}
|
||||
|
||||
@@ -10,13 +10,15 @@
|
||||
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
|
||||
use std::{error::Error, iter::once, result};
|
||||
|
||||
use crossterm::event;
|
||||
use itertools::Itertools;
|
||||
use ratatui::layout::{Constraint, Layout};
|
||||
use ratatui::style::{Color, Modifier, Style, Stylize};
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use ratatui::{
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Constraint, Layout},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
text::Line,
|
||||
widgets::Paragraph,
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
|
||||
type Result<T> = result::Result<T, Box<dyn Error>>;
|
||||
|
||||
@@ -30,14 +32,16 @@ fn main() -> Result<()> {
|
||||
|
||||
fn run(mut terminal: DefaultTerminal) -> Result<()> {
|
||||
loop {
|
||||
terminal.draw(render)?;
|
||||
if event::read()?.is_key_press() {
|
||||
return Ok(());
|
||||
terminal.draw(draw)?;
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render(frame: &mut Frame) {
|
||||
fn draw(frame: &mut Frame) {
|
||||
let vertical = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
|
||||
let [text_area, main_area] = vertical.areas(frame.area());
|
||||
frame.render_widget(
|
||||
|
||||
@@ -9,8 +9,8 @@ rust-version.workspace = true
|
||||
color-eyre.workspace = true
|
||||
crossterm.workspace = true
|
||||
## a collection of line drawing algorithms (e.g. Bresenham's line algorithm)
|
||||
line_drawing = "1.0.1"
|
||||
rand = "0.9.1"
|
||||
line_drawing = "1.0.0"
|
||||
rand = "0.9.0"
|
||||
ratatui.workspace = true
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -9,15 +9,20 @@
|
||||
///
|
||||
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{
|
||||
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, MouseEvent,
|
||||
MouseEventKind,
|
||||
use crossterm::{
|
||||
event::{
|
||||
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, MouseEvent,
|
||||
MouseEventKind,
|
||||
},
|
||||
execute,
|
||||
};
|
||||
use ratatui::{
|
||||
layout::{Position, Rect, Size},
|
||||
style::{Color, Stylize},
|
||||
symbols,
|
||||
text::Line,
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
use crossterm::execute;
|
||||
use ratatui::layout::{Position, Rect, Size};
|
||||
use ratatui::style::{Color, Stylize};
|
||||
use ratatui::text::Line;
|
||||
use ratatui::{symbols, DefaultTerminal, Frame};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
@@ -60,11 +65,8 @@ impl MouseDrawingApp {
|
||||
}
|
||||
|
||||
/// Quit the app if the user presses 'q' or 'Esc'
|
||||
fn on_key_event(&mut self, key: KeyEvent) {
|
||||
if !key.is_press() {
|
||||
return;
|
||||
}
|
||||
match key.code {
|
||||
fn on_key_event(&mut self, event: KeyEvent) {
|
||||
match event.code {
|
||||
KeyCode::Char(' ') => {
|
||||
self.current_color = Color::Rgb(rand::random(), rand::random(), rand::random());
|
||||
}
|
||||
|
||||
@@ -30,10 +30,12 @@
|
||||
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
|
||||
/// [Color Eyre recipe]: https://ratatui.rs/recipes/apps/color-eyre
|
||||
use color_eyre::{eyre::bail, Result};
|
||||
use crossterm::event::{self, KeyCode};
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::{Block, Paragraph};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use ratatui::{
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
text::Line,
|
||||
widgets::{Block, Paragraph},
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
@@ -53,9 +55,9 @@ impl App {
|
||||
|
||||
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
|
||||
loop {
|
||||
terminal.draw(|frame| self.render(frame))?;
|
||||
terminal.draw(|frame| self.draw(frame))?;
|
||||
|
||||
if let Some(key) = event::read()?.as_key_press_event() {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
match key.code {
|
||||
KeyCode::Char('p') => panic!("intentional demo panic"),
|
||||
KeyCode::Char('e') => bail!("intentional demo error"),
|
||||
@@ -70,7 +72,7 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self, frame: &mut Frame) {
|
||||
fn draw(&self, frame: &mut Frame) {
|
||||
let text = vec![
|
||||
if self.hook_enabled {
|
||||
Line::from("HOOK IS CURRENTLY **ENABLED**")
|
||||
|
||||
@@ -8,11 +8,13 @@
|
||||
///
|
||||
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode};
|
||||
use ratatui::layout::{Constraint, Flex, Layout, Rect};
|
||||
use ratatui::style::Stylize;
|
||||
use ratatui::widgets::{Block, Clear, Paragraph, Wrap};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use ratatui::{
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Constraint, Flex, Layout, Rect},
|
||||
style::Stylize,
|
||||
widgets::{Block, Clear, Paragraph, Wrap},
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
@@ -30,19 +32,21 @@ struct App {
|
||||
impl App {
|
||||
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
|
||||
loop {
|
||||
terminal.draw(|frame| self.render(frame))?;
|
||||
terminal.draw(|frame| self.draw(frame))?;
|
||||
|
||||
if let Some(key) = event::read()?.as_key_press_event() {
|
||||
match key.code {
|
||||
KeyCode::Char('q') => return Ok(()),
|
||||
KeyCode::Char('p') => self.show_popup = !self.show_popup,
|
||||
_ => {}
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
match key.code {
|
||||
KeyCode::Char('q') => return Ok(()),
|
||||
KeyCode::Char('p') => self.show_popup = !self.show_popup,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self, frame: &mut Frame) {
|
||||
fn draw(&self, frame: &mut Frame) {
|
||||
let area = frame.area();
|
||||
|
||||
let vertical = Layout::vertical([Constraint::Percentage(20), Constraint::Percentage(80)]);
|
||||
|
||||
@@ -11,13 +11,15 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode};
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Margin};
|
||||
use ratatui::style::{Color, Style, Stylize};
|
||||
use ratatui::symbols::scrollbar;
|
||||
use ratatui::text::{Line, Masked, Span};
|
||||
use ratatui::widgets::{Block, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use ratatui::{
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
layout::{Alignment, Constraint, Layout, Margin},
|
||||
style::{Color, Style, Stylize},
|
||||
symbols::scrollbar,
|
||||
text::{Line, Masked, Span},
|
||||
widgets::{Block, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState},
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
struct App {
|
||||
@@ -40,52 +42,47 @@ impl App {
|
||||
let tick_rate = Duration::from_millis(250);
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
terminal.draw(|frame| self.render(frame))?;
|
||||
terminal.draw(|frame| self.draw(frame))?;
|
||||
|
||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||
if !event::poll(timeout)? {
|
||||
last_tick = Instant::now();
|
||||
continue;
|
||||
}
|
||||
if let Some(key) = event::read()?.as_key_press_event() {
|
||||
match key.code {
|
||||
KeyCode::Char('q') => return Ok(()),
|
||||
KeyCode::Char('j') | KeyCode::Down => self.scroll_down(),
|
||||
KeyCode::Char('k') | KeyCode::Up => self.scroll_up(),
|
||||
KeyCode::Char('h') | KeyCode::Left => self.scroll_left(),
|
||||
KeyCode::Char('l') | KeyCode::Right => self.scroll_right(),
|
||||
_ => {}
|
||||
if event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
match key.code {
|
||||
KeyCode::Char('q') => return Ok(()),
|
||||
KeyCode::Char('j') | KeyCode::Down => {
|
||||
self.vertical_scroll = self.vertical_scroll.saturating_add(1);
|
||||
self.vertical_scroll_state =
|
||||
self.vertical_scroll_state.position(self.vertical_scroll);
|
||||
}
|
||||
KeyCode::Char('k') | KeyCode::Up => {
|
||||
self.vertical_scroll = self.vertical_scroll.saturating_sub(1);
|
||||
self.vertical_scroll_state =
|
||||
self.vertical_scroll_state.position(self.vertical_scroll);
|
||||
}
|
||||
KeyCode::Char('h') | KeyCode::Left => {
|
||||
self.horizontal_scroll = self.horizontal_scroll.saturating_sub(1);
|
||||
self.horizontal_scroll_state = self
|
||||
.horizontal_scroll_state
|
||||
.position(self.horizontal_scroll);
|
||||
}
|
||||
KeyCode::Char('l') | KeyCode::Right => {
|
||||
self.horizontal_scroll = self.horizontal_scroll.saturating_add(1);
|
||||
self.horizontal_scroll_state = self
|
||||
.horizontal_scroll_state
|
||||
.position(self.horizontal_scroll);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
last_tick = Instant::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scroll_down(&mut self) {
|
||||
self.vertical_scroll = self.vertical_scroll.saturating_add(1);
|
||||
self.vertical_scroll_state = self.vertical_scroll_state.position(self.vertical_scroll);
|
||||
}
|
||||
|
||||
fn scroll_up(&mut self) {
|
||||
self.vertical_scroll = self.vertical_scroll.saturating_sub(1);
|
||||
self.vertical_scroll_state = self.vertical_scroll_state.position(self.vertical_scroll);
|
||||
}
|
||||
|
||||
fn scroll_left(&mut self) {
|
||||
self.horizontal_scroll = self.horizontal_scroll.saturating_sub(1);
|
||||
self.horizontal_scroll_state = self
|
||||
.horizontal_scroll_state
|
||||
.position(self.horizontal_scroll);
|
||||
}
|
||||
|
||||
fn scroll_right(&mut self) {
|
||||
self.horizontal_scroll = self.horizontal_scroll.saturating_add(1);
|
||||
self.horizontal_scroll_state = self
|
||||
.horizontal_scroll_state
|
||||
.position(self.horizontal_scroll);
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines, clippy::cast_possible_truncation)]
|
||||
fn render(&mut self, frame: &mut Frame) {
|
||||
#[allow(clippy::too_many_lines, clippy::cast_possible_truncation)]
|
||||
fn draw(&mut self, frame: &mut Frame) {
|
||||
let area = frame.area();
|
||||
|
||||
// Words made "loooong" to demonstrate line breaking.
|
||||
|
||||
@@ -6,16 +6,19 @@
|
||||
///
|
||||
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode, KeyModifiers};
|
||||
use crossterm::event::KeyModifiers;
|
||||
use itertools::Itertools;
|
||||
use ratatui::layout::{Constraint, Layout, Margin, Rect};
|
||||
use ratatui::style::{self, Color, Modifier, Style, Stylize};
|
||||
use ratatui::text::Text;
|
||||
use ratatui::widgets::{
|
||||
Block, BorderType, Cell, HighlightSpacing, Paragraph, Row, Scrollbar, ScrollbarOrientation,
|
||||
ScrollbarState, Table, TableState,
|
||||
use ratatui::{
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Constraint, Layout, Margin, Rect},
|
||||
style::{self, Color, Modifier, Style, Stylize},
|
||||
text::Text,
|
||||
widgets::{
|
||||
Block, BorderType, Cell, HighlightSpacing, Paragraph, Row, Scrollbar, ScrollbarOrientation,
|
||||
ScrollbarState, Table, TableState,
|
||||
},
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use style::palette::tailwind;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
@@ -80,20 +83,14 @@ impl Data {
|
||||
[&self.name, &self.address, &self.email]
|
||||
}
|
||||
|
||||
// https://github.com/rust-lang/rust/issues/139338
|
||||
#[allow(clippy::missing_const_for_fn)]
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
// https://github.com/rust-lang/rust/issues/139338
|
||||
#[allow(clippy::missing_const_for_fn)]
|
||||
fn address(&self) -> &str {
|
||||
&self.address
|
||||
}
|
||||
|
||||
// https://github.com/rust-lang/rust/issues/139338
|
||||
#[allow(clippy::missing_const_for_fn)]
|
||||
fn email(&self) -> &str {
|
||||
&self.email
|
||||
}
|
||||
@@ -158,42 +155,44 @@ impl App {
|
||||
self.state.select_previous_column();
|
||||
}
|
||||
|
||||
pub const fn next_color(&mut self) {
|
||||
pub fn next_color(&mut self) {
|
||||
self.color_index = (self.color_index + 1) % PALETTES.len();
|
||||
}
|
||||
|
||||
pub const fn previous_color(&mut self) {
|
||||
pub fn previous_color(&mut self) {
|
||||
let count = PALETTES.len();
|
||||
self.color_index = (self.color_index + count - 1) % count;
|
||||
}
|
||||
|
||||
pub const fn set_colors(&mut self) {
|
||||
pub fn set_colors(&mut self) {
|
||||
self.colors = TableColors::new(&PALETTES[self.color_index]);
|
||||
}
|
||||
|
||||
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
|
||||
loop {
|
||||
terminal.draw(|frame| self.render(frame))?;
|
||||
terminal.draw(|frame| self.draw(frame))?;
|
||||
|
||||
if let Some(key) = event::read()?.as_key_press_event() {
|
||||
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();
|
||||
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('l') | KeyCode::Right => self.next_column(),
|
||||
KeyCode::Char('h') | KeyCode::Left => self.previous_column(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, frame: &mut Frame) {
|
||||
fn draw(&mut self, frame: &mut Frame) {
|
||||
let vertical = &Layout::vertical([Constraint::Min(5), Constraint::Length(4)]);
|
||||
let rects = vertical.split(frame.area());
|
||||
|
||||
@@ -336,7 +335,7 @@ fn constraint_len_calculator(items: &[Data]) -> (u16, u16, u16) {
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
(name_len as u16, address_len as u16, email_len as u16)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,17 +6,22 @@
|
||||
///
|
||||
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode, KeyEvent};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::palette::tailwind::{BLUE, GREEN, SLATE};
|
||||
use ratatui::style::{Color, Modifier, Style, Stylize};
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::{
|
||||
Block, Borders, HighlightSpacing, List, ListItem, ListState, Padding, Paragraph,
|
||||
StatefulWidget, Widget, Wrap,
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{
|
||||
palette::tailwind::{BLUE, GREEN, SLATE},
|
||||
Color, Modifier, Style, Stylize,
|
||||
},
|
||||
symbols,
|
||||
text::Line,
|
||||
widgets::{
|
||||
Block, Borders, HighlightSpacing, List, ListItem, ListState, Padding, Paragraph,
|
||||
StatefulWidget, Widget, Wrap,
|
||||
},
|
||||
DefaultTerminal,
|
||||
};
|
||||
use ratatui::{symbols, DefaultTerminal};
|
||||
|
||||
const TODO_HEADER_STYLE: Style = Style::new().fg(SLATE.c100).bg(BLUE.c800);
|
||||
const NORMAL_ROW_BG: Color = SLATE.c950;
|
||||
@@ -103,14 +108,17 @@ impl App {
|
||||
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
|
||||
while !self.should_exit {
|
||||
terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?;
|
||||
if let Some(key) = event::read()?.as_key_press_event() {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
self.handle_key(key);
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_key(&mut self, key: KeyEvent) {
|
||||
if key.kind != KeyEventKind::Press {
|
||||
return;
|
||||
}
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.should_exit = true,
|
||||
KeyCode::Char('h') | KeyCode::Left => self.select_none(),
|
||||
@@ -125,7 +133,7 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
const fn select_none(&mut self) {
|
||||
fn select_none(&mut self) {
|
||||
self.todo_list.state.select(None);
|
||||
}
|
||||
|
||||
@@ -136,11 +144,11 @@ impl App {
|
||||
self.todo_list.state.select_previous();
|
||||
}
|
||||
|
||||
const fn select_first(&mut self) {
|
||||
fn select_first(&mut self) {
|
||||
self.todo_list.state.select_first();
|
||||
}
|
||||
|
||||
const fn select_last(&mut self) {
|
||||
fn select_last(&mut self) {
|
||||
self.todo_list.state.select_last();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,14 +20,14 @@
|
||||
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
|
||||
use std::{fs::File, time::Duration};
|
||||
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, Event, KeyCode};
|
||||
use ratatui::widgets::{Block, Paragraph};
|
||||
use ratatui::Frame;
|
||||
use color_eyre::{eyre::Context, Result};
|
||||
use ratatui::{
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
widgets::{Block, Paragraph},
|
||||
Frame,
|
||||
};
|
||||
use tracing::{debug, info, instrument, trace, Level};
|
||||
use tracing_appender::non_blocking;
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
use tracing_appender::{non_blocking, non_blocking::WorkerGuard};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@@ -40,7 +40,7 @@ fn main() -> Result<()> {
|
||||
let mut events = vec![]; // a buffer to store the recent events to display in the UI
|
||||
while !should_exit(&events) {
|
||||
handle_events(&mut events)?;
|
||||
terminal.draw(|frame| render(frame, &events))?;
|
||||
terminal.draw(|frame| draw(frame, &events))?;
|
||||
}
|
||||
ratatui::restore();
|
||||
|
||||
@@ -69,7 +69,7 @@ fn handle_events(events: &mut Vec<Event>) -> Result<()> {
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
fn render(frame: &mut Frame, events: &[Event]) {
|
||||
fn draw(frame: &mut Frame, events: &[Event]) {
|
||||
// To view this event, run the example with `RUST_LOG=tracing=debug cargo run --example tracing`
|
||||
trace!(frame_count = frame.count(), event_count = events.len());
|
||||
let events = events.iter().map(|e| format!("{e:?}")).collect::<Vec<_>>();
|
||||
|
||||
@@ -21,12 +21,14 @@
|
||||
///
|
||||
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode, KeyEventKind};
|
||||
use ratatui::layout::{Constraint, Layout, Position};
|
||||
use ratatui::style::{Color, Modifier, Style, Stylize};
|
||||
use ratatui::text::{Line, Span, Text};
|
||||
use ratatui::widgets::{Block, List, ListItem, Paragraph};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use ratatui::{
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Constraint, Layout, Position},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
text::{Line, Span, Text},
|
||||
widgets::{Block, List, ListItem, Paragraph},
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
@@ -117,7 +119,7 @@ impl App {
|
||||
new_cursor_pos.clamp(0, self.input.chars().count())
|
||||
}
|
||||
|
||||
const fn reset_cursor(&mut self) {
|
||||
fn reset_cursor(&mut self) {
|
||||
self.character_index = 0;
|
||||
}
|
||||
|
||||
@@ -129,9 +131,9 @@ impl App {
|
||||
|
||||
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
|
||||
loop {
|
||||
terminal.draw(|frame| self.render(frame))?;
|
||||
terminal.draw(|frame| self.draw(frame))?;
|
||||
|
||||
if let Some(key) = event::read()?.as_key_press_event() {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
match self.input_mode {
|
||||
InputMode::Normal => match key.code {
|
||||
KeyCode::Char('e') => {
|
||||
@@ -157,7 +159,7 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self, frame: &mut Frame) {
|
||||
fn draw(&self, frame: &mut Frame) {
|
||||
let vertical = Layout::vertical([
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(3),
|
||||
@@ -204,7 +206,7 @@ impl App {
|
||||
|
||||
// Make the cursor visible and ask ratatui to put it at the specified coordinates after
|
||||
// rendering
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
InputMode::Editing => frame.set_cursor_position(Position::new(
|
||||
// Draw the cursor at the current position in the input field.
|
||||
// This position can be controlled via the left and right arrow key
|
||||
|
||||
@@ -8,7 +8,7 @@ rust-version.workspace = true
|
||||
[dependencies]
|
||||
color-eyre.workspace = true
|
||||
crossterm.workspace = true
|
||||
rand = "0.9.1"
|
||||
rand = "0.9.0"
|
||||
ratatui.workspace = true
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -9,13 +9,15 @@
|
||||
//! [`BarChart`]: https://docs.rs/ratatui/latest/ratatui/widgets/struct.BarChart.html
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, KeyCode};
|
||||
use rand::{rng, Rng};
|
||||
use ratatui::layout::{Constraint, Layout};
|
||||
use ratatui::style::{Color, Style, Stylize};
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::{Bar, BarChart, BarGroup};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use ratatui::{
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Constraint, Layout},
|
||||
style::{Color, Style, Stylize},
|
||||
text::Line,
|
||||
widgets::{Bar, BarChart, BarGroup},
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
@@ -42,23 +44,22 @@ impl App {
|
||||
|
||||
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
|
||||
while !self.should_exit {
|
||||
terminal.draw(|frame| self.render(frame))?;
|
||||
terminal.draw(|frame| self.draw(frame))?;
|
||||
self.handle_events()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_events(&mut self) -> Result<()> {
|
||||
if event::read()?
|
||||
.as_key_press_event()
|
||||
.is_some_and(|key| key.code == KeyCode::Char('q'))
|
||||
{
|
||||
self.should_exit = true;
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
||||
self.should_exit = true;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render(&self, frame: &mut Frame) {
|
||||
fn draw(&self, frame: &mut Frame) {
|
||||
let [title, main] = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)])
|
||||
.spacing(1)
|
||||
.areas(frame.area());
|
||||
|
||||
@@ -14,10 +14,12 @@ use std::iter::zip;
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||
use ratatui::widgets::{Block, Paragraph, Widget, WidgetRef};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
widgets::{Block, Paragraph, Widget, WidgetRef},
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
@@ -30,8 +32,8 @@ fn main() -> Result<()> {
|
||||
fn run(mut terminal: DefaultTerminal) -> Result<()> {
|
||||
loop {
|
||||
terminal.draw(render)?;
|
||||
if event::read()?.is_key_press() {
|
||||
return Ok(());
|
||||
if matches!(event::read()?, event::Event::Key(_)) {
|
||||
break Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ description = """
|
||||
Core types and traits for the Ratatui Terminal UI library.
|
||||
Widget libraries should use this crate. Applications should use the main Ratatui crate.
|
||||
"""
|
||||
version = "0.1.0-alpha.4"
|
||||
version = "0.1.0-alpha.2"
|
||||
readme = "README.md"
|
||||
authors.workspace = true
|
||||
documentation.workspace = true
|
||||
@@ -24,19 +24,6 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
[features]
|
||||
default = []
|
||||
|
||||
## enables std
|
||||
std = [
|
||||
"itertools/use_std",
|
||||
"thiserror/std",
|
||||
"kasuari/std",
|
||||
"compact_str/std",
|
||||
"unicode-truncate/std",
|
||||
"strum/std",
|
||||
]
|
||||
|
||||
## enables layout cache
|
||||
layout-cache = ["std"]
|
||||
|
||||
## enables conversions to / from colors, modifiers, and styles in the ['anstyle'] crate
|
||||
anstyle = ["dep:anstyle"]
|
||||
|
||||
@@ -56,21 +43,21 @@ scrolling-regions = []
|
||||
serde = ["dep:serde", "bitflags/serde", "compact_str/serde"]
|
||||
|
||||
[dependencies]
|
||||
anstyle = { workspace = true, optional = true }
|
||||
bitflags.workspace = true
|
||||
compact_str.workspace = true
|
||||
anstyle = { version = "1", optional = true }
|
||||
bitflags = "2.3"
|
||||
cassowary = "0.3"
|
||||
compact_str = "0.8.0"
|
||||
document-features = { workspace = true, optional = true }
|
||||
hashbrown.workspace = true
|
||||
indoc.workspace = true
|
||||
itertools.workspace = true
|
||||
kasuari = { workspace = true, default-features = false }
|
||||
lru.workspace = true
|
||||
palette = { workspace = true, optional = true }
|
||||
lru = "0.12.0"
|
||||
palette = { version = "0.7.6", optional = true }
|
||||
paste = "1.0.2"
|
||||
serde = { workspace = true, optional = true }
|
||||
strum.workspace = true
|
||||
thiserror = { workspace = true, default-features = false }
|
||||
thiserror = "2"
|
||||
unicode-segmentation.workspace = true
|
||||
unicode-truncate = { workspace = true, default-features = false }
|
||||
unicode-truncate = "2"
|
||||
unicode-width.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -100,11 +100,14 @@
|
||||
//! [Examples]: https://github.com/ratatui/ratatui/tree/main/ratatui/examples/README.md
|
||||
//! [Backend Comparison]: https://ratatui.rs/concepts/backends/comparison/
|
||||
//! [Ratatui Website]: https://ratatui.rs
|
||||
use std::io;
|
||||
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
use crate::buffer::Cell;
|
||||
use crate::layout::{Position, Size};
|
||||
use crate::{
|
||||
buffer::Cell,
|
||||
layout::{Position, Size},
|
||||
};
|
||||
|
||||
mod test;
|
||||
pub use self::test::TestBackend;
|
||||
@@ -146,22 +149,19 @@ pub struct WindowSize {
|
||||
///
|
||||
/// [`Terminal`]: https://docs.rs/ratatui/latest/ratatui/struct.Terminal.html
|
||||
pub trait Backend {
|
||||
/// Error type associated with this Backend.
|
||||
type Error: core::error::Error;
|
||||
|
||||
/// Draw the given content to the terminal screen.
|
||||
///
|
||||
/// The content is provided as an iterator over `(u16, u16, &Cell)` tuples, where the first two
|
||||
/// elements represent the x and y coordinates, and the third element is a reference to the
|
||||
/// [`Cell`] to be drawn.
|
||||
fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
|
||||
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
||||
where
|
||||
I: Iterator<Item = (u16, u16, &'a Cell)>;
|
||||
|
||||
/// Insert `n` line breaks to the terminal screen.
|
||||
///
|
||||
/// This method is optional and may not be implemented by all backends.
|
||||
fn append_lines(&mut self, _n: u16) -> Result<(), Self::Error> {
|
||||
fn append_lines(&mut self, _n: u16) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -183,14 +183,14 @@ pub trait Backend {
|
||||
/// ```
|
||||
///
|
||||
/// [`show_cursor`]: Self::show_cursor
|
||||
fn hide_cursor(&mut self) -> Result<(), Self::Error>;
|
||||
fn hide_cursor(&mut self) -> io::Result<()>;
|
||||
|
||||
/// Show the cursor on the terminal screen.
|
||||
///
|
||||
/// See [`hide_cursor`] for an example.
|
||||
///
|
||||
/// [`hide_cursor`]: Self::hide_cursor
|
||||
fn show_cursor(&mut self) -> Result<(), Self::Error>;
|
||||
fn show_cursor(&mut self) -> io::Result<()>;
|
||||
|
||||
/// Get the current cursor position on the terminal screen.
|
||||
///
|
||||
@@ -200,7 +200,7 @@ pub trait Backend {
|
||||
/// See [`set_cursor_position`] for an example.
|
||||
///
|
||||
/// [`set_cursor_position`]: Self::set_cursor_position
|
||||
fn get_cursor_position(&mut self) -> Result<Position, Self::Error>;
|
||||
fn get_cursor_position(&mut self) -> io::Result<Position>;
|
||||
|
||||
/// Set the cursor position on the terminal screen to the given x and y coordinates.
|
||||
///
|
||||
@@ -217,14 +217,14 @@ pub trait Backend {
|
||||
/// assert_eq!(backend.get_cursor_position()?, Position { x: 10, y: 20 });
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> Result<(), Self::Error>;
|
||||
fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()>;
|
||||
|
||||
/// Get the current cursor position on the terminal screen.
|
||||
///
|
||||
/// The returned tuple contains the x and y coordinates of the cursor. The origin
|
||||
/// (0, 0) is at the top left corner of the screen.
|
||||
#[deprecated = "use `get_cursor_position()` instead which returns `Result<Position>`"]
|
||||
fn get_cursor(&mut self) -> Result<(u16, u16), Self::Error> {
|
||||
#[deprecated = "the method get_cursor_position indicates more clearly what about the cursor to get"]
|
||||
fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
|
||||
let Position { x, y } = self.get_cursor_position()?;
|
||||
Ok((x, y))
|
||||
}
|
||||
@@ -232,8 +232,8 @@ pub trait Backend {
|
||||
/// Set the cursor position on the terminal screen to the given x and y coordinates.
|
||||
///
|
||||
/// The origin (0, 0) is at the top left corner of the screen.
|
||||
#[deprecated = "use `set_cursor_position((x, y))` instead which takes `impl Into<Position>`"]
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), Self::Error> {
|
||||
#[deprecated = "the method set_cursor_position indicates more clearly what about the cursor to set"]
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
|
||||
self.set_cursor_position(Position { x, y })
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ pub trait Backend {
|
||||
/// backend.clear()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
fn clear(&mut self) -> Result<(), Self::Error>;
|
||||
fn clear(&mut self) -> io::Result<()>;
|
||||
|
||||
/// Clears a specific region of the terminal specified by the [`ClearType`] parameter
|
||||
///
|
||||
@@ -274,7 +274,18 @@ pub trait Backend {
|
||||
/// return an error if the `clear_type` is not supported by the backend.
|
||||
///
|
||||
/// [`clear`]: Self::clear
|
||||
fn clear_region(&mut self, clear_type: ClearType) -> Result<(), Self::Error>;
|
||||
fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
|
||||
match clear_type {
|
||||
ClearType::All => self.clear(),
|
||||
ClearType::AfterCursor
|
||||
| ClearType::BeforeCursor
|
||||
| ClearType::CurrentLine
|
||||
| ClearType::UntilNewLine => Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("clear_type [{clear_type:?}] not supported with this backend"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the size of the terminal screen in columns/rows as a [`Size`].
|
||||
///
|
||||
@@ -288,19 +299,19 @@ pub trait Backend {
|
||||
/// use ratatui::{backend::Backend, layout::Size};
|
||||
///
|
||||
/// assert_eq!(backend.size()?, Size::new(80, 25));
|
||||
/// # Result::Ok(())
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
fn size(&self) -> Result<Size, Self::Error>;
|
||||
fn size(&self) -> io::Result<Size>;
|
||||
|
||||
/// Get the size of the terminal screen in columns/rows and pixels as a [`WindowSize`].
|
||||
///
|
||||
/// The reason for this not returning only the pixel size, given the redundancy with the
|
||||
/// `size()` method, is that the underlying backends most likely get both values with one
|
||||
/// syscall, and the user is also most likely to need columns and rows along with pixel size.
|
||||
fn window_size(&mut self) -> Result<WindowSize, Self::Error>;
|
||||
fn window_size(&mut self) -> io::Result<WindowSize>;
|
||||
|
||||
/// Flush any buffered content to the terminal screen.
|
||||
fn flush(&mut self) -> Result<(), Self::Error>;
|
||||
fn flush(&mut self) -> io::Result<()>;
|
||||
|
||||
/// Scroll a region of the screen upwards, where a region is specified by a (half-open) range
|
||||
/// of rows.
|
||||
@@ -332,11 +343,8 @@ pub trait Backend {
|
||||
/// For examples of how this function is expected to work, refer to the tests for
|
||||
/// [`TestBackend::scroll_region_up`].
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn scroll_region_up(
|
||||
&mut self,
|
||||
region: core::ops::Range<u16>,
|
||||
line_count: u16,
|
||||
) -> Result<(), Self::Error>;
|
||||
fn scroll_region_up(&mut self, region: std::ops::Range<u16>, line_count: u16)
|
||||
-> io::Result<()>;
|
||||
|
||||
/// Scroll a region of the screen downwards, where a region is specified by a (half-open) range
|
||||
/// of rows.
|
||||
@@ -359,15 +367,13 @@ pub trait Backend {
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn scroll_region_down(
|
||||
&mut self,
|
||||
region: core::ops::Range<u16>,
|
||||
region: std::ops::Range<u16>,
|
||||
line_count: u16,
|
||||
) -> Result<(), Self::Error>;
|
||||
) -> io::Result<()>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
|
||||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
//! This module provides the `TestBackend` implementation for the [`Backend`] trait.
|
||||
//! It is used in the integration tests to verify the correctness of the library.
|
||||
|
||||
use alloc::string::String;
|
||||
use alloc::vec;
|
||||
use core::fmt::{self, Write};
|
||||
use core::iter;
|
||||
use std::{
|
||||
fmt::{self, Write},
|
||||
io, iter,
|
||||
};
|
||||
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::backend::{Backend, ClearType, WindowSize};
|
||||
use crate::buffer::{Buffer, Cell};
|
||||
use crate::layout::{Position, Rect, Size};
|
||||
use crate::{
|
||||
backend::{Backend, ClearType, WindowSize},
|
||||
buffer::{Buffer, Cell},
|
||||
layout::{Position, Rect, Size},
|
||||
};
|
||||
|
||||
/// A [`Backend`] implementation used for integration testing that renders to an memory buffer.
|
||||
///
|
||||
@@ -27,7 +29,7 @@ use crate::layout::{Position, Rect, Size};
|
||||
/// let mut backend = TestBackend::new(10, 2);
|
||||
/// backend.clear()?;
|
||||
/// backend.assert_buffer_lines([" "; 2]);
|
||||
/// # Result::Ok(())
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
@@ -56,7 +58,7 @@ fn buffer_view(buffer: &Buffer) -> String {
|
||||
} else {
|
||||
overwritten.push((x, c.symbol()));
|
||||
}
|
||||
skip = core::cmp::max(skip, c.symbol().width()).saturating_sub(1);
|
||||
skip = std::cmp::max(skip, c.symbol().width()).saturating_sub(1);
|
||||
}
|
||||
view.push('"');
|
||||
if !overwritten.is_empty() {
|
||||
@@ -138,7 +140,7 @@ impl TestBackend {
|
||||
///
|
||||
/// When they are not equal, a panic occurs with a detailed error message showing the
|
||||
/// differences between the expected and actual buffers.
|
||||
#[expect(deprecated)]
|
||||
#[allow(deprecated)]
|
||||
#[track_caller]
|
||||
pub fn assert_buffer(&self, expected: &Buffer) {
|
||||
// TODO: use assert_eq!()
|
||||
@@ -232,12 +234,8 @@ impl fmt::Display for TestBackend {
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T, E = core::convert::Infallible> = core::result::Result<T, E>;
|
||||
|
||||
impl Backend for TestBackend {
|
||||
type Error = core::convert::Infallible;
|
||||
|
||||
fn draw<'a, I>(&mut self, content: I) -> Result<()>
|
||||
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
||||
where
|
||||
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
||||
{
|
||||
@@ -247,31 +245,31 @@ impl Backend for TestBackend {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn hide_cursor(&mut self) -> Result<()> {
|
||||
fn hide_cursor(&mut self) -> io::Result<()> {
|
||||
self.cursor = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn show_cursor(&mut self) -> Result<()> {
|
||||
fn show_cursor(&mut self) -> io::Result<()> {
|
||||
self.cursor = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_cursor_position(&mut self) -> Result<Position> {
|
||||
fn get_cursor_position(&mut self) -> io::Result<Position> {
|
||||
Ok(self.pos.into())
|
||||
}
|
||||
|
||||
fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> Result<()> {
|
||||
fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> {
|
||||
self.pos = position.into().into();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear(&mut self) -> Result<()> {
|
||||
fn clear(&mut self) -> io::Result<()> {
|
||||
self.buffer.reset();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear_region(&mut self, clear_type: ClearType) -> Result<()> {
|
||||
fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
|
||||
let region = match clear_type {
|
||||
ClearType::All => return self.clear(),
|
||||
ClearType::AfterCursor => {
|
||||
@@ -311,7 +309,7 @@ impl Backend for TestBackend {
|
||||
/// the cursor y position then that number of empty lines (at most the buffer's height in this
|
||||
/// case but this limit is instead replaced with scrolling in most backend implementations) will
|
||||
/// be added after the current position and the cursor will be moved to the last row.
|
||||
fn append_lines(&mut self, line_count: u16) -> Result<()> {
|
||||
fn append_lines(&mut self, line_count: u16) -> io::Result<()> {
|
||||
let Position { x: cur_x, y: cur_y } = self.get_cursor_position()?;
|
||||
let Rect { width, height, .. } = self.buffer.area;
|
||||
|
||||
@@ -348,11 +346,11 @@ impl Backend for TestBackend {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn size(&self) -> Result<Size> {
|
||||
fn size(&self) -> io::Result<Size> {
|
||||
Ok(self.buffer.area.as_size())
|
||||
}
|
||||
|
||||
fn window_size(&mut self) -> Result<WindowSize> {
|
||||
fn window_size(&mut self) -> io::Result<WindowSize> {
|
||||
// Some arbitrary window pixel size, probably doesn't need much testing.
|
||||
const WINDOW_PIXEL_SIZE: Size = Size {
|
||||
width: 640,
|
||||
@@ -364,12 +362,12 @@ impl Backend for TestBackend {
|
||||
})
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<()> {
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn scroll_region_up(&mut self, region: core::ops::Range<u16>, scroll_by: u16) -> Result<()> {
|
||||
fn scroll_region_up(&mut self, region: std::ops::Range<u16>, scroll_by: u16) -> io::Result<()> {
|
||||
let width: usize = self.buffer.area.width.into();
|
||||
let cell_region_start = width * region.start.min(self.buffer.area.height) as usize;
|
||||
let cell_region_end = width * region.end.min(self.buffer.area.height) as usize;
|
||||
@@ -415,7 +413,11 @@ impl Backend for TestBackend {
|
||||
}
|
||||
|
||||
#[cfg(feature = "scrolling-regions")]
|
||||
fn scroll_region_down(&mut self, region: core::ops::Range<u16>, scroll_by: u16) -> Result<()> {
|
||||
fn scroll_region_down(
|
||||
&mut self,
|
||||
region: std::ops::Range<u16>,
|
||||
scroll_by: u16,
|
||||
) -> io::Result<()> {
|
||||
let width: usize = self.buffer.area.width.into();
|
||||
let cell_region_start = width * region.start.min(self.buffer.area.height) as usize;
|
||||
let cell_region_end = width * region.end.min(self.buffer.area.height) as usize;
|
||||
@@ -453,8 +455,6 @@ fn append_to_scrollback(scrollback: &mut Buffer, cells: impl IntoIterator<Item =
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
|
||||
use itertools::Itertools as _;
|
||||
|
||||
use super::*;
|
||||
@@ -916,7 +916,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_lines_truncates_beyond_u16_max() -> Result<()> {
|
||||
fn append_lines_truncates_beyond_u16_max() -> io::Result<()> {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
|
||||
// Fill the scrollback with 65535 + 10 lines.
|
||||
@@ -1030,7 +1030,7 @@ mod tests {
|
||||
#[case([A, B, C, D, E], 2..2, 2, [], [A, B, C, D, E])]
|
||||
fn scroll_region_up<const L: usize, const M: usize, const N: usize>(
|
||||
#[case] initial_screen: [&'static str; L],
|
||||
#[case] range: core::ops::Range<u16>,
|
||||
#[case] range: std::ops::Range<u16>,
|
||||
#[case] scroll_by: u16,
|
||||
#[case] expected_scrollback: [&'static str; M],
|
||||
#[case] expected_buffer: [&'static str; N],
|
||||
@@ -1064,7 +1064,7 @@ mod tests {
|
||||
#[case([A, B, C, D, E], 2..2, 2, [A, B, C, D, E])]
|
||||
fn scroll_region_down<const M: usize, const N: usize>(
|
||||
#[case] initial_screen: [&'static str; M],
|
||||
#[case] range: core::ops::Range<u16>,
|
||||
#[case] range: std::ops::Range<u16>,
|
||||
#[case] scroll_by: u16,
|
||||
#[case] expected_buffer: [&'static str; N],
|
||||
) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// # Panics
|
||||
/// When the buffers differ this method panics and displays the differences similar to
|
||||
/// `assert_eq!()`.
|
||||
#[deprecated = "use `assert_eq!(&actual, &expected)`"]
|
||||
#[deprecated = "use assert_eq!(&actual, &expected)"]
|
||||
#[macro_export]
|
||||
macro_rules! assert_buffer_eq {
|
||||
($actual_expr:expr, $expected_expr:expr) => {
|
||||
@@ -19,9 +19,9 @@ macro_rules! assert_buffer_eq {
|
||||
.enumerate()
|
||||
.map(|(i, (x, y, cell))| {
|
||||
let expected_cell = &expected[(x, y)];
|
||||
::alloc::format!("{i}: at ({x}, {y})\n expected: {expected_cell:?}\n actual: {cell:?}")
|
||||
format!("{i}: at ({x}, {y})\n expected: {expected_cell:?}\n actual: {cell:?}")
|
||||
})
|
||||
.collect::<::alloc::vec::Vec<::alloc::string::String>>()
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
assert!(
|
||||
nice_diff.is_empty(),
|
||||
@@ -38,12 +38,14 @@ macro_rules! assert_buffer_eq {
|
||||
};
|
||||
}
|
||||
|
||||
#[expect(deprecated)]
|
||||
#[allow(deprecated)]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::buffer::Buffer;
|
||||
use crate::layout::Rect;
|
||||
use crate::style::{Color, Style};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Color, Style},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn assert_buffer_eq_does_not_panic_on_equal_buffers() {
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use core::ops::{Index, IndexMut};
|
||||
use core::{cmp, fmt};
|
||||
use std::{
|
||||
fmt,
|
||||
ops::{Index, IndexMut},
|
||||
};
|
||||
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::buffer::Cell;
|
||||
use crate::layout::{Position, Rect};
|
||||
use crate::style::Style;
|
||||
use crate::text::{Line, Span};
|
||||
use crate::{
|
||||
buffer::Cell,
|
||||
layout::{Position, Rect},
|
||||
style::Style,
|
||||
text::{Line, Span},
|
||||
};
|
||||
|
||||
/// A buffer that maps to the desired content of the terminal after the draw call
|
||||
///
|
||||
@@ -21,9 +23,11 @@ use crate::text::{Line, Span};
|
||||
/// # Examples:
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui_core::buffer::{Buffer, Cell};
|
||||
/// use ratatui_core::layout::{Position, Rect};
|
||||
/// use ratatui_core::style::{Color, Style};
|
||||
/// use ratatui_core::{
|
||||
/// buffer::{Buffer, Cell},
|
||||
/// layout::{Position, Rect},
|
||||
/// style::{Color, Style},
|
||||
/// };
|
||||
///
|
||||
/// # fn foo() -> Option<()> {
|
||||
/// let mut buf = Buffer::empty(Rect {
|
||||
@@ -104,8 +108,6 @@ impl Buffer {
|
||||
}
|
||||
|
||||
/// Returns the content of the buffer as a slice
|
||||
// https://github.com/rust-lang/rust/issues/139338
|
||||
#[allow(clippy::missing_const_for_fn)]
|
||||
pub fn content(&self) -> &[Cell] {
|
||||
&self.content
|
||||
}
|
||||
@@ -127,7 +129,7 @@ impl Buffer {
|
||||
///
|
||||
/// Panics if the index is out of bounds.
|
||||
#[track_caller]
|
||||
#[deprecated = "use `Buffer[(x, y)]` instead. To avoid panicking, use `Buffer::cell((x, y))`. Both methods take `impl Into<Position>`."]
|
||||
#[deprecated(note = "Use Buffer[] or Buffer::cell instead")]
|
||||
#[must_use]
|
||||
pub fn get(&self, x: u16, y: u16) -> &Cell {
|
||||
let i = self.index_of(x, y);
|
||||
@@ -147,7 +149,7 @@ impl Buffer {
|
||||
///
|
||||
/// Panics if the position is outside the `Buffer`'s area.
|
||||
#[track_caller]
|
||||
#[deprecated = "use `Buffer[(x, y)]` instead. To avoid panicking, use `Buffer::cell_mut((x, y))`. Both methods take `impl Into<Position>`."]
|
||||
#[deprecated(note = "Use Buffer[] or Buffer::cell_mut instead")]
|
||||
#[must_use]
|
||||
pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
|
||||
let i = self.index_of(x, y);
|
||||
@@ -166,8 +168,10 @@ impl Buffer {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::buffer::{Buffer, Cell};
|
||||
/// use ratatui_core::layout::{Position, Rect};
|
||||
/// use ratatui_core::{
|
||||
/// buffer::{Buffer, Cell},
|
||||
/// layout::{Position, Rect},
|
||||
/// };
|
||||
///
|
||||
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
|
||||
///
|
||||
@@ -195,9 +199,11 @@ impl Buffer {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::buffer::{Buffer, Cell};
|
||||
/// use ratatui_core::layout::{Position, Rect};
|
||||
/// use ratatui_core::style::{Color, Style};
|
||||
/// use ratatui_core::{
|
||||
/// buffer::{Buffer, Cell},
|
||||
/// layout::{Position, Rect},
|
||||
/// style::{Color, Style},
|
||||
/// };
|
||||
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
|
||||
///
|
||||
/// if let Some(cell) = buffer.cell_mut(Position::new(0, 0)) {
|
||||
@@ -221,8 +227,7 @@ impl Buffer {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui_core::buffer::Buffer;
|
||||
/// use ratatui_core::layout::Rect;
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect};
|
||||
///
|
||||
/// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10));
|
||||
/// // Global coordinates to the top corner of this buffer's area
|
||||
@@ -234,8 +239,7 @@ impl Buffer {
|
||||
/// Panics when given an coordinate that is outside of this Buffer's area.
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use ratatui_core::buffer::Buffer;
|
||||
/// use ratatui_core::layout::Rect;
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect};
|
||||
///
|
||||
/// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10));
|
||||
/// // Top coordinate is outside of the buffer in global coordinate space, as the Buffer's area
|
||||
@@ -278,8 +282,7 @@ impl Buffer {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui_core::buffer::Buffer;
|
||||
/// use ratatui_core::layout::Rect;
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect};
|
||||
///
|
||||
/// let rect = Rect::new(200, 100, 10, 10);
|
||||
/// let buffer = Buffer::empty(rect);
|
||||
@@ -292,8 +295,7 @@ impl Buffer {
|
||||
/// Panics when given an index that is outside the Buffer's content.
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use ratatui_core::buffer::Buffer;
|
||||
/// use ratatui_core::layout::Rect;
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect};
|
||||
///
|
||||
/// let rect = Rect::new(0, 0, 10, 10); // 100 cells in total
|
||||
/// let buffer = Buffer::empty(rect);
|
||||
@@ -501,8 +503,8 @@ impl Buffer {
|
||||
|
||||
to_skip = current.symbol().width().saturating_sub(1);
|
||||
|
||||
let affected_width = cmp::max(current.symbol().width(), previous.symbol().width());
|
||||
invalidated = cmp::max(affected_width, invalidated).saturating_sub(1);
|
||||
let affected_width = std::cmp::max(current.symbol().width(), previous.symbol().width());
|
||||
invalidated = std::cmp::max(affected_width, invalidated).saturating_sub(1);
|
||||
}
|
||||
updates
|
||||
}
|
||||
@@ -524,8 +526,10 @@ impl<P: Into<Position>> Index<P> for Buffer {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui_core::buffer::{Buffer, Cell};
|
||||
/// use ratatui_core::layout::{Position, Rect};
|
||||
/// use ratatui_core::{
|
||||
/// buffer::{Buffer, Cell},
|
||||
/// layout::{Position, Rect},
|
||||
/// };
|
||||
///
|
||||
/// let buf = Buffer::empty(Rect::new(0, 0, 10, 10));
|
||||
/// let cell = &buf[(0, 0)];
|
||||
@@ -552,8 +556,10 @@ impl<P: Into<Position>> IndexMut<P> for Buffer {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui_core::buffer::{Buffer, Cell};
|
||||
/// use ratatui_core::layout::{Position, Rect};
|
||||
/// use ratatui_core::{
|
||||
/// buffer::{Buffer, Cell},
|
||||
/// layout::{Position, Rect},
|
||||
/// };
|
||||
///
|
||||
/// let mut buf = Buffer::empty(Rect::new(0, 0, 10, 10));
|
||||
/// buf[(0, 0)].set_symbol("A");
|
||||
@@ -594,7 +600,7 @@ impl fmt::Debug for Buffer {
|
||||
} else {
|
||||
overwritten.push((x, c.symbol()));
|
||||
}
|
||||
skip = cmp::max(skip, c.symbol().width()).saturating_sub(1);
|
||||
skip = std::cmp::max(skip, c.symbol().width()).saturating_sub(1);
|
||||
#[cfg(feature = "underline-color")]
|
||||
{
|
||||
let style = (c.fg, c.bg, c.underline_color, c.modifier);
|
||||
@@ -640,10 +646,7 @@ impl fmt::Debug for Buffer {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
use alloc::string::ToString;
|
||||
use core::iter;
|
||||
use std::{dbg, println};
|
||||
use std::iter;
|
||||
|
||||
use itertools::Itertools;
|
||||
use rstest::{fixture, rstest};
|
||||
@@ -961,11 +964,9 @@ mod tests {
|
||||
|
||||
// set_line only sets the style for non-empty cells (unlike Line::render which sets the
|
||||
// style for all cells)
|
||||
let expected_styles = iter::repeat_n(color, content.len().min(5))
|
||||
.chain(iter::repeat_n(
|
||||
Color::default(),
|
||||
5_usize.saturating_sub(content.len()),
|
||||
))
|
||||
let expected_styles = iter::repeat(color)
|
||||
.take(content.len().min(5))
|
||||
.chain(iter::repeat(Color::default()).take(5_usize.saturating_sub(content.len())))
|
||||
.collect_vec();
|
||||
assert_eq!(actual_contents, expected);
|
||||
assert_eq!(actual_styles, expected_styles);
|
||||
|
||||
@@ -83,13 +83,13 @@ impl Cell {
|
||||
}
|
||||
|
||||
/// Sets the foreground color of the cell.
|
||||
pub const fn set_fg(&mut self, color: Color) -> &mut Self {
|
||||
pub fn set_fg(&mut self, color: Color) -> &mut Self {
|
||||
self.fg = color;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the background color of the cell.
|
||||
pub const fn set_bg(&mut self, color: Color) -> &mut Self {
|
||||
pub fn set_bg(&mut self, color: Color) -> &mut Self {
|
||||
self.bg = color;
|
||||
self
|
||||
}
|
||||
@@ -132,7 +132,7 @@ impl Cell {
|
||||
///
|
||||
/// This is helpful when it is necessary to prevent the buffer from overwriting a cell that is
|
||||
/// covered by an image from some terminal graphics protocol (Sixel / iTerm / Kitty ...).
|
||||
pub const fn set_skip(&mut self, skip: bool) -> &mut Self {
|
||||
pub fn set_skip(&mut self, skip: bool) -> &mut Self {
|
||||
self.skip = skip;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ mod position;
|
||||
mod rect;
|
||||
mod size;
|
||||
|
||||
pub use alignment::{Alignment, HorizontalAlignment, VerticalAlignment};
|
||||
pub use alignment::Alignment;
|
||||
pub use constraint::Constraint;
|
||||
pub use direction::Direction;
|
||||
pub use flex::Flex;
|
||||
|
||||
@@ -1,38 +1,15 @@
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
/// A type alias for `HorizontalAlignment`.
|
||||
///
|
||||
/// Prior to Ratatui 0.30.0, [`HorizontalAlignment`] was named `Alignment`. This alias is provided
|
||||
/// for backwards compatibility. Because this type is used almost everywhere in Ratatui related apps
|
||||
/// and libraries, it's unlikely that this alias will be removed in the future.
|
||||
pub type Alignment = HorizontalAlignment;
|
||||
|
||||
/// A type representing horizontal alignment.
|
||||
///
|
||||
/// Prior to Ratatui 0.30.0, this type was named `Alignment`. In Ratatui 0.30.0, the name was
|
||||
/// changed to `HorizontalAlignment` to make it more descriptive. The old name is still available as
|
||||
/// an alias for backwards compatibility.
|
||||
#[derive(Debug, Default, Display, EnumString, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub enum HorizontalAlignment {
|
||||
pub enum Alignment {
|
||||
#[default]
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
}
|
||||
|
||||
/// A type representing vertical alignment.
|
||||
#[derive(Debug, Default, Display, EnumString, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub enum VerticalAlignment {
|
||||
#[default]
|
||||
Top,
|
||||
Center,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
|
||||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
@@ -51,26 +28,4 @@ mod tests {
|
||||
assert_eq!("Right".parse::<Alignment>(), Ok(Alignment::Right));
|
||||
assert_eq!("".parse::<Alignment>(), Err(ParseError::VariantNotFound));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vertical_alignment_to_string() {
|
||||
assert_eq!(VerticalAlignment::Top.to_string(), "Top");
|
||||
assert_eq!(VerticalAlignment::Center.to_string(), "Center");
|
||||
assert_eq!(VerticalAlignment::Bottom.to_string(), "Bottom");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vertical_alignment_from_str() {
|
||||
let top = "Top".parse::<VerticalAlignment>();
|
||||
assert_eq!(top, Ok(VerticalAlignment::Top));
|
||||
|
||||
let center = "Center".parse::<VerticalAlignment>();
|
||||
assert_eq!(center, Ok(VerticalAlignment::Center));
|
||||
|
||||
let bottom = "Bottom".parse::<VerticalAlignment>();
|
||||
assert_eq!(bottom, Ok(VerticalAlignment::Bottom));
|
||||
|
||||
let invalid = "".parse::<VerticalAlignment>();
|
||||
assert_eq!(invalid, Err(ParseError::VariantNotFound));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use alloc::vec::Vec;
|
||||
use core::fmt;
|
||||
use std::fmt;
|
||||
|
||||
use strum::EnumIs;
|
||||
|
||||
@@ -383,9 +382,6 @@ impl fmt::Display for Constraint {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
use alloc::vec;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -464,7 +460,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[expect(deprecated)]
|
||||
#[allow(deprecated)]
|
||||
fn apply() {
|
||||
assert_eq!(Constraint::Percentage(0).apply(100), 0);
|
||||
assert_eq!(Constraint::Percentage(50).apply(100), 50);
|
||||
|
||||
@@ -9,8 +9,6 @@ pub enum Direction {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
|
||||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use strum::{Display, EnumIs, EnumString};
|
||||
|
||||
#[expect(unused_imports)]
|
||||
#[allow(unused_imports)]
|
||||
use crate::layout::Constraint;
|
||||
|
||||
/// Defines the options for layout flex justify content in a container.
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
use alloc::rc::Rc;
|
||||
use alloc::vec::Vec;
|
||||
use core::iter;
|
||||
#[cfg(feature = "layout-cache")]
|
||||
use core::num::NonZeroUsize;
|
||||
use std::{cell::RefCell, collections::HashMap, iter, num::NonZeroUsize, rc::Rc};
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use cassowary::{
|
||||
strength::REQUIRED,
|
||||
AddConstraintError, Expression, Solver, Variable,
|
||||
WeightedRelation::{EQ, GE, LE},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use kasuari::WeightedRelation::{EQ, GE, LE};
|
||||
use kasuari::{AddConstraintError, Expression, Solver, Strength, Variable};
|
||||
#[cfg(feature = "layout-cache")]
|
||||
use lru::LruCache;
|
||||
|
||||
use self::strengths::{
|
||||
@@ -31,7 +28,6 @@ type Spacers = Rects;
|
||||
// └ ┘└──────────────────┘└ ┘└──────────────────┘└ ┘└──────────────────┘└ ┘
|
||||
//
|
||||
// Number of spacers will always be one more than number of segments.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
type Cache = LruCache<(Rect, Layout), (Segments, Spacers)>;
|
||||
|
||||
// Multiplier that decides floating point precision when rounding.
|
||||
@@ -39,9 +35,8 @@ type Cache = LruCache<(Rect, Layout), (Segments, Spacers)>;
|
||||
// calculations.
|
||||
const FLOAT_PRECISION_MULTIPLIER: f64 = 100.0;
|
||||
|
||||
#[cfg(feature = "layout-cache")]
|
||||
std::thread_local! {
|
||||
static LAYOUT_CACHE: core::cell::RefCell<Cache> = core::cell::RefCell::new(Cache::new(
|
||||
thread_local! {
|
||||
static LAYOUT_CACHE: RefCell<Cache> = RefCell::new(Cache::new(
|
||||
NonZeroUsize::new(Layout::DEFAULT_CACHE_SIZE).unwrap(),
|
||||
));
|
||||
}
|
||||
@@ -119,7 +114,7 @@ impl From<i16> for Spacing {
|
||||
/// - a flex option
|
||||
/// - a spacing option
|
||||
///
|
||||
/// The algorithm used to compute the layout is based on the [`kasuari`] solver. It is a simple
|
||||
/// The algorithm used to compute the layout is based on the [`cassowary`] solver. It is a simple
|
||||
/// linear solver that can be used to solve linear equations and inequalities. In our case, we
|
||||
/// define a set of constraints that are applied to split the provided area into Rects aligned in a
|
||||
/// single direction, and the solver computes the values of the position and sizes that satisfy as
|
||||
@@ -153,12 +148,14 @@ impl From<i16> for Spacing {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::buffer::Buffer;
|
||||
/// use ratatui_core::layout::{Constraint, Direction, Layout, Rect};
|
||||
/// use ratatui_core::text::Text;
|
||||
/// use ratatui_core::widgets::Widget;
|
||||
/// use ratatui_core::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::{Constraint, Direction, Layout, Rect},
|
||||
/// text::Text,
|
||||
/// widgets::Widget,
|
||||
/// };
|
||||
///
|
||||
/// fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// fn render(area: Rect, buf: &mut ratatui_core::buffer::Buffer) {
|
||||
/// let layout = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
|
||||
/// let [left, right] = layout.areas(area);
|
||||
/// Text::from("foo").render(left, buf);
|
||||
@@ -172,7 +169,7 @@ impl From<i16> for Spacing {
|
||||
/// 
|
||||
///
|
||||
/// [`kasuari`]: https://crates.io/crates/kasuari
|
||||
/// [`cassowary`]: https://crates.io/crates/cassowary
|
||||
/// [Examples]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Layout {
|
||||
@@ -189,8 +186,6 @@ impl Layout {
|
||||
/// bit more to make it a round number. This gives enough entries to store a layout for every
|
||||
/// row and every column, twice over, which should be enough for most apps. For those that need
|
||||
/// more, the cache size can be set with [`Layout::init_cache()`].
|
||||
/// This const is unused if layout cache is disabled.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
pub const DEFAULT_CACHE_SIZE: usize = 500;
|
||||
|
||||
/// Creates a new layout with default values.
|
||||
@@ -283,9 +278,8 @@ impl Layout {
|
||||
/// grows until `cache_size` is reached.
|
||||
///
|
||||
/// By default, the cache size is [`Self::DEFAULT_CACHE_SIZE`].
|
||||
#[cfg(feature = "layout-cache")]
|
||||
pub fn init_cache(cache_size: NonZeroUsize) {
|
||||
LAYOUT_CACHE.with_borrow_mut(|cache| cache.resize(cache_size));
|
||||
LAYOUT_CACHE.with_borrow_mut(|c| c.resize(cache_size));
|
||||
}
|
||||
|
||||
/// Set the direction of the layout.
|
||||
@@ -446,8 +440,7 @@ impl Layout {
|
||||
/// In this example, the items in the layout will be aligned to the start.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::layout::Constraint::*;
|
||||
/// use ratatui_core::layout::{Flex, Layout};
|
||||
/// use ratatui_core::layout::{Constraint::*, Flex, Layout};
|
||||
///
|
||||
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).flex(Flex::Start);
|
||||
/// ```
|
||||
@@ -456,8 +449,7 @@ impl Layout {
|
||||
/// space.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::layout::Constraint::*;
|
||||
/// use ratatui_core::layout::{Flex, Layout};
|
||||
/// use ratatui_core::layout::{Constraint::*, Flex, Layout};
|
||||
///
|
||||
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).flex(Flex::Legacy);
|
||||
/// ```
|
||||
@@ -485,8 +477,7 @@ impl Layout {
|
||||
/// In this example, the spacing between each item in the layout is set to 2 cells.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::layout::Constraint::*;
|
||||
/// use ratatui_core::layout::Layout;
|
||||
/// use ratatui_core::layout::{Constraint::*, Layout};
|
||||
///
|
||||
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).spacing(2);
|
||||
/// ```
|
||||
@@ -495,8 +486,7 @@ impl Layout {
|
||||
/// three segments will have an overlapping border.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::layout::Constraint::*;
|
||||
/// use ratatui_core::layout::Layout;
|
||||
/// use ratatui_core::layout::{Constraint::*, Layout};
|
||||
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).spacing(-1);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
@@ -657,18 +647,11 @@ impl Layout {
|
||||
/// );
|
||||
/// ```
|
||||
pub fn split_with_spacers(&self, area: Rect) -> (Segments, Spacers) {
|
||||
let split = || self.try_split(area).expect("failed to split");
|
||||
|
||||
#[cfg(feature = "layout-cache")]
|
||||
{
|
||||
LAYOUT_CACHE.with_borrow_mut(|cache| {
|
||||
let key = (area, self.clone());
|
||||
cache.get_or_insert(key, split).clone()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "layout-cache"))]
|
||||
split()
|
||||
LAYOUT_CACHE.with_borrow_mut(|c| {
|
||||
let key = (area, self.clone());
|
||||
c.get_or_insert(key, || self.try_split(area).expect("failed to split"))
|
||||
.clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn try_split(&self, area: Rect) -> Result<(Segments, Spacers), AddConstraintError> {
|
||||
@@ -782,8 +765,8 @@ fn configure_area(
|
||||
area_start: f64,
|
||||
area_end: f64,
|
||||
) -> Result<(), AddConstraintError> {
|
||||
solver.add_constraint(area.start | EQ(Strength::REQUIRED) | area_start)?;
|
||||
solver.add_constraint(area.end | EQ(Strength::REQUIRED) | area_end)?;
|
||||
solver.add_constraint(area.start | EQ(REQUIRED) | area_start)?;
|
||||
solver.add_constraint(area.end | EQ(REQUIRED) | area_end)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -794,8 +777,8 @@ fn configure_variable_in_area_constraints(
|
||||
) -> Result<(), AddConstraintError> {
|
||||
// all variables are in the range [area.start, area.end]
|
||||
for &variable in variables {
|
||||
solver.add_constraint(variable | GE(Strength::REQUIRED) | area.start)?;
|
||||
solver.add_constraint(variable | LE(Strength::REQUIRED) | area.end)?;
|
||||
solver.add_constraint(variable | GE(REQUIRED) | area.start)?;
|
||||
solver.add_constraint(variable | LE(REQUIRED) | area.end)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -815,7 +798,7 @@ fn configure_variable_constraints(
|
||||
// └v0 └v1 └v2 └v3 └v4 └v5 └v6 └v7
|
||||
|
||||
for (&left, &right) in variables.iter().skip(1).tuples() {
|
||||
solver.add_constraint(left | LE(Strength::REQUIRED) | right)?;
|
||||
solver.add_constraint(left | LE(REQUIRED) | right)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -985,20 +968,6 @@ fn configure_fill_constraints(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Used instead of `f64::round` directly, to provide fallback for `no_std`.
|
||||
#[cfg(feature = "std")]
|
||||
#[inline]
|
||||
fn round(value: f64) -> f64 {
|
||||
value.round()
|
||||
}
|
||||
|
||||
// A rounding fallback for `no_std` in pure rust.
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[inline]
|
||||
fn round(value: f64) -> f64 {
|
||||
(value + 0.5f64.copysign(value)) as i64 as f64
|
||||
}
|
||||
|
||||
fn changes_to_rects(
|
||||
changes: &HashMap<Variable, f64>,
|
||||
elements: &[Element],
|
||||
@@ -1011,8 +980,8 @@ fn changes_to_rects(
|
||||
.map(|element| {
|
||||
let start = changes.get(&element.start).unwrap_or(&0.0);
|
||||
let end = changes.get(&element.end).unwrap_or(&0.0);
|
||||
let start = round(round(*start) / FLOAT_PRECISION_MULTIPLIER) as u16;
|
||||
let end = round(round(*end) / FLOAT_PRECISION_MULTIPLIER) as u16;
|
||||
let start = (start.round() / FLOAT_PRECISION_MULTIPLIER).round() as u16;
|
||||
let end = (end.round() / FLOAT_PRECISION_MULTIPLIER).round() as u16;
|
||||
let size = end.saturating_sub(start);
|
||||
match direction {
|
||||
Direction::Horizontal => Rect {
|
||||
@@ -1034,10 +1003,9 @@ fn changes_to_rects(
|
||||
|
||||
/// please leave this here as it's useful for debugging unit tests when we make any changes to
|
||||
/// layout code - we should replace this with tracing in the future.
|
||||
#[expect(dead_code)]
|
||||
#[cfg(feature = "std")]
|
||||
#[allow(dead_code)]
|
||||
fn debug_elements(elements: &[Element], changes: &HashMap<Variable, f64>) {
|
||||
let variables = alloc::format!(
|
||||
let variables = format!(
|
||||
"{:?}",
|
||||
elements
|
||||
.iter()
|
||||
@@ -1047,7 +1015,7 @@ fn debug_elements(elements: &[Element], changes: &HashMap<Variable, f64>) {
|
||||
))
|
||||
.collect::<Vec<(f64, f64)>>()
|
||||
);
|
||||
std::dbg!(variables);
|
||||
dbg!(variables);
|
||||
}
|
||||
|
||||
/// A container used by the solver inside split
|
||||
@@ -1064,7 +1032,7 @@ impl From<(Variable, Variable)> for Element {
|
||||
}
|
||||
|
||||
impl Element {
|
||||
#[expect(dead_code)]
|
||||
#[allow(dead_code)]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
start: Variable::new(),
|
||||
@@ -1076,24 +1044,24 @@ impl Element {
|
||||
self.end - self.start
|
||||
}
|
||||
|
||||
fn has_max_size(&self, size: u16, strength: Strength) -> kasuari::Constraint {
|
||||
fn has_max_size(&self, size: u16, strength: f64) -> cassowary::Constraint {
|
||||
self.size() | LE(strength) | (f64::from(size) * FLOAT_PRECISION_MULTIPLIER)
|
||||
}
|
||||
|
||||
fn has_min_size(&self, size: i16, strength: Strength) -> kasuari::Constraint {
|
||||
fn has_min_size(&self, size: i16, strength: f64) -> cassowary::Constraint {
|
||||
self.size() | GE(strength) | (f64::from(size) * FLOAT_PRECISION_MULTIPLIER)
|
||||
}
|
||||
|
||||
fn has_int_size(&self, size: u16, strength: Strength) -> kasuari::Constraint {
|
||||
fn has_int_size(&self, size: u16, strength: f64) -> cassowary::Constraint {
|
||||
self.size() | EQ(strength) | (f64::from(size) * FLOAT_PRECISION_MULTIPLIER)
|
||||
}
|
||||
|
||||
fn has_size<E: Into<Expression>>(&self, size: E, strength: Strength) -> kasuari::Constraint {
|
||||
fn has_size<E: Into<Expression>>(&self, size: E, strength: f64) -> cassowary::Constraint {
|
||||
self.size() | EQ(strength) | size.into()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> kasuari::Constraint {
|
||||
self.size() | EQ(Strength::REQUIRED - Strength::WEAK) | 0.0
|
||||
fn is_empty(&self) -> cassowary::Constraint {
|
||||
self.size() | EQ(REQUIRED - 1.0) | 0.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1112,104 +1080,101 @@ impl From<&Element> for Expression {
|
||||
}
|
||||
|
||||
mod strengths {
|
||||
use kasuari::Strength;
|
||||
use cassowary::strength::{MEDIUM, REQUIRED, STRONG, WEAK};
|
||||
|
||||
/// The strength to apply to Spacers to ensure that their sizes are equal.
|
||||
///
|
||||
/// ┌ ┐┌───┐┌ ┐┌───┐┌ ┐
|
||||
/// ==x │ │ ==x │ │ ==x
|
||||
/// └ ┘└───┘└ ┘└───┘└ ┘
|
||||
pub const SPACER_SIZE_EQ: Strength = Strength::REQUIRED.div_f64(10.0);
|
||||
pub const SPACER_SIZE_EQ: f64 = REQUIRED / 10.0;
|
||||
|
||||
/// The strength to apply to Min inequality constraints.
|
||||
///
|
||||
/// ┌────────┐
|
||||
/// │Min(>=x)│
|
||||
/// └────────┘
|
||||
pub const MIN_SIZE_GE: Strength = Strength::STRONG.mul_f64(100.0);
|
||||
pub const MIN_SIZE_GE: f64 = STRONG * 100.0;
|
||||
|
||||
/// The strength to apply to Max inequality constraints.
|
||||
///
|
||||
/// ┌────────┐
|
||||
/// │Max(<=x)│
|
||||
/// └────────┘
|
||||
pub const MAX_SIZE_LE: Strength = Strength::STRONG.mul_f64(100.0);
|
||||
pub const MAX_SIZE_LE: f64 = STRONG * 100.0;
|
||||
|
||||
/// The strength to apply to Length constraints.
|
||||
///
|
||||
/// ┌───────────┐
|
||||
/// │Length(==x)│
|
||||
/// └───────────┘
|
||||
pub const LENGTH_SIZE_EQ: Strength = Strength::STRONG.mul_f64(10.0);
|
||||
pub const LENGTH_SIZE_EQ: f64 = STRONG * 10.0;
|
||||
|
||||
/// The strength to apply to Percentage constraints.
|
||||
///
|
||||
/// ┌───────────────┐
|
||||
/// │Percentage(==x)│
|
||||
/// └───────────────┘
|
||||
pub const PERCENTAGE_SIZE_EQ: Strength = Strength::STRONG;
|
||||
pub const PERCENTAGE_SIZE_EQ: f64 = STRONG;
|
||||
|
||||
/// The strength to apply to Ratio constraints.
|
||||
///
|
||||
/// ┌────────────┐
|
||||
/// │Ratio(==x,y)│
|
||||
/// └────────────┘
|
||||
pub const RATIO_SIZE_EQ: Strength = Strength::STRONG.div_f64(10.0);
|
||||
pub const RATIO_SIZE_EQ: f64 = STRONG / 10.0;
|
||||
|
||||
/// The strength to apply to Min equality constraints.
|
||||
///
|
||||
/// ┌────────┐
|
||||
/// │Min(==x)│
|
||||
/// └────────┘
|
||||
pub const MIN_SIZE_EQ: Strength = Strength::MEDIUM.mul_f64(10.0);
|
||||
pub const MIN_SIZE_EQ: f64 = MEDIUM * 10.0;
|
||||
|
||||
/// The strength to apply to Max equality constraints.
|
||||
///
|
||||
/// ┌────────┐
|
||||
/// │Max(==x)│
|
||||
/// └────────┘
|
||||
pub const MAX_SIZE_EQ: Strength = Strength::MEDIUM.mul_f64(10.0);
|
||||
pub const MAX_SIZE_EQ: f64 = MEDIUM * 10.0;
|
||||
|
||||
/// The strength to apply to Fill growing constraints.
|
||||
///
|
||||
/// ┌─────────────────────┐
|
||||
/// │<= Fill(x) =>│
|
||||
/// └─────────────────────┘
|
||||
pub const FILL_GROW: Strength = Strength::MEDIUM;
|
||||
pub const FILL_GROW: f64 = MEDIUM;
|
||||
|
||||
/// The strength to apply to growing constraints.
|
||||
///
|
||||
/// ┌────────────┐
|
||||
/// │<= Min(x) =>│
|
||||
/// └────────────┘
|
||||
pub const GROW: Strength = Strength::MEDIUM.div_f64(10.0);
|
||||
pub const GROW: f64 = MEDIUM / 10.0;
|
||||
|
||||
/// The strength to apply to Spacer growing constraints.
|
||||
///
|
||||
/// ┌ ┐
|
||||
/// <= x =>
|
||||
/// └ ┘
|
||||
pub const SPACE_GROW: Strength = Strength::WEAK.mul_f64(10.0);
|
||||
pub const SPACE_GROW: f64 = WEAK * 10.0;
|
||||
|
||||
/// The strength to apply to growing the size of all segments equally.
|
||||
///
|
||||
/// ┌───────┐
|
||||
/// │<= x =>│
|
||||
/// └───────┘
|
||||
pub const ALL_SEGMENT_GROW: Strength = Strength::WEAK;
|
||||
pub const ALL_SEGMENT_GROW: f64 = WEAK;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
// The compiler will optimize out the comparisons, but this ensures that the constants are
|
||||
// defined in the correct order of priority.
|
||||
#[allow(clippy::assertions_on_constants)]
|
||||
pub fn strength_is_valid() {
|
||||
use strengths::*;
|
||||
assert!(SPACER_SIZE_EQ > MAX_SIZE_LE);
|
||||
@@ -1226,15 +1191,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "layout-cache")]
|
||||
fn cache_size() {
|
||||
LAYOUT_CACHE.with_borrow(|cache| {
|
||||
assert_eq!(cache.cap().get(), Layout::DEFAULT_CACHE_SIZE);
|
||||
LAYOUT_CACHE.with_borrow(|c| {
|
||||
assert_eq!(c.cap().get(), Layout::DEFAULT_CACHE_SIZE);
|
||||
});
|
||||
|
||||
Layout::init_cache(NonZeroUsize::new(10).unwrap());
|
||||
LAYOUT_CACHE.with_borrow(|cache| {
|
||||
assert_eq!(cache.cap().get(), 10);
|
||||
LAYOUT_CACHE.with_borrow(|c| {
|
||||
assert_eq!(c.cap().get(), 10);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1261,7 +1225,7 @@ mod tests {
|
||||
assert_eq!(layout.constraints, [Constraint::Min(0)]);
|
||||
|
||||
// array_ref
|
||||
#[expect(clippy::needless_borrows_for_generic_args)] // backwards compatibility test
|
||||
#[allow(clippy::needless_borrows_for_generic_args)] // backwards compatibility test
|
||||
let layout = Layout::new(Direction::Horizontal, &[Constraint::Min(0)]);
|
||||
assert_eq!(layout.direction, Direction::Horizontal);
|
||||
assert_eq!(layout.constraints, [Constraint::Min(0)]);
|
||||
@@ -1272,7 +1236,7 @@ mod tests {
|
||||
assert_eq!(layout.constraints, [Constraint::Min(0)]);
|
||||
|
||||
// vec_ref
|
||||
#[expect(clippy::needless_borrows_for_generic_args)] // backwards compatibility test
|
||||
#[allow(clippy::needless_borrows_for_generic_args)] // backwards compatibility test
|
||||
let layout = Layout::new(Direction::Horizontal, &(vec![Constraint::Min(0)]));
|
||||
assert_eq!(layout.direction, Direction::Horizontal);
|
||||
assert_eq!(layout.constraints, [Constraint::Min(0)]);
|
||||
@@ -1314,6 +1278,11 @@ mod tests {
|
||||
/// The purpose of this test is to ensure that layout can be constructed with any type that
|
||||
/// implements `IntoIterator<Item = AsRef<Constraint>>`.
|
||||
#[test]
|
||||
#[allow(
|
||||
clippy::needless_borrow,
|
||||
clippy::unnecessary_to_owned,
|
||||
clippy::useless_asref
|
||||
)]
|
||||
fn constraints() {
|
||||
const CONSTRAINTS: [Constraint; 2] = [Constraint::Min(0), Constraint::Max(10)];
|
||||
let fixed_size_array = CONSTRAINTS;
|
||||
@@ -1431,19 +1400,21 @@ mod tests {
|
||||
/// - underflow: constraint is for less than the full space
|
||||
/// - overflow: constraint is for more than the full space
|
||||
mod split {
|
||||
use alloc::string::ToString;
|
||||
use core::ops::Range;
|
||||
use std::ops::Range;
|
||||
|
||||
use itertools::Itertools;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
use crate::buffer::Buffer;
|
||||
use crate::layout::Constraint::{self, *};
|
||||
use crate::layout::{Direction, Flex, Layout, Rect};
|
||||
use crate::text::Text;
|
||||
use crate::widgets::Widget;
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::{
|
||||
Constraint::{self, *},
|
||||
Direction, Flex, Layout, Rect,
|
||||
},
|
||||
text::Text,
|
||||
widgets::Widget,
|
||||
};
|
||||
|
||||
/// Test that the given constraints applied to the given area result in the expected layout.
|
||||
/// Each chunk is filled with a letter repeated as many times as the width of the chunk. The
|
||||
@@ -2704,4 +2675,41 @@ mod tests {
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_solver() {
|
||||
use super::*;
|
||||
|
||||
let mut solver = Solver::new();
|
||||
let x = Variable::new();
|
||||
let y = Variable::new();
|
||||
|
||||
solver.add_constraint((x + y) | EQ(4.0) | 5.0).unwrap();
|
||||
solver.add_constraint(x | EQ(1.0) | 2.0).unwrap();
|
||||
for _ in 0..5 {
|
||||
solver.add_constraint(y | EQ(1.0) | 2.0).unwrap();
|
||||
}
|
||||
|
||||
let changes: HashMap<Variable, f64> = solver.fetch_changes().iter().copied().collect();
|
||||
let x = changes.get(&x).unwrap_or(&0.0).round() as u16;
|
||||
let y = changes.get(&y).unwrap_or(&0.0).round() as u16;
|
||||
assert_eq!(x, 3);
|
||||
assert_eq!(y, 2);
|
||||
|
||||
let mut solver = Solver::new();
|
||||
let x = Variable::new();
|
||||
let y = Variable::new();
|
||||
|
||||
solver.add_constraint((x + y) | EQ(4.0) | 5.0).unwrap();
|
||||
solver.add_constraint(y | EQ(1.0) | 2.0).unwrap();
|
||||
for _ in 0..5 {
|
||||
solver.add_constraint(x | EQ(1.0) | 2.0).unwrap();
|
||||
}
|
||||
|
||||
let changes: HashMap<Variable, f64> = solver.fetch_changes().iter().copied().collect();
|
||||
let x = changes.get(&x).unwrap_or(&0.0).round() as u16;
|
||||
let y = changes.get(&y).unwrap_or(&0.0).round() as u16;
|
||||
assert_eq!(x, 2);
|
||||
assert_eq!(y, 3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use core::fmt;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
@@ -24,8 +24,6 @@ impl fmt::Display for Margin {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#![warn(missing_docs)]
|
||||
use core::fmt;
|
||||
use std::fmt;
|
||||
|
||||
use crate::layout::Rect;
|
||||
|
||||
@@ -75,8 +75,6 @@ impl fmt::Display for Position {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#![warn(missing_docs)]
|
||||
use core::cmp::{max, min};
|
||||
use core::fmt;
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
fmt,
|
||||
};
|
||||
|
||||
use crate::layout::{Margin, Position, Size};
|
||||
|
||||
mod iter;
|
||||
pub use iter::*;
|
||||
|
||||
use super::{Constraint, Flex, Layout};
|
||||
|
||||
/// A Rectangular area.
|
||||
///
|
||||
/// A simple rectangle used in the computation of the layout and to give widgets a hint about the
|
||||
@@ -273,10 +273,7 @@ impl Rect {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui_core::buffer::Buffer;
|
||||
/// use ratatui_core::layout::Rect;
|
||||
/// use ratatui_core::text::Line;
|
||||
/// use ratatui_core::widgets::Widget;
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
|
||||
///
|
||||
/// fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// for row in area.rows() {
|
||||
@@ -293,10 +290,7 @@ impl Rect {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui_core::buffer::Buffer;
|
||||
/// use ratatui_core::layout::Rect;
|
||||
/// use ratatui_core::text::Text;
|
||||
/// use ratatui_core::widgets::Widget;
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect, text::Text, widgets::Widget};
|
||||
///
|
||||
/// fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// for (i, column) in area.columns().enumerate() {
|
||||
@@ -315,8 +309,7 @@ impl Rect {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui_core::buffer::Buffer;
|
||||
/// use ratatui_core::layout::Rect;
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect};
|
||||
///
|
||||
/// fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// for position in area.positions() {
|
||||
@@ -353,70 +346,6 @@ impl Rect {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new Rect, centered horizontally based on the provided constraint.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui_core::layout::Constraint;
|
||||
/// use ratatui_core::terminal::Frame;
|
||||
///
|
||||
/// fn render(frame: &mut Frame) {
|
||||
/// let area = frame.area().centered_horizontally(Constraint::Ratio(1, 2));
|
||||
/// }
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn centered_horizontally(self, constraint: Constraint) -> Self {
|
||||
let [area] = Layout::horizontal([constraint])
|
||||
.flex(Flex::Center)
|
||||
.areas(self);
|
||||
area
|
||||
}
|
||||
|
||||
/// Returns a new Rect, centered vertically based on the provided constraint.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui_core::layout::Constraint;
|
||||
/// use ratatui_core::terminal::Frame;
|
||||
///
|
||||
/// fn render(frame: &mut Frame) {
|
||||
/// let area = frame.area().centered_vertically(Constraint::Ratio(1, 2));
|
||||
/// }
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn centered_vertically(self, constraint: Constraint) -> Self {
|
||||
let [area] = Layout::vertical([constraint])
|
||||
.flex(Flex::Center)
|
||||
.areas(self);
|
||||
area
|
||||
}
|
||||
|
||||
/// Returns a new Rect, centered horizontally and vertically based on the provided constraints.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui_core::layout::Constraint;
|
||||
/// use ratatui_core::terminal::Frame;
|
||||
///
|
||||
/// fn render(frame: &mut Frame) {
|
||||
/// let area = frame
|
||||
/// .area()
|
||||
/// .centered(Constraint::Ratio(1, 2), Constraint::Ratio(1, 3));
|
||||
/// }
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn centered(
|
||||
self,
|
||||
horizontal_constraint: Constraint,
|
||||
vertical_constraint: Constraint,
|
||||
) -> Self {
|
||||
self.centered_horizontally(horizontal_constraint)
|
||||
.centered_vertically(vertical_constraint)
|
||||
}
|
||||
|
||||
/// indents the x value of the `Rect` by a given `offset`
|
||||
///
|
||||
/// This is pub(crate) for now as we need to stabilize the naming / design of this API.
|
||||
@@ -443,11 +372,6 @@ impl From<(Position, Size)> for Rect {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
@@ -729,31 +653,4 @@ mod tests {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn centered_horizontally() {
|
||||
let rect = Rect::new(0, 0, 5, 5);
|
||||
assert_eq!(
|
||||
rect.centered_horizontally(Constraint::Length(3)),
|
||||
Rect::new(1, 0, 3, 5)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn centered_vertically() {
|
||||
let rect = Rect::new(0, 0, 5, 5);
|
||||
assert_eq!(
|
||||
rect.centered_vertically(Constraint::Length(1)),
|
||||
Rect::new(0, 2, 5, 1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn centered() {
|
||||
let rect = Rect::new(0, 0, 5, 5);
|
||||
assert_eq!(
|
||||
rect.centered(Constraint::Length(3), Constraint::Length(1)),
|
||||
Rect::new(1, 2, 3, 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ impl Iterator for Positions {
|
||||
///
|
||||
/// Returns `None` when there are no more positions to iterate through.
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if !self.rect.contains(self.current_position) {
|
||||
if self.current_position.y >= self.rect.bottom() {
|
||||
return None;
|
||||
}
|
||||
let position = self.current_position;
|
||||
@@ -326,31 +326,4 @@ mod tests {
|
||||
assert_eq!(positions.next(), None);
|
||||
assert_eq!(positions.size_hint(), (0, Some(0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn positions_zero_width() {
|
||||
let rect = Rect::new(0, 0, 0, 1);
|
||||
let mut positions = Positions::new(rect);
|
||||
assert_eq!(positions.size_hint(), (0, Some(0)));
|
||||
assert_eq!(positions.next(), None);
|
||||
assert_eq!(positions.size_hint(), (0, Some(0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn positions_zero_height() {
|
||||
let rect = Rect::new(0, 0, 1, 0);
|
||||
let mut positions = Positions::new(rect);
|
||||
assert_eq!(positions.size_hint(), (0, Some(0)));
|
||||
assert_eq!(positions.next(), None);
|
||||
assert_eq!(positions.size_hint(), (0, Some(0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn positions_zero_by_zero() {
|
||||
let rect = Rect::new(0, 0, 0, 0);
|
||||
let mut positions = Positions::new(rect);
|
||||
assert_eq!(positions.size_hint(), (0, Some(0)));
|
||||
assert_eq!(positions.next(), None);
|
||||
assert_eq!(positions.size_hint(), (0, Some(0)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#![warn(missing_docs)]
|
||||
use core::fmt;
|
||||
use std::fmt;
|
||||
|
||||
use crate::layout::Rect;
|
||||
|
||||
@@ -46,8 +46,6 @@ impl fmt::Display for Size {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![no_std]
|
||||
// show the feature flags in the generated documentation
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
@@ -39,14 +38,6 @@
|
||||
//!
|
||||
//! This project is licensed under the MIT License. See the [LICENSE](../LICENSE) file for details.
|
||||
|
||||
#![warn(clippy::std_instead_of_core)]
|
||||
#![warn(clippy::std_instead_of_alloc)]
|
||||
#![warn(clippy::alloc_instead_of_core)]
|
||||
|
||||
extern crate alloc;
|
||||
#[cfg(feature = "std")]
|
||||
extern crate std;
|
||||
|
||||
pub mod backend;
|
||||
pub mod buffer;
|
||||
pub mod layout;
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```
|
||||
//! use ratatui_core::style::{Color, Modifier, Style};
|
||||
//! use ratatui_core::text::Span;
|
||||
//! use ratatui_core::{
|
||||
//! style::{Color, Modifier, Style},
|
||||
//! text::Span,
|
||||
//! };
|
||||
//!
|
||||
//! let heading_style = Style::new()
|
||||
//! .fg(Color::Black)
|
||||
@@ -41,8 +43,10 @@
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```
|
||||
//! use ratatui_core::style::{Color, Modifier, Style, Stylize};
|
||||
//! use ratatui_core::text::{Span, Text};
|
||||
//! use ratatui_core::{
|
||||
//! style::{Color, Modifier, Style, Stylize},
|
||||
//! text::{Span, Text},
|
||||
//! };
|
||||
//!
|
||||
//! assert_eq!(
|
||||
//! "hello".red().on_blue().bold(),
|
||||
@@ -68,7 +72,7 @@
|
||||
//!
|
||||
//! [`Span`]: crate::text::Span
|
||||
|
||||
use core::fmt;
|
||||
use std::fmt;
|
||||
|
||||
use bitflags::bitflags;
|
||||
pub use color::{Color, ParseColorError};
|
||||
@@ -81,7 +85,6 @@ mod color;
|
||||
pub mod palette;
|
||||
#[cfg(feature = "palette")]
|
||||
mod palette_conversion;
|
||||
#[macro_use]
|
||||
mod stylize;
|
||||
|
||||
bitflags! {
|
||||
@@ -153,8 +156,10 @@ impl fmt::Debug for Modifier {
|
||||
/// anywhere that accepts `Into<Style>`.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::{Color, Modifier, Style};
|
||||
/// use ratatui_core::text::Line;
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier, Style},
|
||||
/// text::Line,
|
||||
/// };
|
||||
///
|
||||
/// Line::styled("hello", Style::new().fg(Color::Red));
|
||||
/// // simplifies to
|
||||
@@ -170,9 +175,11 @@ impl fmt::Debug for Modifier {
|
||||
/// just S3.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::buffer::Buffer;
|
||||
/// use ratatui_core::layout::Rect;
|
||||
/// use ratatui_core::style::{Color, Modifier, Style};
|
||||
/// use ratatui_core::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// style::{Color, Modifier, Style},
|
||||
/// };
|
||||
///
|
||||
/// let styles = [
|
||||
/// Style::default()
|
||||
@@ -208,9 +215,11 @@ impl fmt::Debug for Modifier {
|
||||
/// reset all properties until that point use [`Style::reset`].
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui_core::buffer::Buffer;
|
||||
/// use ratatui_core::layout::Rect;
|
||||
/// use ratatui_core::style::{Color, Modifier, Style};
|
||||
/// use ratatui_core::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// style::{Color, Modifier, Style},
|
||||
/// };
|
||||
///
|
||||
/// let styles = [
|
||||
/// Style::default()
|
||||
@@ -260,6 +269,18 @@ impl fmt::Debug for Style {
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for Style {
|
||||
type Item = Self;
|
||||
|
||||
fn style(&self) -> Style {
|
||||
*self
|
||||
}
|
||||
|
||||
fn set_style<S: Into<Self>>(self, style: S) -> Self::Item {
|
||||
self.patch(style)
|
||||
}
|
||||
}
|
||||
|
||||
impl Style {
|
||||
/// Returns a `Style` with default properties.
|
||||
pub const fn new() -> Self {
|
||||
@@ -482,33 +503,6 @@ impl Style {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
color!(pub const Color::Black, black(), on_black() -> Self);
|
||||
color!(pub const Color::Red, red(), on_red() -> Self);
|
||||
color!(pub const Color::Green, green(), on_green() -> Self);
|
||||
color!(pub const Color::Yellow, yellow(), on_yellow() -> Self);
|
||||
color!(pub const Color::Blue, blue(), on_blue() -> Self);
|
||||
color!(pub const Color::Magenta, magenta(), on_magenta() -> Self);
|
||||
color!(pub const Color::Cyan, cyan(), on_cyan() -> Self);
|
||||
color!(pub const Color::Gray, gray(), on_gray() -> Self);
|
||||
color!(pub const Color::DarkGray, dark_gray(), on_dark_gray() -> Self);
|
||||
color!(pub const Color::LightRed, light_red(), on_light_red() -> Self);
|
||||
color!(pub const Color::LightGreen, light_green(), on_light_green() -> Self);
|
||||
color!(pub const Color::LightYellow, light_yellow(), on_light_yellow() -> Self);
|
||||
color!(pub const Color::LightBlue, light_blue(), on_light_blue() -> Self);
|
||||
color!(pub const Color::LightMagenta, light_magenta(), on_light_magenta() -> Self);
|
||||
color!(pub const Color::LightCyan, light_cyan(), on_light_cyan() -> Self);
|
||||
color!(pub const Color::White, white(), on_white() -> Self);
|
||||
|
||||
modifier!(pub const Modifier::BOLD, bold(), not_bold() -> Self);
|
||||
modifier!(pub const Modifier::DIM, dim(), not_dim() -> Self);
|
||||
modifier!(pub const Modifier::ITALIC, italic(), not_italic() -> Self);
|
||||
modifier!(pub const Modifier::UNDERLINED, underlined(), not_underlined() -> Self);
|
||||
modifier!(pub const Modifier::SLOW_BLINK, slow_blink(), not_slow_blink() -> Self);
|
||||
modifier!(pub const Modifier::RAPID_BLINK, rapid_blink(), not_rapid_blink() -> Self);
|
||||
modifier!(pub const Modifier::REVERSED, reversed(), not_reversed() -> Self);
|
||||
modifier!(pub const Modifier::HIDDEN, hidden(), not_hidden() -> Self);
|
||||
modifier!(pub const Modifier::CROSSED_OUT, crossed_out(), not_crossed_out() -> Self);
|
||||
}
|
||||
|
||||
impl From<Color> for Style {
|
||||
@@ -648,15 +642,12 @@ impl From<(Color, Color, Modifier, Modifier)> for Style {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[rstest]
|
||||
#[case(Style::new(), "Style::new()")]
|
||||
#[case(Style::default(), "Style::new()")]
|
||||
#[case(Style::new().red(), "Style::new().red()")]
|
||||
#[case(Style::new().on_blue(), "Style::new().on_blue()")]
|
||||
#[case(Style::new().bold(), "Style::new().bold()")]
|
||||
@@ -698,8 +689,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn combine_individual_modifiers() {
|
||||
use crate::buffer::Buffer;
|
||||
use crate::layout::Rect;
|
||||
use crate::{buffer::Buffer, layout::Rect};
|
||||
|
||||
let mods = [
|
||||
Modifier::BOLD,
|
||||
@@ -750,19 +740,14 @@ mod tests {
|
||||
|
||||
const _RESET: Style = Style::reset();
|
||||
const _RED_FG: Style = Style::new().fg(RED);
|
||||
const _RED_FG_SHORT: Style = Style::new().red();
|
||||
const _BLACK_BG: Style = Style::new().bg(BLACK);
|
||||
const _BLACK_BG_SHORT: Style = Style::new().on_black();
|
||||
const _ADD_BOLD: Style = Style::new().add_modifier(BOLD);
|
||||
const _ADD_BOLD_SHORT: Style = Style::new().bold();
|
||||
const _REMOVE_ITALIC: Style = Style::new().remove_modifier(ITALIC);
|
||||
const _REMOVE_ITALIC_SHORT: Style = Style::new().not_italic();
|
||||
const ALL: Style = Style::new()
|
||||
.fg(RED)
|
||||
.bg(BLACK)
|
||||
.add_modifier(BOLD)
|
||||
.remove_modifier(ITALIC);
|
||||
const ALL_SHORT: Style = Style::new().red().on_black().bold().not_italic();
|
||||
assert_eq!(
|
||||
ALL,
|
||||
Style::new()
|
||||
@@ -771,7 +756,6 @@ mod tests {
|
||||
.add_modifier(Modifier::BOLD)
|
||||
.remove_modifier(Modifier::ITALIC)
|
||||
);
|
||||
assert_eq!(ALL, ALL_SHORT);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -846,6 +830,11 @@ mod tests {
|
||||
assert_eq!(stylized, Style::new().remove_modifier(expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_can_be_stylized() {
|
||||
assert_eq!(Style::new().reset(), Style::reset());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_color() {
|
||||
assert_eq!(Style::from(Color::Red), Style::new().fg(Color::Red));
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#![allow(clippy::unreadable_literal)]
|
||||
|
||||
use core::fmt;
|
||||
use core::str::FromStr;
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
use crate::style::stylize::{ColorDebug, ColorDebugKind};
|
||||
|
||||
@@ -145,8 +144,6 @@ impl serde::Serialize for Color {
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
use alloc::string::ToString;
|
||||
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
@@ -208,9 +205,6 @@ impl<'de> serde::Deserialize<'de> for Color {
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
|
||||
/// Colors are currently serialized with the `Display` implementation, so
|
||||
/// RGB values are serialized via hex, for example "#FFFFFF".
|
||||
///
|
||||
@@ -254,7 +248,7 @@ impl fmt::Display for ParseColorError {
|
||||
}
|
||||
}
|
||||
|
||||
impl core::error::Error for ParseColorError {}
|
||||
impl std::error::Error for ParseColorError {}
|
||||
|
||||
/// Converts a string representation to a `Color` instance.
|
||||
///
|
||||
@@ -480,39 +474,9 @@ impl Color {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 3]> for Color {
|
||||
/// Converts an array of 3 u8 values to a `Color::Rgb` instance.
|
||||
fn from([r, g, b]: [u8; 3]) -> Self {
|
||||
Self::Rgb(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u8, u8, u8)> for Color {
|
||||
/// Converts a tuple of 3 u8 values to a `Color::Rgb` instance.
|
||||
fn from((r, g, b): (u8, u8, u8)) -> Self {
|
||||
Self::Rgb(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 4]> for Color {
|
||||
/// Converts an array of 4 u8 values to a `Color::Rgb` instance (ignoring the alpha value).
|
||||
fn from([r, g, b, _]: [u8; 4]) -> Self {
|
||||
Self::Rgb(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u8, u8, u8, u8)> for Color {
|
||||
/// Converts a tuple of 4 u8 values to a `Color::Rgb` instance (ignoring the alpha value).
|
||||
fn from((r, g, b, _): (u8, u8, u8, u8)) -> Self {
|
||||
Self::Rgb(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::boxed::Box;
|
||||
use alloc::format;
|
||||
use core::error::Error;
|
||||
use std::error::Error;
|
||||
|
||||
#[cfg(feature = "palette")]
|
||||
use palette::{Hsl, Hsluv};
|
||||
@@ -770,19 +734,4 @@ mod tests {
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_array_and_tuple_conversions() {
|
||||
let from_array3 = Color::from([123, 45, 67]);
|
||||
assert_eq!(from_array3, Color::Rgb(123, 45, 67));
|
||||
|
||||
let from_tuple3 = Color::from((89, 76, 54));
|
||||
assert_eq!(from_tuple3, Color::Rgb(89, 76, 54));
|
||||
|
||||
let from_array4 = Color::from([10, 20, 30, 255]);
|
||||
assert_eq!(from_array4, Color::Rgb(10, 20, 30));
|
||||
|
||||
let from_tuple4 = Color::from((200, 150, 100, 0));
|
||||
assert_eq!(from_tuple4, Color::Rgb(200, 150, 100));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,8 +403,10 @@
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use ratatui_core::style::palette::material::{BLUE, RED};
|
||||
//! use ratatui_core::style::Color;
|
||||
//! use ratatui_core::style::{
|
||||
//! palette::material::{BLUE, RED},
|
||||
//! Color,
|
||||
//! };
|
||||
//!
|
||||
//! assert_eq!(RED.c500, Color::Rgb(244, 67, 54));
|
||||
//! assert_eq!(BLUE.c500, Color::Rgb(33, 150, 243));
|
||||
|
||||
@@ -268,8 +268,10 @@
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use ratatui_core::style::palette::tailwind::{BLUE, RED};
|
||||
//! use ratatui_core::style::Color;
|
||||
//! use ratatui_core::style::{
|
||||
//! palette::tailwind::{BLUE, RED},
|
||||
//! Color,
|
||||
//! };
|
||||
//!
|
||||
//! assert_eq!(RED.c500, Color::Rgb(239, 68, 68));
|
||||
//! assert_eq!(BLUE.c500, Color::Rgb(59, 130, 246));
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
//! Conversions from colors in the `palette` crate to [`Color`].
|
||||
|
||||
use ::palette::bool_mask::LazySelect;
|
||||
use ::palette::num::{Arithmetics, MulSub, PartialCmp, Powf, Real};
|
||||
use ::palette::LinSrgb;
|
||||
use palette::stimulus::IntoStimulus;
|
||||
use palette::Srgb;
|
||||
use ::palette::{
|
||||
bool_mask::LazySelect,
|
||||
num::{Arithmetics, MulSub, PartialCmp, Powf, Real},
|
||||
LinSrgb,
|
||||
};
|
||||
use palette::{stimulus::IntoStimulus, Srgb};
|
||||
|
||||
use crate::style::Color;
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::string::{String, ToString};
|
||||
use core::fmt;
|
||||
use std::fmt;
|
||||
|
||||
use crate::style::{Color, Modifier, Style};
|
||||
use crate::text::Span;
|
||||
use paste::paste;
|
||||
|
||||
use crate::{
|
||||
style::{Color, Modifier, Style},
|
||||
text::Span,
|
||||
};
|
||||
|
||||
/// A trait for objects that have a `Style`.
|
||||
///
|
||||
@@ -97,7 +99,7 @@ impl fmt::Debug for ColorDebug {
|
||||
/// the color of the style to the corresponding color.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// color!(Color::Black, black(), on_black() -> T);
|
||||
/// color!(black);
|
||||
///
|
||||
/// // generates
|
||||
///
|
||||
@@ -112,31 +114,19 @@ impl fmt::Debug for ColorDebug {
|
||||
/// }
|
||||
/// ```
|
||||
macro_rules! color {
|
||||
( $variant:expr, $color:ident(), $on_color:ident() -> $ty:ty ) => {
|
||||
#[doc = concat!("Sets the foreground color to [`", stringify!($color), "`](", stringify!($variant), ").")]
|
||||
#[must_use = concat!("`", stringify!($color), "` returns the modified style without modifying the original")]
|
||||
fn $color(self) -> $ty {
|
||||
self.fg($variant)
|
||||
}
|
||||
( $color:ident ) => {
|
||||
paste! {
|
||||
#[doc = "Sets the foreground color to [`" $color "`](Color::" $color:camel ")."]
|
||||
#[must_use = concat!("`", stringify!($color), "` returns the modified style without modifying the original")]
|
||||
fn $color(self) -> T {
|
||||
self.fg(Color::[<$color:camel>])
|
||||
}
|
||||
|
||||
#[doc = concat!("Sets the background color to [`", stringify!($color), "`](", stringify!($variant), ").")]
|
||||
#[must_use = concat!("`", stringify!($on_color), "` returns the modified style without modifying the original")]
|
||||
fn $on_color(self) -> $ty {
|
||||
self.bg($variant)
|
||||
}
|
||||
};
|
||||
|
||||
(pub const $variant:expr, $color:ident(), $on_color:ident() -> $ty:ty ) => {
|
||||
#[doc = concat!("Sets the foreground color to [`", stringify!($color), "`](", stringify!($variant), ").")]
|
||||
#[must_use = concat!("`", stringify!($color), "` returns the modified style without modifying the original")]
|
||||
pub const fn $color(self) -> $ty {
|
||||
self.fg($variant)
|
||||
}
|
||||
|
||||
#[doc = concat!("Sets the background color to [`", stringify!($color), "`](", stringify!($variant), ").")]
|
||||
#[must_use = concat!("`", stringify!($on_color), "` returns the modified style without modifying the original")]
|
||||
pub const fn $on_color(self) -> $ty {
|
||||
self.bg($variant)
|
||||
#[doc = "Sets the background color to [`" $color "`](Color::" $color:camel ")."]
|
||||
#[must_use = concat!("`on_", stringify!($color), "` returns the modified style without modifying the original")]
|
||||
fn [<on_ $color>](self) -> T {
|
||||
self.bg(Color::[<$color:camel>])
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -147,46 +137,36 @@ macro_rules! color {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// modifier!(Modifier::BOLD, bold(), not_bold() -> T);
|
||||
/// modifier!(bold);
|
||||
///
|
||||
/// // generates
|
||||
///
|
||||
/// #[doc = "Adds the [`bold`](Modifier::BOLD) modifier."]
|
||||
/// #[doc = "Adds the [`BOLD`](Modifier::BOLD) modifier."]
|
||||
/// fn bold(self) -> T {
|
||||
/// self.add_modifier(Modifier::BOLD)
|
||||
/// }
|
||||
///
|
||||
/// #[doc = "Removes the [`bold`](Modifier::BOLD) modifier."]
|
||||
/// #[doc = "Removes the [`BOLD`](Modifier::BOLD) modifier."]
|
||||
/// fn not_bold(self) -> T {
|
||||
/// self.remove_modifier(Modifier::BOLD)
|
||||
/// }
|
||||
/// ```
|
||||
macro_rules! modifier {
|
||||
( $variant:expr, $modifier:ident(), $not_modifier:ident() -> $ty:ty ) => {
|
||||
#[doc = concat!("Adds the [`", stringify!($modifier), "`](", stringify!($variant), ") modifier.")]
|
||||
#[must_use = concat!("`", stringify!($modifier), "` returns the modified style without modifying the original")]
|
||||
fn $modifier(self) -> $ty {
|
||||
self.add_modifier($variant)
|
||||
( $modifier:ident ) => {
|
||||
paste! {
|
||||
#[doc = "Adds the [`" $modifier:upper "`](Modifier::" $modifier:upper ") modifier."]
|
||||
#[must_use = concat!("`", stringify!($modifier), "` returns the modified style without modifying the original")]
|
||||
fn [<$modifier>](self) -> T {
|
||||
self.add_modifier(Modifier::[<$modifier:upper>])
|
||||
}
|
||||
}
|
||||
|
||||
#[doc = concat!("Removes the [`", stringify!($modifier), "`](", stringify!($variant), ") modifier.")]
|
||||
#[must_use = concat!("`", stringify!($not_modifier), "` returns the modified style without modifying the original")]
|
||||
fn $not_modifier(self) -> $ty {
|
||||
self.remove_modifier($variant)
|
||||
}
|
||||
};
|
||||
|
||||
(pub const $variant:expr, $modifier:ident(), $not_modifier:ident() -> $ty:ty ) => {
|
||||
#[doc = concat!("Adds the [`", stringify!($modifier), "`](", stringify!($variant), ") modifier.")]
|
||||
#[must_use = concat!("`", stringify!($modifier), "` returns the modified style without modifying the original")]
|
||||
pub const fn $modifier(self) -> $ty {
|
||||
self.add_modifier($variant)
|
||||
}
|
||||
|
||||
#[doc = concat!("Removes the [`", stringify!($modifier), "`](", stringify!($variant), ") modifier.")]
|
||||
#[must_use = concat!("`", stringify!($not_modifier), "` returns the modified style without modifying the original")]
|
||||
pub const fn $not_modifier(self) -> $ty {
|
||||
self.remove_modifier($variant)
|
||||
paste! {
|
||||
#[doc = "Removes the [`" $modifier:upper "`](Modifier::" $modifier:upper ") modifier."]
|
||||
#[must_use = concat!("`not_", stringify!($modifier), "` returns the modified style without modifying the original")]
|
||||
fn [<not_ $modifier>](self) -> T {
|
||||
self.remove_modifier(Modifier::[<$modifier:upper>])
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -242,32 +222,32 @@ pub trait Stylize<'a, T>: Sized {
|
||||
#[must_use = "`remove_modifier` returns the modified style without modifying the original"]
|
||||
fn remove_modifier(self, modifier: Modifier) -> T;
|
||||
|
||||
color!(Color::Black, black(), on_black() -> T);
|
||||
color!(Color::Red, red(), on_red() -> T);
|
||||
color!(Color::Green, green(), on_green() -> T);
|
||||
color!(Color::Yellow, yellow(), on_yellow() -> T);
|
||||
color!(Color::Blue, blue(), on_blue() -> T);
|
||||
color!(Color::Magenta, magenta(), on_magenta() -> T);
|
||||
color!(Color::Cyan, cyan(), on_cyan() -> T);
|
||||
color!(Color::Gray, gray(), on_gray() -> T);
|
||||
color!(Color::DarkGray, dark_gray(), on_dark_gray() -> T);
|
||||
color!(Color::LightRed, light_red(), on_light_red() -> T);
|
||||
color!(Color::LightGreen, light_green(), on_light_green() -> T);
|
||||
color!(Color::LightYellow, light_yellow(), on_light_yellow() -> T);
|
||||
color!(Color::LightBlue, light_blue(), on_light_blue() -> T);
|
||||
color!(Color::LightMagenta, light_magenta(), on_light_magenta() -> T);
|
||||
color!(Color::LightCyan, light_cyan(), on_light_cyan() -> T);
|
||||
color!(Color::White, white(), on_white() -> T);
|
||||
color!(black);
|
||||
color!(red);
|
||||
color!(green);
|
||||
color!(yellow);
|
||||
color!(blue);
|
||||
color!(magenta);
|
||||
color!(cyan);
|
||||
color!(gray);
|
||||
color!(dark_gray);
|
||||
color!(light_red);
|
||||
color!(light_green);
|
||||
color!(light_yellow);
|
||||
color!(light_blue);
|
||||
color!(light_magenta);
|
||||
color!(light_cyan);
|
||||
color!(white);
|
||||
|
||||
modifier!(Modifier::BOLD, bold(), not_bold() -> T);
|
||||
modifier!(Modifier::DIM, dim(), not_dim() -> T);
|
||||
modifier!(Modifier::ITALIC, italic(), not_italic() -> T);
|
||||
modifier!(Modifier::UNDERLINED, underlined(), not_underlined() -> T);
|
||||
modifier!(Modifier::SLOW_BLINK, slow_blink(), not_slow_blink() -> T);
|
||||
modifier!(Modifier::RAPID_BLINK, rapid_blink(), not_rapid_blink() -> T);
|
||||
modifier!(Modifier::REVERSED, reversed(), not_reversed() -> T);
|
||||
modifier!(Modifier::HIDDEN, hidden(), not_hidden() -> T);
|
||||
modifier!(Modifier::CROSSED_OUT, crossed_out(), not_crossed_out() -> T);
|
||||
modifier!(bold);
|
||||
modifier!(dim);
|
||||
modifier!(italic);
|
||||
modifier!(underlined);
|
||||
modifier!(slow_blink);
|
||||
modifier!(rapid_blink);
|
||||
modifier!(reversed);
|
||||
modifier!(hidden);
|
||||
modifier!(crossed_out);
|
||||
}
|
||||
|
||||
impl<T, U> Stylize<'_, T> for U
|
||||
@@ -311,18 +291,6 @@ impl<'a> Styled for &'a str {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Styled for Cow<'a, str> {
|
||||
type Item = Span<'a>;
|
||||
|
||||
fn style(&self) -> Style {
|
||||
Style::default()
|
||||
}
|
||||
|
||||
fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
|
||||
Span::styled(self, style)
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for String {
|
||||
type Item = Span<'static>;
|
||||
|
||||
@@ -335,43 +303,8 @@ impl Styled for String {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! styled {
|
||||
($impl_type:ty) => {
|
||||
impl Styled for $impl_type {
|
||||
type Item = Span<'static>;
|
||||
|
||||
fn style(&self) -> Style {
|
||||
Style::default()
|
||||
}
|
||||
|
||||
fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
|
||||
Span::styled(self.to_string(), style)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
styled!(bool);
|
||||
styled!(char);
|
||||
styled!(f32);
|
||||
styled!(f64);
|
||||
styled!(i8);
|
||||
styled!(i16);
|
||||
styled!(i32);
|
||||
styled!(i64);
|
||||
styled!(i128);
|
||||
styled!(isize);
|
||||
styled!(u8);
|
||||
styled!(u16);
|
||||
styled!(u32);
|
||||
styled!(u64);
|
||||
styled!(u128);
|
||||
styled!(usize);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
|
||||
use itertools::Itertools;
|
||||
use rstest::rstest;
|
||||
|
||||
@@ -477,12 +410,6 @@ mod tests {
|
||||
assert_eq!(s.clone().reset(), Span::from("hello").reset());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cow_string_styled() {
|
||||
let s = Cow::Borrowed("a");
|
||||
assert_eq!(s.red(), "a".red());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn temporary_string_styled() {
|
||||
// to_string() is used to create a temporary String, which is then styled. Without the
|
||||
@@ -498,26 +425,6 @@ mod tests {
|
||||
assert_eq!(sss, [Span::from("aa").red(), Span::from("bb").red()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_primitives_styled() {
|
||||
assert_eq!(true.red(), "true".red());
|
||||
assert_eq!('a'.red(), "a".red());
|
||||
assert_eq!(0.1f32.red(), "0.1".red());
|
||||
assert_eq!(0.1f64.red(), "0.1".red());
|
||||
assert_eq!(0i8.red(), "0".red());
|
||||
assert_eq!(0i16.red(), "0".red());
|
||||
assert_eq!(0i32.red(), "0".red());
|
||||
assert_eq!(0i64.red(), "0".red());
|
||||
assert_eq!(0i128.red(), "0".red());
|
||||
assert_eq!(0isize.red(), "0".red());
|
||||
assert_eq!(0u8.red(), "0".red());
|
||||
assert_eq!(0u16.red(), "0".red());
|
||||
assert_eq!(0u32.red(), "0".red());
|
||||
assert_eq!(0u64.red(), "0".red());
|
||||
assert_eq!(0u64.red(), "0".red());
|
||||
assert_eq!(0usize.red(), "0".red());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -235,7 +235,7 @@ pub const ONE_EIGHTH_RIGHT_EIGHT: &str = "▕";
|
||||
/// ▏xxxxx▕
|
||||
/// ▔▔▔▔▔▔▔
|
||||
/// ```
|
||||
#[expect(clippy::doc_markdown)]
|
||||
#[allow(clippy::doc_markdown)]
|
||||
pub const ONE_EIGHTH_WIDE: Set = Set {
|
||||
top_right: ONE_EIGHTH_BOTTOM_EIGHT,
|
||||
top_left: ONE_EIGHTH_BOTTOM_EIGHT,
|
||||
@@ -255,7 +255,7 @@ pub const ONE_EIGHTH_WIDE: Set = Set {
|
||||
/// ▕xx▏
|
||||
/// ▕▁▁▏
|
||||
/// ```
|
||||
#[expect(clippy::doc_markdown)]
|
||||
#[allow(clippy::doc_markdown)]
|
||||
pub const ONE_EIGHTH_TALL: Set = Set {
|
||||
top_right: ONE_EIGHTH_LEFT_EIGHT,
|
||||
top_left: ONE_EIGHTH_RIGHT_EIGHT,
|
||||
@@ -365,9 +365,6 @@ pub const EMPTY: Set = Set {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
|
||||
use indoc::{formatdoc, indoc};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -167,9 +167,6 @@ pub const HEAVY_QUADRUPLE_DASHED: Set = Set {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
|
||||
use indoc::{formatdoc, indoc};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -29,8 +29,6 @@ pub enum Marker {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
|
||||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::buffer::Buffer;
|
||||
use crate::layout::{Position, Rect};
|
||||
use crate::widgets::{StatefulWidget, Widget};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::{Position, Rect},
|
||||
widgets::{StatefulWidget, Widget},
|
||||
};
|
||||
|
||||
/// A consistent view into the terminal state for rendering a single frame.
|
||||
///
|
||||
@@ -65,7 +67,7 @@ impl Frame<'_> {
|
||||
/// If your app listens for a resize event from the backend, it should ignore the values from
|
||||
/// the event for any calculations that are used to render the current frame and use this value
|
||||
/// instead as this is the area of the buffer that is used to render the current frame.
|
||||
#[deprecated = "use `area()` instead"]
|
||||
#[deprecated = "use .area() as it's the more correct name"]
|
||||
pub const fn size(&self) -> Rect {
|
||||
self.viewport_area
|
||||
}
|
||||
@@ -152,13 +154,13 @@ impl Frame<'_> {
|
||||
/// [`Terminal::hide_cursor`]: crate::terminal::Terminal::hide_cursor
|
||||
/// [`Terminal::show_cursor`]: crate::terminal::Terminal::show_cursor
|
||||
/// [`Terminal::set_cursor_position`]: crate::terminal::Terminal::set_cursor_position
|
||||
#[deprecated = "use `set_cursor_position((x, y))` instead which takes `impl Into<Position>`"]
|
||||
#[deprecated = "the method set_cursor_position indicates more clearly what about the cursor to set"]
|
||||
pub fn set_cursor(&mut self, x: u16, y: u16) {
|
||||
self.set_cursor_position(Position { x, y });
|
||||
}
|
||||
|
||||
/// Gets the buffer that this `Frame` draws into as a mutable reference.
|
||||
pub const fn buffer_mut(&mut self) -> &mut Buffer {
|
||||
pub fn buffer_mut(&mut self) -> &mut Buffer {
|
||||
self.buffer
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
use crate::backend::{Backend, ClearType};
|
||||
use crate::buffer::{Buffer, Cell};
|
||||
use crate::layout::{Position, Rect, Size};
|
||||
use crate::terminal::{CompletedFrame, Frame, TerminalOptions, Viewport};
|
||||
use std::io;
|
||||
|
||||
use crate::{
|
||||
backend::{Backend, ClearType},
|
||||
buffer::{Buffer, Cell},
|
||||
layout::{Position, Rect, Size},
|
||||
terminal::{CompletedFrame, Frame, TerminalOptions, Viewport},
|
||||
};
|
||||
|
||||
/// An interface to interact and draw [`Frame`]s on the user's terminal.
|
||||
///
|
||||
@@ -89,10 +93,8 @@ where
|
||||
fn drop(&mut self) {
|
||||
// Attempt to restore the cursor state
|
||||
if self.hidden_cursor {
|
||||
#[allow(unused_variables)]
|
||||
if let Err(err) = self.show_cursor() {
|
||||
#[cfg(feature = "std")]
|
||||
std::eprintln!("Failed to show the cursor: {err}");
|
||||
eprintln!("Failed to show the cursor: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,7 +117,7 @@ where
|
||||
/// let terminal = Terminal::new(backend)?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn new(backend: B) -> Result<Self, B::Error> {
|
||||
pub fn new(backend: B) -> io::Result<Self> {
|
||||
Self::with_options(
|
||||
backend,
|
||||
TerminalOptions {
|
||||
@@ -138,7 +140,7 @@ where
|
||||
/// let terminal = Terminal::with_options(backend, TerminalOptions { viewport })?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn with_options(mut backend: B, options: TerminalOptions) -> Result<Self, B::Error> {
|
||||
pub fn with_options(mut backend: B, options: TerminalOptions) -> io::Result<Self> {
|
||||
let area = match options.viewport {
|
||||
Viewport::Fullscreen | Viewport::Inline(_) => {
|
||||
Rect::from((Position::ORIGIN, backend.size()?))
|
||||
@@ -166,7 +168,7 @@ where
|
||||
}
|
||||
|
||||
/// Get a Frame object which provides a consistent view into the terminal state for rendering.
|
||||
pub const fn get_frame(&mut self) -> Frame {
|
||||
pub fn get_frame(&mut self) -> Frame {
|
||||
let count = self.frame_count;
|
||||
Frame {
|
||||
cursor_position: None,
|
||||
@@ -177,7 +179,7 @@ where
|
||||
}
|
||||
|
||||
/// Gets the current buffer as a mutable reference.
|
||||
pub const fn current_buffer_mut(&mut self) -> &mut Buffer {
|
||||
pub fn current_buffer_mut(&mut self) -> &mut Buffer {
|
||||
&mut self.buffers[self.current]
|
||||
}
|
||||
|
||||
@@ -187,13 +189,13 @@ where
|
||||
}
|
||||
|
||||
/// Gets the backend as a mutable reference
|
||||
pub const fn backend_mut(&mut self) -> &mut B {
|
||||
pub fn backend_mut(&mut self) -> &mut B {
|
||||
&mut self.backend
|
||||
}
|
||||
|
||||
/// Obtains a difference between the previous and the current buffer and passes it to the
|
||||
/// current backend for drawing.
|
||||
pub fn flush(&mut self) -> Result<(), B::Error> {
|
||||
pub fn flush(&mut self) -> io::Result<()> {
|
||||
let previous_buffer = &self.buffers[1 - self.current];
|
||||
let current_buffer = &self.buffers[self.current];
|
||||
let updates = previous_buffer.diff(current_buffer);
|
||||
@@ -207,7 +209,7 @@ where
|
||||
///
|
||||
/// Requested area will be saved to remain consistent when rendering. This leads to a full clear
|
||||
/// of the screen.
|
||||
pub fn resize(&mut self, area: Rect) -> Result<(), B::Error> {
|
||||
pub fn resize(&mut self, area: Rect) -> io::Result<()> {
|
||||
let next_area = match self.viewport {
|
||||
Viewport::Inline(height) => {
|
||||
let offset_in_previous_viewport = self
|
||||
@@ -238,14 +240,14 @@ where
|
||||
}
|
||||
|
||||
/// Queries the backend for size and resizes if it doesn't match the previous size.
|
||||
pub fn autoresize(&mut self) -> Result<(), B::Error> {
|
||||
pub fn autoresize(&mut self) -> io::Result<()> {
|
||||
// fixed viewports do not get autoresized
|
||||
if matches!(self.viewport, Viewport::Fullscreen | Viewport::Inline(_)) {
|
||||
let area = Rect::from((Position::ORIGIN, self.size()?));
|
||||
if area != self.last_known_area {
|
||||
self.resize(area)?;
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -299,13 +301,13 @@ where
|
||||
/// }
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn draw<F>(&mut self, render_callback: F) -> Result<CompletedFrame, B::Error>
|
||||
pub fn draw<F>(&mut self, render_callback: F) -> io::Result<CompletedFrame>
|
||||
where
|
||||
F: FnOnce(&mut Frame),
|
||||
{
|
||||
self.try_draw(|frame| {
|
||||
render_callback(frame);
|
||||
Ok::<(), B::Error>(())
|
||||
io::Result::Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -374,10 +376,10 @@ where
|
||||
/// }
|
||||
/// # io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn try_draw<F, E>(&mut self, render_callback: F) -> Result<CompletedFrame, B::Error>
|
||||
pub fn try_draw<F, E>(&mut self, render_callback: F) -> io::Result<CompletedFrame>
|
||||
where
|
||||
F: FnOnce(&mut Frame) -> Result<(), E>,
|
||||
E: Into<B::Error>,
|
||||
E: Into<io::Error>,
|
||||
{
|
||||
// Autoresize - otherwise we get glitches if shrinking or potential desync between widgets
|
||||
// and the terminal (if growing), which may OOB.
|
||||
@@ -421,14 +423,14 @@ where
|
||||
}
|
||||
|
||||
/// Hides the cursor.
|
||||
pub fn hide_cursor(&mut self) -> Result<(), B::Error> {
|
||||
pub fn hide_cursor(&mut self) -> io::Result<()> {
|
||||
self.backend.hide_cursor()?;
|
||||
self.hidden_cursor = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Shows the cursor.
|
||||
pub fn show_cursor(&mut self) -> Result<(), B::Error> {
|
||||
pub fn show_cursor(&mut self) -> io::Result<()> {
|
||||
self.backend.show_cursor()?;
|
||||
self.hidden_cursor = false;
|
||||
Ok(())
|
||||
@@ -438,27 +440,27 @@ where
|
||||
///
|
||||
/// This is the position of the cursor after the last draw call and is returned as a tuple of
|
||||
/// `(x, y)` coordinates.
|
||||
#[deprecated = "use `get_cursor_position()` instead which returns `Result<Position>`"]
|
||||
pub fn get_cursor(&mut self) -> Result<(u16, u16), B::Error> {
|
||||
#[deprecated = "the method get_cursor_position indicates more clearly what about the cursor to get"]
|
||||
pub fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
|
||||
let Position { x, y } = self.get_cursor_position()?;
|
||||
Ok((x, y))
|
||||
}
|
||||
|
||||
/// Sets the cursor position.
|
||||
#[deprecated = "use `set_cursor_position((x, y))` instead which takes `impl Into<Position>`"]
|
||||
pub fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), B::Error> {
|
||||
#[deprecated = "the method set_cursor_position indicates more clearly what about the cursor to set"]
|
||||
pub fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
|
||||
self.set_cursor_position(Position { x, y })
|
||||
}
|
||||
|
||||
/// Gets the current cursor position.
|
||||
///
|
||||
/// This is the position of the cursor after the last draw call.
|
||||
pub fn get_cursor_position(&mut self) -> Result<Position, B::Error> {
|
||||
pub fn get_cursor_position(&mut self) -> io::Result<Position> {
|
||||
self.backend.get_cursor_position()
|
||||
}
|
||||
|
||||
/// Sets the cursor position.
|
||||
pub fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> Result<(), B::Error> {
|
||||
pub fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> {
|
||||
let position = position.into();
|
||||
self.backend.set_cursor_position(position)?;
|
||||
self.last_known_cursor_pos = position;
|
||||
@@ -466,7 +468,7 @@ where
|
||||
}
|
||||
|
||||
/// Clear the terminal and force a full redraw on the next draw call.
|
||||
pub fn clear(&mut self) -> Result<(), B::Error> {
|
||||
pub fn clear(&mut self) -> io::Result<()> {
|
||||
match self.viewport {
|
||||
Viewport::Fullscreen => self.backend.clear_region(ClearType::All)?,
|
||||
Viewport::Inline(_) => {
|
||||
@@ -494,7 +496,7 @@ where
|
||||
}
|
||||
|
||||
/// Queries the real size of the backend.
|
||||
pub fn size(&self) -> Result<Size, B::Error> {
|
||||
pub fn size(&self) -> io::Result<Size> {
|
||||
self.backend.size()
|
||||
}
|
||||
|
||||
@@ -574,7 +576,7 @@ where
|
||||
/// .render(buf.area, buf);
|
||||
/// });
|
||||
/// ```
|
||||
pub fn insert_before<F>(&mut self, height: u16, draw_fn: F) -> Result<(), B::Error>
|
||||
pub fn insert_before<F>(&mut self, height: u16, draw_fn: F) -> io::Result<()>
|
||||
where
|
||||
F: FnOnce(&mut Buffer),
|
||||
{
|
||||
@@ -593,7 +595,7 @@ where
|
||||
&mut self,
|
||||
height: u16,
|
||||
draw_fn: impl FnOnce(&mut Buffer),
|
||||
) -> Result<(), B::Error> {
|
||||
) -> io::Result<()> {
|
||||
// The approach of this function is to first render all of the lines to insert into a
|
||||
// temporary buffer, and then to loop drawing chunks from the buffer to the screen. drawing
|
||||
// this buffer onto the screen.
|
||||
@@ -694,7 +696,7 @@ where
|
||||
&mut self,
|
||||
mut height: u16,
|
||||
draw_fn: impl FnOnce(&mut Buffer),
|
||||
) -> Result<(), B::Error> {
|
||||
) -> io::Result<()> {
|
||||
// The approach of this function is to first render all of the lines to insert into a
|
||||
// temporary buffer, and then to loop drawing chunks from the buffer to the screen. drawing
|
||||
// this buffer onto the screen.
|
||||
@@ -766,7 +768,7 @@ where
|
||||
y_offset: u16,
|
||||
lines_to_draw: u16,
|
||||
cells: &'a [Cell],
|
||||
) -> Result<&'a [Cell], B::Error> {
|
||||
) -> io::Result<&'a [Cell]> {
|
||||
let width: usize = self.last_known_area.width.into();
|
||||
let (to_draw, remainder) = cells.split_at(width * lines_to_draw as usize);
|
||||
if lines_to_draw > 0 {
|
||||
@@ -789,7 +791,7 @@ where
|
||||
y_offset: u16,
|
||||
lines_to_draw: u16,
|
||||
cells: &'a [Cell],
|
||||
) -> Result<&'a [Cell], B::Error> {
|
||||
) -> io::Result<&'a [Cell]> {
|
||||
let width: usize = self.last_known_area.width.into();
|
||||
let (to_draw, remainder) = cells.split_at(width * lines_to_draw as usize);
|
||||
if lines_to_draw > 0 {
|
||||
@@ -807,7 +809,7 @@ where
|
||||
|
||||
/// Scroll the whole screen up by the given number of lines.
|
||||
#[cfg(not(feature = "scrolling-regions"))]
|
||||
fn scroll_up(&mut self, lines_to_scroll: u16) -> Result<(), B::Error> {
|
||||
fn scroll_up(&mut self, lines_to_scroll: u16) -> io::Result<()> {
|
||||
if lines_to_scroll > 0 {
|
||||
self.set_cursor_position(Position::new(
|
||||
0,
|
||||
@@ -824,7 +826,7 @@ fn compute_inline_size<B: Backend>(
|
||||
height: u16,
|
||||
size: Size,
|
||||
offset_in_previous_viewport: u16,
|
||||
) -> Result<(Rect, Position), B::Error> {
|
||||
) -> io::Result<(Rect, Position)> {
|
||||
let pos = backend.get_cursor_position()?;
|
||||
let mut row = pos.y;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use core::fmt;
|
||||
use std::fmt;
|
||||
|
||||
use crate::layout::Rect;
|
||||
|
||||
@@ -42,8 +42,6 @@ impl fmt::Display for Viewport {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
#![deny(missing_docs)]
|
||||
#![warn(clippy::pedantic, clippy::nursery, clippy::arithmetic_side_effects)]
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use core::fmt;
|
||||
use std::{borrow::Cow, fmt};
|
||||
|
||||
use unicode_truncate::UnicodeTruncateStr;
|
||||
|
||||
use crate::buffer::Buffer;
|
||||
use crate::layout::{Alignment, Rect};
|
||||
use crate::style::{Style, Styled};
|
||||
use crate::text::{Span, StyledGrapheme, Text};
|
||||
use crate::widgets::Widget;
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Rect},
|
||||
style::{Style, Styled},
|
||||
text::{Span, StyledGrapheme, Text},
|
||||
widgets::Widget,
|
||||
};
|
||||
|
||||
/// A line of text, consisting of one or more [`Span`]s.
|
||||
///
|
||||
@@ -77,8 +75,10 @@ use crate::widgets::Widget;
|
||||
/// [`Style`].
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::{Color, Modifier, Style, Stylize};
|
||||
/// use ratatui_core::text::{Line, Span};
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier, Style, Stylize},
|
||||
/// text::{Line, Span},
|
||||
/// };
|
||||
///
|
||||
/// let style = Style::new().yellow();
|
||||
/// let line = Line::raw("Hello, world!").style(style);
|
||||
@@ -102,8 +102,10 @@ use crate::widgets::Widget;
|
||||
/// methods of the [`Stylize`] trait.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::{Color, Modifier, Style, Stylize};
|
||||
/// use ratatui_core::text::Line;
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier, Style, Stylize},
|
||||
/// text::Line,
|
||||
/// };
|
||||
///
|
||||
/// let line = Line::from("Hello world!").style(Style::new().yellow().italic());
|
||||
/// let line = Line::from("Hello world!").style(Color::Yellow);
|
||||
@@ -119,8 +121,7 @@ use crate::widgets::Widget;
|
||||
/// ignored and the line is truncated.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::layout::Alignment;
|
||||
/// use ratatui_core::text::Line;
|
||||
/// use ratatui_core::{layout::Alignment, text::Line};
|
||||
///
|
||||
/// let line = Line::from("Hello world!").alignment(Alignment::Right);
|
||||
/// let line = Line::from("Hello world!").centered();
|
||||
@@ -133,11 +134,13 @@ use crate::widgets::Widget;
|
||||
/// `Line` implements the [`Widget`] trait, which means it can be rendered to a [`Buffer`].
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::buffer::Buffer;
|
||||
/// use ratatui_core::layout::Rect;
|
||||
/// use ratatui_core::style::{Style, Stylize};
|
||||
/// use ratatui_core::text::Line;
|
||||
/// use ratatui_core::widgets::Widget;
|
||||
/// use ratatui_core::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Line,
|
||||
/// widgets::Widget,
|
||||
/// };
|
||||
///
|
||||
/// # fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// // in another widget's render method
|
||||
@@ -271,8 +274,10 @@ impl<'a> Line<'a> {
|
||||
/// ```rust
|
||||
/// use std::borrow::Cow;
|
||||
///
|
||||
/// use ratatui_core::style::{Style, Stylize};
|
||||
/// use ratatui_core::text::Line;
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Line,
|
||||
/// };
|
||||
///
|
||||
/// let style = Style::new().yellow().italic();
|
||||
/// Line::styled("My text", style);
|
||||
@@ -301,8 +306,7 @@ impl<'a> Line<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::Stylize;
|
||||
/// use ratatui_core::text::Line;
|
||||
/// use ratatui_core::{style::Stylize, text::Line};
|
||||
///
|
||||
/// let line = Line::default().spans(vec!["Hello".blue(), " world!".green()]);
|
||||
/// let line = Line::default().spans([1, 2, 3].iter().map(|i| format!("Item {}", i)));
|
||||
@@ -330,8 +334,10 @@ impl<'a> Line<'a> {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::{Style, Stylize};
|
||||
/// use ratatui_core::text::Line;
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Line,
|
||||
/// };
|
||||
///
|
||||
/// let mut line = Line::from("foo").style(Style::new().red());
|
||||
/// ```
|
||||
@@ -352,8 +358,7 @@ impl<'a> Line<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::layout::Alignment;
|
||||
/// use ratatui_core::text::Line;
|
||||
/// use ratatui_core::{layout::Alignment, text::Line};
|
||||
///
|
||||
/// let mut line = Line::from("Hi, what's up?");
|
||||
/// assert_eq!(None, line.alignment);
|
||||
@@ -429,8 +434,7 @@ impl<'a> Line<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::Stylize;
|
||||
/// use ratatui_core::text::Line;
|
||||
/// use ratatui_core::{style::Stylize, text::Line};
|
||||
///
|
||||
/// let line = Line::from(vec!["Hello".blue(), " world!".green()]);
|
||||
/// assert_eq!(12, line.width());
|
||||
@@ -452,8 +456,10 @@ impl<'a> Line<'a> {
|
||||
/// ```rust
|
||||
/// use std::iter::Iterator;
|
||||
///
|
||||
/// use ratatui_core::style::{Color, Style};
|
||||
/// use ratatui_core::text::{Line, StyledGrapheme};
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Style},
|
||||
/// text::{Line, StyledGrapheme},
|
||||
/// };
|
||||
///
|
||||
/// let line = Line::styled("Text", Style::default().fg(Color::Yellow));
|
||||
/// let style = Style::default().fg(Color::Green).bg(Color::Black);
|
||||
@@ -494,8 +500,10 @@ impl<'a> Line<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::{Color, Modifier};
|
||||
/// use ratatui_core::text::Line;
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier},
|
||||
/// text::Line,
|
||||
/// };
|
||||
///
|
||||
/// let line = Line::styled("My text", Modifier::ITALIC);
|
||||
///
|
||||
@@ -521,8 +529,10 @@ impl<'a> Line<'a> {
|
||||
///
|
||||
/// ```rust
|
||||
/// # let style = Style::default().yellow();
|
||||
/// use ratatui_core::style::{Style, Stylize};
|
||||
/// use ratatui_core::text::Line;
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Line,
|
||||
/// };
|
||||
///
|
||||
/// let line = Line::styled("My text", style);
|
||||
///
|
||||
@@ -534,12 +544,12 @@ impl<'a> Line<'a> {
|
||||
}
|
||||
|
||||
/// Returns an iterator over the spans of this line.
|
||||
pub fn iter(&self) -> core::slice::Iter<Span<'a>> {
|
||||
pub fn iter(&self) -> std::slice::Iter<Span<'a>> {
|
||||
self.spans.iter()
|
||||
}
|
||||
|
||||
/// Returns a mutable iterator over the spans of this line.
|
||||
pub fn iter_mut(&mut self) -> core::slice::IterMut<Span<'a>> {
|
||||
pub fn iter_mut(&mut self) -> std::slice::IterMut<Span<'a>> {
|
||||
self.spans.iter_mut()
|
||||
}
|
||||
|
||||
@@ -564,7 +574,7 @@ impl<'a> Line<'a> {
|
||||
|
||||
impl<'a> IntoIterator for Line<'a> {
|
||||
type Item = Span<'a>;
|
||||
type IntoIter = alloc::vec::IntoIter<Span<'a>>;
|
||||
type IntoIter = std::vec::IntoIter<Span<'a>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.spans.into_iter()
|
||||
@@ -573,7 +583,7 @@ impl<'a> IntoIterator for Line<'a> {
|
||||
|
||||
impl<'a> IntoIterator for &'a Line<'a> {
|
||||
type Item = &'a Span<'a>;
|
||||
type IntoIter = core::slice::Iter<'a, Span<'a>>;
|
||||
type IntoIter = std::slice::Iter<'a, Span<'a>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
@@ -582,7 +592,7 @@ impl<'a> IntoIterator for &'a Line<'a> {
|
||||
|
||||
impl<'a> IntoIterator for &'a mut Line<'a> {
|
||||
type Item = &'a mut Span<'a>;
|
||||
type IntoIter = core::slice::IterMut<'a, Span<'a>>;
|
||||
type IntoIter = std::slice::IterMut<'a, Span<'a>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter_mut()
|
||||
@@ -641,7 +651,7 @@ where
|
||||
}
|
||||
|
||||
/// Adds a `Span` to a `Line`, returning a new `Line` with the `Span` added.
|
||||
impl<'a> core::ops::Add<Span<'a>> for Line<'a> {
|
||||
impl<'a> std::ops::Add<Span<'a>> for Line<'a> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(mut self, rhs: Span<'a>) -> Self::Output {
|
||||
@@ -651,7 +661,7 @@ impl<'a> core::ops::Add<Span<'a>> for Line<'a> {
|
||||
}
|
||||
|
||||
/// Adds two `Line`s together, returning a new `Text` with the contents of the two `Line`s.
|
||||
impl<'a> core::ops::Add<Self> for Line<'a> {
|
||||
impl<'a> std::ops::Add<Self> for Line<'a> {
|
||||
type Output = Text<'a>;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
@@ -659,7 +669,7 @@ impl<'a> core::ops::Add<Self> for Line<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> core::ops::AddAssign<Span<'a>> for Line<'a> {
|
||||
impl<'a> std::ops::AddAssign<Span<'a>> for Line<'a> {
|
||||
fn add_assign(&mut self, rhs: Span<'a>) {
|
||||
self.spans.push(rhs);
|
||||
}
|
||||
@@ -726,7 +736,7 @@ impl Line<'_> {
|
||||
Some(Alignment::Left) | None => 0,
|
||||
};
|
||||
render_spans(&self.spans, area, buf, skip_width);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -834,9 +844,7 @@ impl Styled for Line<'_> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
use core::iter;
|
||||
use std::dbg;
|
||||
use std::iter;
|
||||
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
@@ -1106,12 +1114,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn styled_graphemes() {
|
||||
const RED: Style = Style::new().red();
|
||||
const GREEN: Style = Style::new().green();
|
||||
const BLUE: Style = Style::new().blue();
|
||||
const RED_ON_WHITE: Style = Style::new().red().on_white();
|
||||
const GREEN_ON_WHITE: Style = Style::new().green().on_white();
|
||||
const BLUE_ON_WHITE: Style = Style::new().blue().on_white();
|
||||
const RED: Style = Style::new().fg(Color::Red);
|
||||
const GREEN: Style = Style::new().fg(Color::Green);
|
||||
const BLUE: Style = Style::new().fg(Color::Blue);
|
||||
const RED_ON_WHITE: Style = Style::new().fg(Color::Red).bg(Color::White);
|
||||
const GREEN_ON_WHITE: Style = Style::new().fg(Color::Green).bg(Color::White);
|
||||
const BLUE_ON_WHITE: Style = Style::new().fg(Color::Blue).bg(Color::White);
|
||||
|
||||
let line = Line::from(vec![
|
||||
Span::styled("He", RED),
|
||||
@@ -1192,9 +1200,9 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::buffer::Cell;
|
||||
|
||||
const BLUE: Style = Style::new().blue();
|
||||
const GREEN: Style = Style::new().green();
|
||||
const ITALIC: Style = Style::new().italic();
|
||||
const BLUE: Style = Style::new().fg(Color::Blue);
|
||||
const GREEN: Style = Style::new().fg(Color::Green);
|
||||
const ITALIC: Style = Style::new().add_modifier(Modifier::ITALIC);
|
||||
|
||||
#[fixture]
|
||||
fn hello_world() -> Line<'static> {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use alloc::borrow::Cow;
|
||||
use core::fmt;
|
||||
use std::{borrow::Cow, fmt};
|
||||
|
||||
use crate::text::Text;
|
||||
|
||||
@@ -11,10 +10,12 @@ use crate::text::Text;
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::buffer::Buffer;
|
||||
/// use ratatui_core::layout::Rect;
|
||||
/// use ratatui_core::text::{Masked, Text};
|
||||
/// use ratatui_core::widgets::Widget;
|
||||
/// use ratatui_core::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// text::{Masked, Text},
|
||||
/// widgets::Widget,
|
||||
/// };
|
||||
///
|
||||
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 1));
|
||||
/// let password = Masked::new("12345", 'x');
|
||||
@@ -88,8 +89,6 @@ impl<'a> From<Masked<'a>> for Text<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
|
||||
use super::*;
|
||||
use crate::text::Line;
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::string::ToString;
|
||||
use core::fmt;
|
||||
use std::{borrow::Cow, fmt};
|
||||
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::buffer::Buffer;
|
||||
use crate::layout::Rect;
|
||||
use crate::style::{Style, Styled};
|
||||
use crate::text::{Line, StyledGrapheme};
|
||||
use crate::widgets::Widget;
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Style, Styled},
|
||||
text::{Line, StyledGrapheme},
|
||||
widgets::Widget,
|
||||
};
|
||||
|
||||
/// Represents a part of a line that is contiguous and where all characters share the same style.
|
||||
///
|
||||
@@ -56,8 +56,10 @@ use crate::widgets::Widget;
|
||||
/// the [`Stylize`] trait.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::{Style, Stylize};
|
||||
/// use ratatui_core::text::Span;
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Span,
|
||||
/// };
|
||||
///
|
||||
/// let span = Span::styled("test content", Style::new().green());
|
||||
/// let span = Span::styled(String::from("test content"), Style::new().green());
|
||||
@@ -71,8 +73,7 @@ use crate::widgets::Widget;
|
||||
/// defined in the [`Stylize`] trait.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::Stylize;
|
||||
/// use ratatui_core::text::Span;
|
||||
/// use ratatui_core::{style::Stylize, text::Span};
|
||||
///
|
||||
/// let span = Span::raw("test content").green().on_yellow().italic();
|
||||
/// let span = Span::raw(String::from("test content"))
|
||||
@@ -149,8 +150,10 @@ impl<'a> Span<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::{Style, Stylize};
|
||||
/// use ratatui_core::text::Span;
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Span,
|
||||
/// };
|
||||
///
|
||||
/// let style = Style::new().yellow().on_green().italic();
|
||||
/// Span::styled("test content", style);
|
||||
@@ -205,8 +208,10 @@ impl<'a> Span<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::{Style, Stylize};
|
||||
/// use ratatui_core::text::Span;
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Span,
|
||||
/// };
|
||||
///
|
||||
/// let mut span = Span::default().style(Style::new().green());
|
||||
/// ```
|
||||
@@ -228,8 +233,10 @@ impl<'a> Span<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::{Style, Stylize};
|
||||
/// use ratatui_core::text::Span;
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Span,
|
||||
/// };
|
||||
///
|
||||
/// let span = Span::styled("test content", Style::new().green().italic())
|
||||
/// .patch_style(Style::new().red().on_yellow().bold());
|
||||
@@ -252,8 +259,10 @@ impl<'a> Span<'a> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::{Style, Stylize};
|
||||
/// use ratatui_core::text::Span;
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Span,
|
||||
/// };
|
||||
///
|
||||
/// let span = Span::styled(
|
||||
/// "Test Content",
|
||||
@@ -285,8 +294,10 @@ impl<'a> Span<'a> {
|
||||
/// ```rust
|
||||
/// use std::iter::Iterator;
|
||||
///
|
||||
/// use ratatui_core::style::{Style, Stylize};
|
||||
/// use ratatui_core::text::{Span, StyledGrapheme};
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::{Span, StyledGrapheme},
|
||||
/// };
|
||||
///
|
||||
/// let span = Span::styled("Test", Style::new().green().italic());
|
||||
/// let style = Style::new().red().on_yellow();
|
||||
@@ -329,8 +340,8 @@ impl<'a> Span<'a> {
|
||||
Line::from(self).left_aligned()
|
||||
}
|
||||
|
||||
#[expect(clippy::wrong_self_convention)]
|
||||
#[deprecated = "use `into_left_aligned_line()` instead"]
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
#[deprecated = "use into_left_aligned_line"]
|
||||
pub fn to_left_aligned_line(self) -> Line<'a> {
|
||||
self.into_left_aligned_line()
|
||||
}
|
||||
@@ -349,8 +360,8 @@ impl<'a> Span<'a> {
|
||||
Line::from(self).centered()
|
||||
}
|
||||
|
||||
#[expect(clippy::wrong_self_convention)]
|
||||
#[deprecated = "use `into_centered_line()` instead"]
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
#[deprecated = "use into_centered_line"]
|
||||
pub fn to_centered_line(self) -> Line<'a> {
|
||||
self.into_centered_line()
|
||||
}
|
||||
@@ -369,8 +380,8 @@ impl<'a> Span<'a> {
|
||||
Line::from(self).right_aligned()
|
||||
}
|
||||
|
||||
#[expect(clippy::wrong_self_convention)]
|
||||
#[deprecated = "use `into_right_aligned_line()` instead"]
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
#[deprecated = "use into_right_aligned_line"]
|
||||
pub fn to_right_aligned_line(self) -> Line<'a> {
|
||||
self.into_right_aligned_line()
|
||||
}
|
||||
@@ -385,7 +396,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> core::ops::Add<Self> for Span<'a> {
|
||||
impl<'a> std::ops::Add<Self> for Span<'a> {
|
||||
type Output = Line<'a>;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
@@ -495,15 +506,10 @@ impl fmt::Display for Span<'_> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::String;
|
||||
use alloc::{format, vec};
|
||||
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
use super::*;
|
||||
use crate::buffer::Cell;
|
||||
use crate::layout::Alignment;
|
||||
use crate::style::Stylize;
|
||||
use crate::{buffer::Cell, layout::Alignment, style::Stylize};
|
||||
|
||||
#[fixture]
|
||||
fn small_buf() -> Buffer {
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
#![warn(missing_docs)]
|
||||
use alloc::borrow::{Cow, ToOwned};
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use core::fmt;
|
||||
use std::{borrow::Cow, fmt};
|
||||
|
||||
use crate::buffer::Buffer;
|
||||
use crate::layout::{Alignment, Rect};
|
||||
use crate::style::{Style, Styled};
|
||||
use crate::text::{Line, Span};
|
||||
use crate::widgets::Widget;
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Rect},
|
||||
style::{Style, Styled},
|
||||
text::{Line, Span},
|
||||
widgets::Widget,
|
||||
};
|
||||
|
||||
/// A string split over one or more lines.
|
||||
///
|
||||
@@ -68,11 +66,12 @@ use crate::widgets::Widget;
|
||||
/// [`core::iter::Extend`] which enables the concatenation of several [`Text`] blocks.
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::borrow::Cow;
|
||||
/// use std::iter;
|
||||
/// use std::{borrow::Cow, iter};
|
||||
///
|
||||
/// use ratatui_core::style::{Color, Modifier, Style, Stylize};
|
||||
/// use ratatui_core::text::{Line, Span, Text};
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier, Style, Stylize},
|
||||
/// text::{Line, Span, Text},
|
||||
/// };
|
||||
///
|
||||
/// let style = Style::new().yellow().italic();
|
||||
/// let text = Text::raw("The first line\nThe second line").style(style);
|
||||
@@ -109,8 +108,10 @@ use crate::widgets::Widget;
|
||||
/// [`Stylize`] trait.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::{Color, Modifier, Style, Stylize};
|
||||
/// use ratatui_core::text::{Line, Text};
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier, Style, Stylize},
|
||||
/// text::{Line, Text},
|
||||
/// };
|
||||
///
|
||||
/// let text = Text::from("The first line\nThe second line").style(Style::new().yellow().italic());
|
||||
/// let text = Text::from("The first line\nThe second line")
|
||||
@@ -128,8 +129,10 @@ use crate::widgets::Widget;
|
||||
/// Lines composing the text can also be individually aligned with [`Line::alignment`].
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::layout::Alignment;
|
||||
/// use ratatui_core::text::{Line, Text};
|
||||
/// use ratatui_core::{
|
||||
/// layout::Alignment,
|
||||
/// text::{Line, Text},
|
||||
/// };
|
||||
///
|
||||
/// let text = Text::from("The first line\nThe second line").alignment(Alignment::Right);
|
||||
/// let text = Text::from("The first line\nThe second line").right_aligned();
|
||||
@@ -147,8 +150,7 @@ use crate::widgets::Widget;
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui_core::{buffer::Buffer, layout::Rect};
|
||||
/// use ratatui_core::text::Text;
|
||||
/// use ratatui_core::widgets::Widget;
|
||||
/// use ratatui_core::{text::Text, widgets::Widget};
|
||||
///
|
||||
/// // within another widget's `render` method:
|
||||
/// # fn render(area: Rect, buf: &mut Buffer) {
|
||||
@@ -189,6 +191,7 @@ use crate::widgets::Widget;
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`Paragraph`]: crate::widgets::Paragraph
|
||||
/// [`Stylize`]: crate::style::Stylize
|
||||
#[derive(Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Text<'a> {
|
||||
@@ -254,8 +257,10 @@ impl<'a> Text<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::{Color, Modifier, Style};
|
||||
/// use ratatui_core::text::Text;
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier, Style},
|
||||
/// text::Text,
|
||||
/// };
|
||||
///
|
||||
/// let style = Style::default()
|
||||
/// .fg(Color::Yellow)
|
||||
@@ -314,8 +319,10 @@ impl<'a> Text<'a> {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::{Style, Stylize};
|
||||
/// use ratatui_core::text::Text;
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Text,
|
||||
/// };
|
||||
///
|
||||
/// let mut line = Text::from("foo").style(Style::new().red());
|
||||
/// ```
|
||||
@@ -344,8 +351,10 @@ impl<'a> Text<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::{Color, Modifier};
|
||||
/// use ratatui_core::text::Text;
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier},
|
||||
/// text::Text,
|
||||
/// };
|
||||
///
|
||||
/// let raw_text = Text::styled("The first line\nThe second line", Modifier::ITALIC);
|
||||
/// let styled_text = Text::styled(
|
||||
@@ -375,8 +384,10 @@ impl<'a> Text<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::style::{Color, Modifier, Style};
|
||||
/// use ratatui_core::text::Text;
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier, Style},
|
||||
/// text::Text,
|
||||
/// };
|
||||
///
|
||||
/// let text = Text::styled(
|
||||
/// "The first line\nThe second line",
|
||||
@@ -404,8 +415,7 @@ impl<'a> Text<'a> {
|
||||
/// Set alignment to the whole text.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::layout::Alignment;
|
||||
/// use ratatui_core::text::Text;
|
||||
/// use ratatui_core::{layout::Alignment, text::Text};
|
||||
///
|
||||
/// let mut text = Text::from("Hi, what's up?");
|
||||
/// assert_eq!(None, text.alignment);
|
||||
@@ -418,8 +428,10 @@ impl<'a> Text<'a> {
|
||||
/// Set a default alignment and override it on a per line basis.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::layout::Alignment;
|
||||
/// use ratatui_core::text::{Line, Text};
|
||||
/// use ratatui_core::{
|
||||
/// layout::Alignment,
|
||||
/// text::{Line, Text},
|
||||
/// };
|
||||
///
|
||||
/// let text = Text::from(vec![
|
||||
/// Line::from("left").alignment(Alignment::Left),
|
||||
@@ -507,12 +519,12 @@ impl<'a> Text<'a> {
|
||||
}
|
||||
|
||||
/// Returns an iterator over the lines of the text.
|
||||
pub fn iter(&self) -> core::slice::Iter<Line<'a>> {
|
||||
pub fn iter(&self) -> std::slice::Iter<Line<'a>> {
|
||||
self.lines.iter()
|
||||
}
|
||||
|
||||
/// Returns an iterator that allows modifying each line.
|
||||
pub fn iter_mut(&mut self) -> core::slice::IterMut<Line<'a>> {
|
||||
pub fn iter_mut(&mut self) -> std::slice::IterMut<Line<'a>> {
|
||||
self.lines.iter_mut()
|
||||
}
|
||||
|
||||
@@ -561,7 +573,7 @@ impl<'a> Text<'a> {
|
||||
|
||||
impl<'a> IntoIterator for Text<'a> {
|
||||
type Item = Line<'a>;
|
||||
type IntoIter = alloc::vec::IntoIter<Self::Item>;
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.lines.into_iter()
|
||||
@@ -570,7 +582,7 @@ impl<'a> IntoIterator for Text<'a> {
|
||||
|
||||
impl<'a> IntoIterator for &'a Text<'a> {
|
||||
type Item = &'a Line<'a>;
|
||||
type IntoIter = core::slice::Iter<'a, Line<'a>>;
|
||||
type IntoIter = std::slice::Iter<'a, Line<'a>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
@@ -579,7 +591,7 @@ impl<'a> IntoIterator for &'a Text<'a> {
|
||||
|
||||
impl<'a> IntoIterator for &'a mut Text<'a> {
|
||||
type Item = &'a mut Line<'a>;
|
||||
type IntoIter = core::slice::IterMut<'a, Line<'a>>;
|
||||
type IntoIter = std::slice::IterMut<'a, Line<'a>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter_mut()
|
||||
@@ -644,7 +656,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> core::ops::Add<Line<'a>> for Text<'a> {
|
||||
impl<'a> std::ops::Add<Line<'a>> for Text<'a> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(mut self, line: Line<'a>) -> Self::Output {
|
||||
@@ -656,7 +668,7 @@ impl<'a> core::ops::Add<Line<'a>> for Text<'a> {
|
||||
/// Adds two `Text` together.
|
||||
///
|
||||
/// This ignores the style and alignment of the second `Text`.
|
||||
impl core::ops::Add<Self> for Text<'_> {
|
||||
impl std::ops::Add<Self> for Text<'_> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(mut self, text: Self) -> Self::Output {
|
||||
@@ -665,7 +677,7 @@ impl core::ops::Add<Self> for Text<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> core::ops::AddAssign<Line<'a>> for Text<'a> {
|
||||
impl<'a> std::ops::AddAssign<Line<'a>> for Text<'a> {
|
||||
fn add_assign(&mut self, line: Line<'a>) {
|
||||
self.push_line(line);
|
||||
}
|
||||
@@ -746,8 +758,7 @@ impl Styled for Text<'_> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
use core::iter;
|
||||
use std::iter;
|
||||
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
//! The `widgets` module contains the `Widget` and `StatefulWidget` traits, which are used to
|
||||
//! render UI elements on the screen.
|
||||
|
||||
pub use self::stateful_widget::StatefulWidget;
|
||||
pub use self::widget::Widget;
|
||||
pub use self::{stateful_widget::StatefulWidget, widget::Widget};
|
||||
|
||||
mod stateful_widget;
|
||||
mod widget;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::buffer::Buffer;
|
||||
use crate::layout::Rect;
|
||||
use crate::{buffer::Buffer, layout::Rect};
|
||||
|
||||
/// A `StatefulWidget` is a widget that can take advantage of some local state to remember things
|
||||
/// between two draw calls.
|
||||
@@ -130,16 +129,10 @@ pub trait StatefulWidget {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
use alloc::string::{String, ToString};
|
||||
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
use super::*;
|
||||
use crate::buffer::Buffer;
|
||||
use crate::layout::Rect;
|
||||
use crate::text::Line;
|
||||
use crate::widgets::Widget;
|
||||
use crate::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
|
||||
|
||||
#[fixture]
|
||||
fn buf() -> Buffer {
|
||||
@@ -173,7 +166,7 @@ mod tests {
|
||||
impl StatefulWidget for Bytes {
|
||||
type State = [u8];
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let slice = core::str::from_utf8(state).unwrap();
|
||||
let slice = std::str::from_utf8(state).unwrap();
|
||||
Line::from(format!("Bytes: {slice}")).render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
use alloc::string::String;
|
||||
|
||||
use crate::buffer::Buffer;
|
||||
use crate::layout::Rect;
|
||||
use crate::style::Style;
|
||||
use crate::{buffer::Buffer, layout::Rect, style::Style};
|
||||
|
||||
/// A `Widget` is a type that can be drawn on a [`Buffer`] in a given [`Rect`].
|
||||
///
|
||||
@@ -49,10 +45,7 @@ use crate::style::Style;
|
||||
/// It's common to render widgets inside other widgets:
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::buffer::Buffer;
|
||||
/// use ratatui_core::layout::Rect;
|
||||
/// use ratatui_core::text::Line;
|
||||
/// use ratatui_core::widgets::Widget;
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
|
||||
///
|
||||
/// struct MyWidget;
|
||||
///
|
||||
@@ -105,9 +98,7 @@ mod tests {
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
use super::*;
|
||||
use crate::buffer::Buffer;
|
||||
use crate::layout::Rect;
|
||||
use crate::text::Line;
|
||||
use crate::{buffer::Buffer, layout::Rect, text::Line};
|
||||
|
||||
#[fixture]
|
||||
fn buf() -> Buffer {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user