Compare commits

..

3 Commits

Author SHA1 Message Date
Orhun Parmaksız
a6d580bc96 fix(examples): run the correct example for chart 2025-02-18 09:40:04 +03:00
Orhun Parmaksız
392b28c194 chore(examples): remove examples dir from ratatui 2025-02-12 00:08:56 +03:00
Orhun Parmaksız
5814c7c395 docs(examples): update app examples with tapes 2025-02-12 00:06:48 +03:00
195 changed files with 3416 additions and 5685 deletions

25
.github/workflows/bench_base.yml vendored Normal file
View 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
View 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 }}

View 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"

View File

@@ -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

View File

@@ -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

View File

@@ -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])

File diff suppressed because it is too large Load Diff

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"

View File

@@ -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

View File

@@ -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" },

View File

@@ -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(())
}

View File

@@ -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"

View File

@@ -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(),
_ => {}
}
}
}
}

View File

@@ -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

View File

@@ -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()),

View File

@@ -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(),

View File

@@ -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);

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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,

View File

@@ -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') => {

View File

@@ -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 }

View File

@@ -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] = [

View File

@@ -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(());

View File

@@ -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;

View File

@@ -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))?;

View File

@@ -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()?;

View File

@@ -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)]

View File

@@ -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"

View File

@@ -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(),

View File

@@ -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;

View File

@@ -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)
}

View File

@@ -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()?;

View File

@@ -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};

View File

@@ -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;

View File

@@ -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
}

View File

@@ -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};

View File

@@ -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);

View File

@@ -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

View File

@@ -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)]);

View File

@@ -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)
}

View File

@@ -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(())

View File

@@ -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]

View File

@@ -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))

View File

@@ -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);
}

View File

@@ -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());
}

View File

@@ -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(

View File

@@ -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]

View File

@@ -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());
}

View File

@@ -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**")

View File

@@ -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)]);

View File

@@ -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.

View File

@@ -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)
}

View File

@@ -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();
}

View File

@@ -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<_>>();

View File

@@ -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

View File

@@ -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]

View File

@@ -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());

View File

@@ -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(());
}
}
}

View File

@@ -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]

View File

@@ -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::*;

View File

@@ -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],
) {

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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
}

View File

@@ -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;

View File

@@ -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));
}
}

View File

@@ -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);

View File

@@ -9,8 +9,6 @@ pub enum Direction {
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use strum::ParseError;
use super::*;

View File

@@ -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.

View File

@@ -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 {
/// ![layout
/// example](https://camo.githubusercontent.com/77d22f3313b782a81e5e033ef82814bb48d786d2598699c27f8e757ccee62021/68747470733a2f2f7668732e636861726d2e73682f7668732d315a4e6f4e4c4e6c4c746b4a58706767396e435635652e676966)
///
/// [`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);
}
}

View File

@@ -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]

View File

@@ -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]

View File

@@ -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)
);
}
}

View File

@@ -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)));
}
}

View File

@@ -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]

View File

@@ -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;

View File

@@ -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));

View File

@@ -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));
}
}

View File

@@ -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));

View File

@@ -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));

View File

@@ -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;

View File

@@ -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!(

View File

@@ -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::*;

View File

@@ -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::*;

View File

@@ -29,8 +29,6 @@ pub enum Marker {
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use strum::ParseError;
use super::*;

View File

@@ -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
}

View File

@@ -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;

View File

@@ -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]

View File

@@ -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> {

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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};

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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