Compare commits

...

17 Commits

Author SHA1 Message Date
Josh McKinney
26163effdf refactor: modularize
WIP - this is just a PoC to understand whether this would work fine, termion and termwiz disabled for now
2024-09-27 15:01:08 -07:00
Josh McKinney
bc10af5931 chore(style): make Debug output for Text/Line/Span/Style more concise (#1383)
Given:

```rust
Text::from_iter([
    Line::from("without line fields"),
    Line::from("with line fields").bold().centered(),
    Line::from_iter([
        Span::from("without span fields"),
        Span::from("with span fields")
            .green()
            .on_black()
            .italic()
            .not_dim(),
    ]),
])
```

Debug:
```
Text [Line [Span("without line fields")], Line { style: Style::new().add_modifier(Modifier::BOLD), alignment: Some(Center), spans: [Span("with line fields")] }, Line [Span("without span fields"), Span { style: Style::new().green().on_black().add_modifier(Modifier::ITALIC).remove_modifier(Modifier::DIM), content: "with span fields" }]]
```

Fixes: https://github.com/ratatui/ratatui/issues/1382
---------

Co-authored-by: Orhun Parmaksız <orhun@archlinux.org>
Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-09-26 20:38:23 +03:00
dependabot[bot]
784f67a912 chore(deps): update octocrab requirement from 0.39.0 to 0.40.0 (#1386)
Updates the requirements on
[octocrab](https://github.com/XAMPPRocky/octocrab) to permit the latest
version.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/XAMPPRocky/octocrab/releases">octocrab's
releases</a>.</em></p>
<blockquote>
<h2>v0.40.0</h2>
<h3>Added</h3>
<ul>
<li>Support <code>remove_assignees</code> on issue API (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/686">#686</a>)</li>
<li>add missing fields in <code>CreateForkBuilder</code> (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/682">#682</a>)</li>
<li>Add <code>Gist::public</code> field (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/678">#678</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li><em>(refs)</em> [<strong>breaking</strong>] remove
<code>Reference::Commit</code> variant (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/697">#697</a>)</li>
</ul>
<h3>Other</h3>
<ul>
<li>Fix typo in cfg_attr statement (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/693">#693</a>)</li>
<li>Handle empty author object in pr_commits (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/656">#656</a>)</li>
<li>Add <code>DeviceCodes::poll_until_available</code> method (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/679">#679</a>)</li>
<li>Uncomment pr_commits function (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/680">#680</a>)</li>
<li>Only add base_path if req_pandq does not contain it (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/684">#684</a>)</li>
<li>Update code scanning alert (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/673">#673</a>)</li>
<li>Added <code>merged_by</code> and <code>closed_by</code> fields (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/674">#674</a>)</li>
<li>Update and Fixes to the Code Scanning Models &amp; Webhooks (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/675">#675</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/XAMPPRocky/octocrab/blob/main/CHANGELOG.md">octocrab's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/XAMPPRocky/octocrab/compare/v0.39.0...v0.40.0">0.40.0</a>
- 2024-09-22</h2>
<h3>Added</h3>
<ul>
<li>Support <code>remove_assignees</code> on issue API (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/686">#686</a>)</li>
<li>add missing fields in <code>CreateForkBuilder</code> (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/682">#682</a>)</li>
<li>Add <code>Gist::public</code> field (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/678">#678</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li><em>(refs)</em> [<strong>breaking</strong>] remove
<code>Reference::Commit</code> variant (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/697">#697</a>)</li>
</ul>
<h3>Other</h3>
<ul>
<li>Fix typo in cfg_attr statement (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/693">#693</a>)</li>
<li>Handle empty author object in pr_commits (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/656">#656</a>)</li>
<li>Add <code>DeviceCodes::poll_until_available</code> method (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/679">#679</a>)</li>
<li>Uncomment pr_commits function (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/680">#680</a>)</li>
<li>Only add base_path if req_pandq does not contain it (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/684">#684</a>)</li>
<li>Update code scanning alert (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/673">#673</a>)</li>
<li>Added <code>merged_by</code> and <code>closed_by</code> fields (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/674">#674</a>)</li>
<li>Update and Fixes to the Code Scanning Models &amp; Webhooks (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/675">#675</a>)</li>
</ul>
<h2><a
href="https://github.com/XAMPPRocky/octocrab/compare/v0.38.0...v0.39.0">0.39.0</a>
- 2024-07-30</h2>
<h3>Added</h3>
<ul>
<li>support permission on list_collaborators (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/630">#630</a>)</li>
<li>add check run pull requests and list parameters (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/494">#494</a>)</li>
<li>implement hook deliveries (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/668">#668</a>)</li>
<li>allow sending non String payload with execute (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/665">#665</a>)</li>
<li>added /user/blocks functionality (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/657">#657</a>)</li>
<li>add method to create repo webhook (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/640">#640</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>use put instead of get for set_thread_subscription (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/661">#661</a>)</li>
<li><em>(builder)</em> Change add_retry_config signature to match others
in OctocrabBuilder (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/643">#643</a>)</li>
</ul>
<h3>Other</h3>
<ul>
<li>getting Code Scanning (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/669">#669</a>)</li>
<li>added missing /repos/{owner}/{repo}/pulls/... handlers (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/546">#546</a>)
(<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/605">#605</a>)</li>
<li>Properly mark feature-gated functionality in docs (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/662">#662</a>)</li>
<li>repos/releases improvements (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/666">#666</a>)</li>
<li>Add AutoRebaseEnabled to models.rs (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/660">#660</a>)</li>
<li>cargo fmt (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/658">#658</a>)</li>
<li>Fix issue <a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/635">#635</a>
(<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/637">#637</a>)</li>
<li>Update issues.rs (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/634">#634</a>)</li>
<li>Add head repo to create pr (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/636">#636</a>)</li>
<li>Added support for make_latest in UpdateReleaseBuilder (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/646">#646</a>)</li>
<li>Changing the user name from required to optional parameter (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/650">#650</a>)</li>
<li>Update models.rs (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/pull/651">#651</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="bd8b648282"><code>bd8b648</code></a>
chore: release (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/issues/685">#685</a>)</li>
<li><a
href="a8bcee4c29"><code>a8bcee4</code></a>
fix(refs)!: remove <code>Reference::Commit</code> variant (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/issues/697">#697</a>)</li>
<li><a
href="3167540eb0"><code>3167540</code></a>
Fix typo in cfg_attr statement (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/issues/693">#693</a>)</li>
<li><a
href="01767f30eb"><code>01767f3</code></a>
Handle empty author object in pr_commits (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/issues/656">#656</a>)</li>
<li><a
href="689ee43ab0"><code>689ee43</code></a>
feat: Support <code>remove_assignees</code> on issue API (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/issues/686">#686</a>)</li>
<li><a
href="8aa113e079"><code>8aa113e</code></a>
Add <code>DeviceCodes::poll_until_available</code> method (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/issues/679">#679</a>)</li>
<li><a
href="501ef7439d"><code>501ef74</code></a>
feat: add missing fields in <code>CreateForkBuilder</code> (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/issues/682">#682</a>)</li>
<li><a
href="807cc7592d"><code>807cc75</code></a>
Uncomment pr_commits function (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/issues/680">#680</a>)</li>
<li><a
href="0459e31986"><code>0459e31</code></a>
Only add base_path if req_pandq does not contain it (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/issues/684">#684</a>)</li>
<li><a
href="70e9b9e49f"><code>70e9b9e</code></a>
feat: Add <code>Gist::public</code> field (<a
href="https://redirect.github.com/XAMPPRocky/octocrab/issues/678">#678</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/XAMPPRocky/octocrab/compare/v0.39.0...v0.40.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 21:21:17 +03:00
Josh McKinney
f4880b40cc chore: lock unicode-width version to 0.1.13 (#1384)
0.1.14 contains breaking changes which we'll need to investigate.
This commit puts a lock on the current version for now.

Changes
https://github.com/unicode-rs/unicode-width/compare/v0.1.13...v0.1.14
2024-09-21 02:58:29 -07:00
Josh McKinney
67c0ea243b chore(block): deprecate block::Title (#1372)
`ratatui::widgets::block::Title` is deprecated in favor of using `Line`
to represent titles.
This removes an unnecessary layer of wrapping (string -> Span -> Line ->
Title).

This struct will be removed in a future release of Ratatui (likely
0.31).
For more information see:
<https://github.com/ratatui/ratatui/issues/738>

To update your code:
```rust
Block::new().title(Title::from("foo"));
// becomes any of
Block::new().title("foo");
Block::new().title(Line::from("foo"));

Block::new().title(Title::from("foo").position(Position::TOP));
// becomes any of
Block::new().title_top("foo");
Block::new().title_top(Line::from("foo"));

Block::new().title(Title::from("foo").position(Position::BOTTOM));
// becomes any of
Block::new().title_bottom("foo");
Block::new().title_bottom(Line::from("foo"));
```
2024-09-20 10:21:26 +03:00
Anthony Rodgers
b9653ba05a fix: prevent calender render panic when terminal height is small (#1380)
Fixes: #1379
2024-09-19 06:02:26 -07:00
dependabot[bot]
9875d9facc chore(deps): bump DavidAnson/markdownlint-cli2-action from 16 to 17 (#1376)
Bumps
[DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action)
from 16 to 17.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/davidanson/markdownlint-cli2-action/releases">DavidAnson/markdownlint-cli2-action's
releases</a>.</em></p>
<blockquote>
<h2>Update markdownlint version (markdownlint-cli2 v0.14.0, markdownlint
v0.35.0).</h2>
<p>No release notes provided.</p>
<h2>Update markdownlint version (markdownlint-cli2 v0.13.0, markdownlint
v0.34.0).</h2>
<p>No release notes provided.</p>
<p>Update markdownlint version (markdownlint-cli2 v0.12.1, markdownlint
v0.33.0).</p>
<h2>Update markdownlint version (markdownlint-cli2 v0.11.0, markdownlint
v0.32.1), remove deprecated &quot;command&quot; input.</h2>
<p>No release notes provided.</p>
<h2>Update markdownlint version (markdownlint-cli2 v0.10.0, markdownlint
v0.31.1).</h2>
<p>No release notes provided.</p>
<h2>Update markdownlint version (markdownlint-cli2 v0.9.2, markdownlint
v0.30.0).</h2>
<p>No release notes provided.</p>
<h2>Update markdownlint version (markdownlint-cli2 v0.8.1, markdownlint
v0.29.0), add &quot;config&quot; and &quot;fix&quot; inputs, deprecate
&quot;command&quot; input.</h2>
<p>No release notes provided.</p>
<h2>Update markdownlint version (markdownlint-cli2 v0.7.1, markdownlint
v0.28.2).</h2>
<p>No release notes provided.</p>
<h2>Update markdownlint version (markdownlint-cli2 v0.7.0, markdownlint
v0.28.1), include link to rule information in title of annotations
(clickable in GitHub).</h2>
<p>No release notes provided.</p>
<h2>Update markdownlint version (markdownlint-cli2 v0.6.0, markdownlint
v0.27.0).</h2>
<p>No release notes provided.</p>
<h2>Update markdownlint version (markdownlint-cli2 v0.5.1, markdownlint
v0.26.2).</h2>
<p>No release notes provided.</p>
<h2>Update markdownlint version (markdownlint-cli2 v0.4.0, markdownlint
v0.25.1)</h2>
<p>No release notes provided.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="db43aef879"><code>db43aef</code></a>
Update to version 17.0.0.</li>
<li><a
href="c0decc52d0"><code>c0decc5</code></a>
Bump <code>@​stylistic/eslint-plugin</code> from 2.7.2 to 2.8.0</li>
<li><a
href="dd2171bb17"><code>dd2171b</code></a>
Bump eslint from 9.9.1 to 9.10.0</li>
<li><a
href="85b2286968"><code>85b2286</code></a>
Bump <code>@​eslint/js</code> from 9.9.1 to 9.10.0</li>
<li><a
href="95aa6ed6ed"><code>95aa6ed</code></a>
Freshen generated index.js file.</li>
<li><a
href="476aead54e"><code>476aead</code></a>
Bump markdownlint-cli2 from 0.13.0 to 0.14.0</li>
<li><a
href="da0291d977"><code>da0291d</code></a>
Freshen generated index.js file.</li>
<li><a
href="235535bdb7"><code>235535b</code></a>
Add <code>@​stylistic/eslint-plugin</code> to ESLint configuration.</li>
<li><a
href="20384985f1"><code>2038498</code></a>
Bump eslint from 9.9.0 to 9.9.1</li>
<li><a
href="ea9d2c1e2e"><code>ea9d2c1</code></a>
Bump <code>@​eslint/js</code> from 9.9.0 to 9.9.1</li>
<li>Additional commits viewable in <a
href="https://github.com/davidanson/markdownlint-cli2-action/compare/v16...v17">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=DavidAnson/markdownlint-cli2-action&package-manager=github_actions&previous-version=16&new-version=17)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 09:24:38 -07:00
Josh McKinney
b88717b65f docs(constraint): add note about percentages (#1368)
Co-authored-by: Orhun Parmaksız <orhun@archlinux.org>
2024-09-13 14:42:52 +03:00
Vitalii Kryvenko
5635b930c7 ci: add cargo-machete and remove unused deps (#1362)
https://github.com/bnjbvr/cargo-machete
2024-09-08 18:54:33 -07:00
Hossein Nedaee
870bc6a64a docs: use Frame::area() instead of size() in examples (#1361)
`Frame::size()` is deprecated
2024-09-08 13:20:59 -07:00
FujiApple
da821b431e fix: clippy lints from rust 1.81.0 (#1356) 2024-09-06 22:54:36 -07:00
Patryk Wychowaniec
68886d1787 fix: add unstable-backend-writer feature (#1352)
https://github.com/ratatui/ratatui/pull/991 created a new unstable
feature, but forgot to add it to Cargo.toml, making it impossible to use
on newer versions of rustc - this commit fixes it.
2024-09-06 15:33:14 -07:00
Patryk Wychowaniec
0f48239778 fix(terminal): resize() now resizes fixed viewports (#1353)
`Terminal::resize()` on a fixed viewport used to do nothing due to
an accidentally shadowed variable. This now works as intended.
2024-09-04 12:28:30 -07:00
Valentin271
b13e2f9473 docs(backend): added link to stdio FAQ (#1349) 2024-09-01 15:30:13 -07:00
Orhun Parmaksız
c777beb658 chore(ci): bump git-cliff-action to v4 (#1350)
See: https://github.com/orhun/git-cliff-action/releases/tag/v4.0.0
2024-09-01 16:23:35 +02:00
Mo
20c88aaa5b refactor: Avoid unneeded allocations (#1345) 2024-08-26 13:59:30 -07:00
Orhun Parmaksız
e02947be61 style(example): update panic message in minimal template (#1344) 2024-08-25 04:45:34 -07:00
206 changed files with 2477 additions and 1566 deletions

View File

@@ -42,6 +42,13 @@ jobs:
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v2
cargo-machete:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- uses: bnjbvr/cargo-machete@v0.6.2
clippy:
runs-on: ubuntu-latest
steps:
@@ -57,7 +64,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DavidAnson/markdownlint-cli2-action@v16
- uses: DavidAnson/markdownlint-cli2-action@v17
with:
globs: |
'**/*.md'

View File

@@ -33,7 +33,7 @@ jobs:
run: cargo publish --allow-dirty --token ${{ secrets.CARGO_TOKEN }}
- name: Generate a changelog
uses: orhun/git-cliff-action@v3
uses: orhun/git-cliff-action@v4
with:
config: cliff.toml
args: --unreleased --tag ${{ env.NEXT_TAG }} --strip header

View File

@@ -18,7 +18,7 @@ jobs:
fetch-depth: 0
- name: Generate a changelog
uses: orhun/git-cliff-action@v3
uses: orhun/git-cliff-action@v4
with:
config: cliff.toml
args: --latest --strip header

View File

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

View File

@@ -4,14 +4,18 @@
- [Ratatui](#ratatui)
- [Installation](#installation)
- [Introduction](#introduction)
- [Other Documentation](#other-documentation)
- [Other documentation](#other-documentation)
- [Quickstart](#quickstart)
- [Initialize and restore the terminal](#initialize-and-restore-the-terminal)
- [Drawing the UI](#drawing-the-ui)
- [Handling events](#handling-events)
- [Example](#example)
- [Layout](#layout)
- [Text and styling](#text-and-styling)
- [Status of this fork](#status-of-this-fork)
- [Rust version requirements](#rust-version-requirements)
- [Widgets](#widgets)
- [Built in](#built-in)
- [Third\-party libraries, bootstrapping templates and
widgets](#third-party-libraries-bootstrapping-templates-and-widgets)
- [Third-party libraries, bootstrapping templates and widgets](#third-party-libraries-bootstrapping-templates-and-widgets)
- [Apps](#apps)
- [Alternatives](#alternatives)
- [Acknowledgments](#acknowledgments)
@@ -170,7 +174,7 @@ fn handle_events() -> io::Result<bool> {
fn ui(frame: &mut Frame) {
frame.render_widget(
Paragraph::new("Hello World!").block(Block::bordered().title("Greeting")),
frame.size(),
frame.area(),
);
}
```
@@ -199,7 +203,7 @@ fn ui(frame: &mut Frame) {
Constraint::Min(0),
Constraint::Length(1),
])
.areas(frame.size());
.areas(frame.area());
let [left_area, right_area] =
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
.areas(main_area);
@@ -237,7 +241,7 @@ use ratatui::{
};
fn ui(frame: &mut Frame) {
let areas = Layout::vertical([Constraint::Length(1); 4]).split(frame.size());
let areas = Layout::vertical([Constraint::Length(1); 4]).split(frame.area());
let line = Line::from(vec![
Span::raw("Hello "),

50
ratatui-core/Cargo.toml Normal file
View File

@@ -0,0 +1,50 @@
[package]
name = "ratatui-core"
version = "0.1.0"
edition = "2021"
[features]
underline-color = []
unstable-widget-ref = []
[dependencies]
bitflags.workspace = true
cassowary.workspace = true
compact_str.workspace = true
document-features.workspace = true
instability.workspace = true
itertools.workspace = true
lru.workspace = true
paste.workspace = true
palette = { workspace = true, optional = true }
serde = { workspace = true, optional = true, features = ["derive"] }
strum = { workspace = true, features = ["derive"] }
termwiz = { workspace = true, optional = true }
unicode-segmentation.workspace = true
unicode-truncate.workspace = true
unicode-width.workspace = true
[dev-dependencies]
argh = "0.1.12"
color-eyre = "0.6.2"
criterion = { version = "0.5.1", features = ["html_reports"] }
crossterm = { version = "0.28.1", features = ["event-stream"] }
fakeit = "1.1"
font8x8 = "0.3.1"
futures = "0.3.30"
indoc = "2"
octocrab = "0.40.0"
pretty_assertions = "1.4.0"
rand = "0.8.5"
rand_chacha = "0.3.1"
rstest = "0.22.0"
serde_json = "1.0.109"
tokio = { version = "1.39.2", features = [
"rt",
"macros",
"time",
"rt-multi-thread",
] }
tracing = "0.1.40"
tracing-appender = "0.2.3"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }

View File

@@ -109,21 +109,6 @@ use crate::{
layout::{Position, Size},
};
#[cfg(all(not(windows), feature = "termion"))]
mod termion;
#[cfg(all(not(windows), feature = "termion"))]
pub use self::termion::TermionBackend;
#[cfg(feature = "crossterm")]
mod crossterm;
#[cfg(feature = "crossterm")]
pub use self::crossterm::CrosstermBackend;
#[cfg(feature = "termwiz")]
mod termwiz;
#[cfg(feature = "termwiz")]
pub use self::termwiz::TermwizBackend;
mod test;
pub use self::test::TestBackend;

View File

@@ -1,6 +1,5 @@
use std::fmt;
use itertools::Itertools;
use strum::EnumIs;
/// A constraint that defines the size of a layout element.
@@ -117,8 +116,12 @@ pub enum Constraint {
/// Applies a percentage of the available space to the element
///
/// Converts the given percentage to a floating-point value and multiplies that with area.
/// This value is rounded back to a integer as part of the layout split calculation.
/// Converts the given percentage to a floating-point value and multiplies that with area. This
/// value is rounded back to a integer as part of the layout split calculation.
///
/// **Note**: As this value only accepts a `u16`, certain percentages that cannot be
/// represented exactly (e.g. 1/3) are not possible. You might want to use
/// [`Constraint::Ratio`] or [`Constraint::Fill`] in such cases.
///
/// # Examples
///
@@ -229,7 +232,7 @@ impl Constraint {
where
T: IntoIterator<Item = u16>,
{
lengths.into_iter().map(Self::Length).collect_vec()
lengths.into_iter().map(Self::Length).collect()
}
/// Convert an iterator of ratios into a vector of constraints
@@ -246,10 +249,7 @@ impl Constraint {
where
T: IntoIterator<Item = (u32, u32)>,
{
ratios
.into_iter()
.map(|(n, d)| Self::Ratio(n, d))
.collect_vec()
ratios.into_iter().map(|(n, d)| Self::Ratio(n, d)).collect()
}
/// Convert an iterator of percentages into a vector of constraints
@@ -266,7 +266,7 @@ impl Constraint {
where
T: IntoIterator<Item = u16>,
{
percentages.into_iter().map(Self::Percentage).collect_vec()
percentages.into_iter().map(Self::Percentage).collect()
}
/// Convert an iterator of maxes into a vector of constraints
@@ -283,7 +283,7 @@ impl Constraint {
where
T: IntoIterator<Item = u16>,
{
maxes.into_iter().map(Self::Max).collect_vec()
maxes.into_iter().map(Self::Max).collect()
}
/// Convert an iterator of mins into a vector of constraints
@@ -300,7 +300,7 @@ impl Constraint {
where
T: IntoIterator<Item = u16>,
{
mins.into_iter().map(Self::Min).collect_vec()
mins.into_iter().map(Self::Min).collect()
}
/// Convert an iterator of proportional factors into a vector of constraints
@@ -317,10 +317,7 @@ impl Constraint {
where
T: IntoIterator<Item = u16>,
{
proportional_factors
.into_iter()
.map(Self::Fill)
.collect_vec()
proportional_factors.into_iter().map(Self::Fill).collect()
}
}

View File

@@ -428,7 +428,7 @@ impl Layout {
/// ```rust
/// # use ratatui::prelude::*;
/// # fn render(frame: &mut Frame) {
/// let area = frame.size();
/// let area = frame.area();
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
/// let [top, main] = layout.areas(area);
///
@@ -460,7 +460,7 @@ impl Layout {
/// ```rust
/// # use ratatui::prelude::*;
/// # fn render(frame: &mut Frame) {
/// let area = frame.size();
/// let area = frame.area();
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
/// let [top, main] = layout.areas(area);
/// let [before, inbetween, after] = layout.spacers(area);
@@ -1295,7 +1295,7 @@ mod tests {
use crate::{
layout::flex::Flex,
prelude::{Constraint::*, *},
widgets::Paragraph,
// widgets::Paragraph, // TODO
};
/// Test that the given constraints applied to the given area result in the expected layout.
@@ -1315,9 +1315,9 @@ mod tests {
.flex(flex)
.split(area);
let mut buffer = Buffer::empty(area);
for (i, c) in ('a'..='z').take(constraints.len()).enumerate() {
for (c, &area) in ('a'..='z').take(constraints.len()).zip(layout.iter()) {
let s = c.to_string().repeat(area.width as usize);
Paragraph::new(s).render(layout[i], &mut buffer);
// Paragraph::new(s).render(area, &mut buffer); // TODO
}
assert_eq!(buffer, Buffer::with_lines([expected]));
}

View File

@@ -236,7 +236,7 @@ impl Rect {
/// ```rust
/// # use ratatui::prelude::*;
/// # fn render(frame: &mut Frame) {
/// let area = frame.size();
/// let area = frame.area();
/// let rect = Rect::new(0, 0, 100, 100).clamp(area);
/// # }
/// ```

View File

@@ -151,7 +151,7 @@
//! Constraint::Min(0),
//! Constraint::Length(1),
//! ])
//! .areas(frame.size());
//! .areas(frame.area());
//! let [left_area, right_area] =
//! Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
//! .areas(main_area);
@@ -189,7 +189,7 @@
//! };
//!
//! fn draw(frame: &mut Frame) {
//! let areas = Layout::vertical([Constraint::Length(1); 4]).split(frame.size());
//! let areas = Layout::vertical([Constraint::Length(1); 4]).split(frame.area());
//!
//! let line = Line::from(vec![
//! Span::raw("Hello "),

View File

@@ -0,0 +1,41 @@
//! A prelude for conveniently writing applications using this library.
//!
//! ```rust,no_run
//! use ratatui::prelude::*;
//! ```
//!
//! Aside from the main types that are used in the library, this prelude also re-exports several
//! modules to make it easy to qualify types that would otherwise collide. E.g.:
//!
//! ```rust
//! use ratatui::{prelude::*, widgets::*};
//!
//! #[derive(Debug, Default, PartialEq, Eq)]
//! struct Line;
//!
//! assert_eq!(Line::default(), Line);
//! assert_eq!(text::Line::default(), ratatui::text::Line::from(vec![]));
//! ```
// TODO: re-export the following modules:
// #[cfg(feature = "crossterm")]
// pub use crate::backend::CrosstermBackend;
// #[cfg(all(not(windows), feature = "termion"))]
// pub use crate::backend::TermionBackend;
// #[cfg(feature = "termwiz")]
// pub use crate::backend::TermwizBackend;
pub(crate) use crate::widgets::{StatefulWidgetRef, WidgetRef};
pub use crate::{
backend::{self, Backend},
buffer::{self, Buffer},
layout::{self, Alignment, Constraint, Direction, Layout, Margin, Position, Rect, Size},
style::{self, Color, Modifier, Style, Stylize},
symbols::{self},
text::{self, Line, Masked, Span, Text},
widgets::{
// block::BlockExt, // TODO
StatefulWidget,
Widget,
},
Frame, Terminal,
};

View File

@@ -72,6 +72,7 @@ use std::fmt;
use bitflags::bitflags;
pub use color::{Color, ParseColorError};
use stylize::ColorDebugKind;
pub use stylize::{Styled, Stylize};
mod color;
@@ -223,7 +224,7 @@ impl fmt::Debug for Modifier {
/// buffer[(0, 0)].style(),
/// );
/// ```
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
#[derive(Default, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Style {
pub fg: Option<Color>,
@@ -234,6 +235,55 @@ pub struct Style {
pub sub_modifier: Modifier,
}
/// A custom debug implementation that prints only the fields that are not the default, and unwraps
/// the `Option`s.
impl fmt::Debug for Style {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Style::new()")?;
if let Some(fg) = self.fg {
fg.stylize_debug(ColorDebugKind::Foreground).fmt(f)?;
}
if let Some(bg) = self.bg {
bg.stylize_debug(ColorDebugKind::Background).fmt(f)?;
}
#[cfg(feature = "underline-color")]
if let Some(underline_color) = self.underline_color {
underline_color
.stylize_debug(ColorDebugKind::Underline)
.fmt(f)?;
}
for modifier in self.add_modifier.iter() {
match modifier {
Modifier::BOLD => f.write_str(".bold()")?,
Modifier::DIM => f.write_str(".dim()")?,
Modifier::ITALIC => f.write_str(".italic()")?,
Modifier::UNDERLINED => f.write_str(".underlined()")?,
Modifier::SLOW_BLINK => f.write_str(".slow_blink()")?,
Modifier::RAPID_BLINK => f.write_str(".rapid_blink()")?,
Modifier::REVERSED => f.write_str(".reversed()")?,
Modifier::HIDDEN => f.write_str(".hidden()")?,
Modifier::CROSSED_OUT => f.write_str(".crossed_out()")?,
_ => f.write_fmt(format_args!(".add_modifier(Modifier::{modifier:?})"))?,
}
}
for modifier in self.sub_modifier.iter() {
match modifier {
Modifier::BOLD => f.write_str(".not_bold()")?,
Modifier::DIM => f.write_str(".not_dim()")?,
Modifier::ITALIC => f.write_str(".not_italic()")?,
Modifier::UNDERLINED => f.write_str(".not_underlined()")?,
Modifier::SLOW_BLINK => f.write_str(".not_slow_blink()")?,
Modifier::RAPID_BLINK => f.write_str(".not_rapid_blink()")?,
Modifier::REVERSED => f.write_str(".not_reversed()")?,
Modifier::HIDDEN => f.write_str(".not_hidden()")?,
Modifier::CROSSED_OUT => f.write_str(".not_crossed_out()")?,
_ => f.write_fmt(format_args!(".remove_modifier(Modifier::{modifier:?})"))?,
}
}
Ok(())
}
}
impl Styled for Style {
type Item = Self;
@@ -549,6 +599,16 @@ mod tests {
use super::*;
#[rstest]
#[case(Style::new(), "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()")]
#[case(Style::new().not_italic(), "Style::new().not_italic()")]
fn debug(#[case] style: Style, #[case] expected: &'static str) {
assert_eq!(format!("{style:?}"), expected);
}
#[test]
fn combined_patch_gives_same_result_as_individual_patch() {
let styles = [

View File

@@ -2,6 +2,8 @@
use std::{fmt, str::FromStr};
use crate::style::stylize::{ColorDebug, ColorDebugKind};
/// ANSI Color
///
/// All colors from the [ANSI color table] are supported (though some names are not exactly the
@@ -361,6 +363,10 @@ impl fmt::Display for Color {
}
impl Color {
pub(crate) const fn stylize_debug(self, kind: ColorDebugKind) -> ColorDebug {
ColorDebug { kind, color: self }
}
/// Converts a HSL representation to a `Color::Rgb` instance.
///
/// The `from_hsl` function converts the Hue, Saturation and Lightness values to a

View File

@@ -1,3 +1,5 @@
use std::fmt;
use paste::paste;
use crate::{
@@ -23,6 +25,75 @@ pub trait Styled {
fn set_style<S: Into<Style>>(self, style: S) -> Self::Item;
}
/// A helper struct to make it easy to debug using the `Stylize` method names
pub(crate) struct ColorDebug {
pub kind: ColorDebugKind,
pub color: Color,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub(crate) enum ColorDebugKind {
Foreground,
Background,
#[cfg(feature = "underline-color")]
Underline,
}
impl fmt::Debug for ColorDebug {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "underline-color")]
let is_underline = self.kind == ColorDebugKind::Underline;
#[cfg(not(feature = "underline-color"))]
let is_underline = false;
if is_underline
|| matches!(
self.color,
Color::Reset | Color::Indexed(_) | Color::Rgb(_, _, _)
)
{
match self.kind {
ColorDebugKind::Foreground => write!(f, ".fg(")?,
ColorDebugKind::Background => write!(f, ".bg(")?,
#[cfg(feature = "underline-color")]
ColorDebugKind::Underline => write!(f, ".underline_color(")?,
}
write!(f, "Color::{:?}", self.color)?;
write!(f, ")")?;
return Ok(());
}
match self.kind {
ColorDebugKind::Foreground => write!(f, ".")?,
ColorDebugKind::Background => write!(f, ".on_")?,
// TODO: .underline_color_xxx is not implemented on Stylize yet, but it should be
#[cfg(feature = "underline-color")]
ColorDebugKind::Underline => {
unreachable!("covered by the first part of the if statement")
}
}
match self.color {
Color::Black => write!(f, "black")?,
Color::Red => write!(f, "red")?,
Color::Green => write!(f, "green")?,
Color::Yellow => write!(f, "yellow")?,
Color::Blue => write!(f, "blue")?,
Color::Magenta => write!(f, "magenta")?,
Color::Cyan => write!(f, "cyan")?,
Color::Gray => write!(f, "gray")?,
Color::DarkGray => write!(f, "dark_gray")?,
Color::LightRed => write!(f, "light_red")?,
Color::LightGreen => write!(f, "light_green")?,
Color::LightYellow => write!(f, "light_yellow")?,
Color::LightBlue => write!(f, "light_blue")?,
Color::LightMagenta => write!(f, "light_magenta")?,
Color::LightCyan => write!(f, "light_cyan")?,
Color::White => write!(f, "white")?,
_ => unreachable!("covered by the first part of the if statement"),
}
write!(f, "()")
}
}
/// Generates two methods for each color, one for setting the foreground color (`red()`, `blue()`,
/// etc) and one for setting the background color (`on_red()`, `on_blue()`, etc.). Each method sets
/// the color of the style to the corresponding color.
@@ -231,6 +302,7 @@ impl Styled for String {
#[cfg(test)]
mod tests {
use itertools::Itertools;
use rstest::rstest;
use super::*;
@@ -423,4 +495,82 @@ mod tests {
Span::styled("hello", all_modifier_black)
);
}
#[rstest]
#[case(ColorDebugKind::Foreground, Color::Black, ".black()")]
#[case(ColorDebugKind::Foreground, Color::Red, ".red()")]
#[case(ColorDebugKind::Foreground, Color::Green, ".green()")]
#[case(ColorDebugKind::Foreground, Color::Yellow, ".yellow()")]
#[case(ColorDebugKind::Foreground, Color::Blue, ".blue()")]
#[case(ColorDebugKind::Foreground, Color::Magenta, ".magenta()")]
#[case(ColorDebugKind::Foreground, Color::Cyan, ".cyan()")]
#[case(ColorDebugKind::Foreground, Color::Gray, ".gray()")]
#[case(ColorDebugKind::Foreground, Color::DarkGray, ".dark_gray()")]
#[case(ColorDebugKind::Foreground, Color::LightRed, ".light_red()")]
#[case(ColorDebugKind::Foreground, Color::LightGreen, ".light_green()")]
#[case(ColorDebugKind::Foreground, Color::LightYellow, ".light_yellow()")]
#[case(ColorDebugKind::Foreground, Color::LightBlue, ".light_blue()")]
#[case(ColorDebugKind::Foreground, Color::LightMagenta, ".light_magenta()")]
#[case(ColorDebugKind::Foreground, Color::LightCyan, ".light_cyan()")]
#[case(ColorDebugKind::Foreground, Color::White, ".white()")]
#[case(
ColorDebugKind::Foreground,
Color::Indexed(10),
".fg(Color::Indexed(10))"
)]
#[case(
ColorDebugKind::Foreground,
Color::Rgb(255, 0, 0),
".fg(Color::Rgb(255, 0, 0))"
)]
#[case(ColorDebugKind::Background, Color::Black, ".on_black()")]
#[case(ColorDebugKind::Background, Color::Red, ".on_red()")]
#[case(ColorDebugKind::Background, Color::Green, ".on_green()")]
#[case(ColorDebugKind::Background, Color::Yellow, ".on_yellow()")]
#[case(ColorDebugKind::Background, Color::Blue, ".on_blue()")]
#[case(ColorDebugKind::Background, Color::Magenta, ".on_magenta()")]
#[case(ColorDebugKind::Background, Color::Cyan, ".on_cyan()")]
#[case(ColorDebugKind::Background, Color::Gray, ".on_gray()")]
#[case(ColorDebugKind::Background, Color::DarkGray, ".on_dark_gray()")]
#[case(ColorDebugKind::Background, Color::LightRed, ".on_light_red()")]
#[case(ColorDebugKind::Background, Color::LightGreen, ".on_light_green()")]
#[case(ColorDebugKind::Background, Color::LightYellow, ".on_light_yellow()")]
#[case(ColorDebugKind::Background, Color::LightBlue, ".on_light_blue()")]
#[case(ColorDebugKind::Background, Color::LightMagenta, ".on_light_magenta()")]
#[case(ColorDebugKind::Background, Color::LightCyan, ".on_light_cyan()")]
#[case(ColorDebugKind::Background, Color::White, ".on_white()")]
#[case(
ColorDebugKind::Background,
Color::Indexed(10),
".bg(Color::Indexed(10))"
)]
#[case(
ColorDebugKind::Background,
Color::Rgb(255, 0, 0),
".bg(Color::Rgb(255, 0, 0))"
)]
#[cfg(feature = "underline-color")]
#[case(
ColorDebugKind::Underline,
Color::Black,
".underline_color(Color::Black)"
)]
#[cfg(feature = "underline-color")]
#[case(ColorDebugKind::Underline, Color::Red, ".underline_color(Color::Red)")]
#[cfg(feature = "underline-color")]
#[case(
ColorDebugKind::Underline,
Color::Green,
".underline_color(Color::Green)"
)]
#[cfg(feature = "underline-color")]
#[case(
ColorDebugKind::Underline,
Color::Yellow,
".underline_color(Color::Yellow)"
)]
fn stylize_debug(#[case] kind: ColorDebugKind, #[case] color: Color, #[case] expected: &str) {
let debug = color.stylize_debug(kind);
assert_eq!(format!("{debug:?}"), expected);
}
}

View File

@@ -18,7 +18,7 @@
//! let backend = CrosstermBackend::new(stdout());
//! let mut terminal = Terminal::new(backend)?;
//! terminal.draw(|frame| {
//! let area = frame.size();
//! let area = frame.area();
//! frame.render_widget(Paragraph::new("Hello world!"), area);
//! })?;
//! # std::io::Result::Ok(())
@@ -32,15 +32,9 @@
//! [`Buffer`]: crate::buffer::Buffer
mod frame;
#[cfg(feature = "crossterm")]
mod init;
mod terminal;
mod viewport;
pub use frame::{CompletedFrame, Frame};
#[cfg(feature = "crossterm")]
pub use init::{
init, init_with_options, restore, try_init, try_init_with_options, try_restore, DefaultTerminal,
};
pub use terminal::{Options as TerminalOptions, Terminal};
pub use viewport::Viewport;

View File

@@ -38,7 +38,7 @@ use crate::{
/// let backend = CrosstermBackend::new(stdout());
/// let mut terminal = Terminal::new(backend)?;
/// terminal.draw(|frame| {
/// let area = frame.size();
/// let area = frame.area();
/// frame.render_widget(Paragraph::new("Hello World!"), area);
/// })?;
/// # std::io::Result::Ok(())
@@ -205,7 +205,6 @@ where
/// of the screen.
pub fn resize(&mut self, area: Rect) -> io::Result<()> {
let next_area = match self.viewport {
Viewport::Fullscreen => area,
Viewport::Inline(height) => {
let offset_in_previous_viewport = self
.last_known_cursor_pos
@@ -219,7 +218,7 @@ where
)?
.0
}
Viewport::Fixed(area) => area,
Viewport::Fixed(_) | Viewport::Fullscreen => area,
};
self.set_viewport_area(next_area);
self.clear()?;
@@ -284,7 +283,7 @@ where
///
/// // with a closure
/// terminal.draw(|frame| {
/// let area = frame.size();
/// let area = frame.area();
/// frame.render_widget(Paragraph::new("Hello World!"), area);
/// frame.set_cursor_position(Position { x: 0, y: 0 });
/// })?;
@@ -293,7 +292,7 @@ where
/// terminal.draw(render)?;
///
/// fn render(frame: &mut ratatui::Frame) {
/// frame.render_widget(Paragraph::new("Hello World!"), frame.size());
/// frame.render_widget(Paragraph::new("Hello World!"), frame.area());
/// }
/// # std::io::Result::Ok(())
/// ```
@@ -356,7 +355,7 @@ where
/// // with a closure
/// terminal.try_draw(|frame| {
/// let value: u8 = "not a number".parse().map_err(io::Error::other)?;
/// let area = frame.size();
/// let area = frame.area();
/// frame.render_widget(Paragraph::new("Hello World!"), area);
/// frame.set_cursor_position(Position { x: 0, y: 0 });
/// io::Result::Ok(())
@@ -367,7 +366,7 @@ where
///
/// fn render(frame: &mut ratatui::Frame) -> io::Result<()> {
/// let value: u8 = "not a number".parse().map_err(io::Error::other)?;
/// frame.render_widget(Paragraph::new("Hello World!"), frame.size());
/// frame.render_widget(Paragraph::new("Hello World!"), frame.area());
/// Ok(())
/// }
/// # io::Result::Ok(())

View File

@@ -26,7 +26,7 @@ impl<'a> StyledGrapheme<'a> {
}
}
pub(crate) fn is_whitespace(&self) -> bool {
pub fn is_whitespace(&self) -> bool {
let symbol = self.symbol;
symbol == ZWSP || symbol.chars().all(char::is_whitespace) && symbol != NBSP
}

View File

@@ -149,16 +149,33 @@ use crate::{prelude::*, style::Styled, text::StyledGrapheme};
/// ```
///
/// [`Paragraph`]: crate::widgets::Paragraph
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
#[derive(Default, Clone, Eq, PartialEq, Hash)]
pub struct Line<'a> {
/// The spans that make up this line of text.
pub spans: Vec<Span<'a>>,
/// The style of this line of text.
pub style: Style,
/// The alignment of this line of text.
pub alignment: Option<Alignment>,
/// The spans that make up this line of text.
pub spans: Vec<Span<'a>>,
}
impl fmt::Debug for Line<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.style == Style::default() && self.alignment.is_none() {
f.write_str("Line ")?;
return f.debug_list().entries(&self.spans).finish();
}
let mut debug = f.debug_struct("Line");
if self.style != Style::default() {
debug.field("style", &self.style);
}
if let Some(alignment) = self.alignment {
debug.field("alignment", &format!("Alignment::{alignment}"));
}
debug.field("spans", &self.spans).finish()
}
}
fn cow_to_spans<'a>(content: impl Into<Cow<'a, str>>) -> Vec<Span<'a>> {

View File

@@ -81,19 +81,31 @@ use crate::{prelude::*, style::Styled, text::StyledGrapheme};
/// use ratatui::prelude::*;
///
/// # fn render_frame(frame: &mut Frame) {
/// frame.render_widget("test content".green().on_yellow().italic(), frame.size());
/// frame.render_widget("test content".green().on_yellow().italic(), frame.area());
/// # }
/// ```
/// [`Line`]: crate::text::Line
/// [`Paragraph`]: crate::widgets::Paragraph
/// [`Stylize`]: crate::style::Stylize
/// [`Cow<str>`]: std::borrow::Cow
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
#[derive(Default, Clone, Eq, PartialEq, Hash)]
pub struct Span<'a> {
/// The content of the span as a Clone-on-write string.
pub content: Cow<'a, str>,
/// The style of the span.
pub style: Style,
/// The content of the span as a Clone-on-write string.
pub content: Cow<'a, str>,
}
impl fmt::Debug for Span<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.style == Style::default() {
return write!(f, "Span({:?})", self.content);
}
f.debug_struct("Span")
.field("style", &self.style)
.field("content", &self.content)
.finish()
}
}
impl<'a> Span<'a> {

View File

@@ -1,8 +1,6 @@
#![warn(missing_docs)]
use std::{borrow::Cow, fmt};
use itertools::{Itertools, Position};
use crate::{prelude::*, style::Styled};
/// A string split over one or more lines.
@@ -165,14 +163,32 @@ use crate::{prelude::*, style::Styled};
/// ```
///
/// [`Paragraph`]: crate::widgets::Paragraph
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
#[derive(Default, Clone, Eq, PartialEq, Hash)]
pub struct Text<'a> {
/// The lines that make up this piece of text.
pub lines: Vec<Line<'a>>,
/// The style of this text.
pub style: Style,
/// The alignment of this text.
pub alignment: Option<Alignment>,
/// The style of this text.
pub style: Style,
/// The lines that make up this piece of text.
pub lines: Vec<Line<'a>>,
}
impl fmt::Debug for Text<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.style == Style::default() && self.alignment.is_none() {
f.write_str("Text ")?;
f.debug_list().entries(&self.lines).finish()
} else {
let mut debug = f.debug_struct("Text");
if self.style != Style::default() {
debug.field("style", &self.style);
}
if let Some(alignment) = self.alignment {
debug.field("alignment", &format!("Alignment::{alignment}"));
}
debug.field("lines", &self.lines).finish()
}
}
}
impl<'a> Text<'a> {
@@ -632,12 +648,11 @@ impl<T: fmt::Display> ToText for T {
impl fmt::Display for Text<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (position, line) in self.iter().with_position() {
if position == Position::Last || position == Position::Only {
write!(f, "{line}")?;
} else {
if let Some((last, rest)) = self.lines.split_last() {
for line in rest {
writeln!(f, "{line}")?;
}
write!(f, "{last}")?;
}
Ok(())
}
@@ -1235,4 +1250,46 @@ mod tests {
assert_eq!(result, "Hello world!");
}
}
mod debug {
use super::*;
#[test]
#[ignore = "This is just showing the debug output of the assertions"]
fn no_style() {
let text = Text::from("single unstyled line");
assert_eq!(text, Text::default());
}
#[test]
#[ignore = "This is just showing the debug output of the assertions"]
fn text_style() {
let text = Text::from("single styled line")
.red()
.on_black()
.bold()
.not_italic();
assert_eq!(text, Text::default());
}
#[test]
#[ignore = "This is just showing the debug output of the assertions"]
fn line_style() {
let text = Text::from(vec![
Line::from("first line").red().alignment(Alignment::Right),
Line::from("second line").on_black(),
]);
assert_eq!(text, Text::default());
}
#[test]
#[ignore = "This is just showing the debug output of the assertions"]
fn span_style() {
let text = Text::from(Line::from(vec![
Span::from("first span").red(),
Span::from("second span").on_black(),
]));
assert_eq!(text, Text::default());
}
}
}

View File

@@ -21,37 +21,7 @@
//! - [`Tabs`]: displays a tab bar and allows selection.
//!
//! [`Canvas`]: crate::widgets::canvas::Canvas
mod barchart;
pub mod block;
mod borders;
#[cfg(feature = "widget-calendar")]
pub mod calendar;
pub mod canvas;
mod chart;
mod clear;
mod gauge;
mod list;
mod paragraph;
mod reflow;
mod scrollbar;
mod sparkline;
mod table;
mod tabs;
pub use self::{
barchart::{Bar, BarChart, BarGroup},
block::{Block, BorderType, Padding},
borders::*,
chart::{Axis, Chart, Dataset, GraphType, LegendPosition},
clear::Clear,
gauge::{Gauge, LineGauge},
list::{List, ListDirection, ListItem, ListState},
paragraph::{Paragraph, Wrap},
scrollbar::{ScrollDirection, Scrollbar, ScrollbarOrientation, ScrollbarState},
sparkline::{RenderDirection, Sparkline},
table::{Cell, HighlightSpacing, Row, Table, TableState},
tabs::Tabs,
};
use crate::{buffer::Buffer, layout::Rect, style::Style};
/// A `Widget` is a type that can be drawn on a [`Buffer`] in a given [`Rect`].
@@ -88,7 +58,7 @@ use crate::{buffer::Buffer, layout::Rect, style::Style};
/// # let mut terminal = Terminal::new(backend).unwrap();
///
/// terminal.draw(|frame| {
/// frame.render_widget(Clear, frame.size());
/// frame.render_widget(Clear, frame.area());
/// });
/// ```
///

View File

@@ -0,0 +1,20 @@
[package]
name = "ratatui-crossterm"
version = "0.1.0"
edition = "2021"
[features]
default = ["underline-color"]
## enables the backend code that sets the underline color.
## Underline color is only supported by the [`CrosstermBackend`](backend::CrosstermBackend) backend,
## and is not supported on Windows 7.
underline-color = []
[dependencies]
ratatui-core = { workspace = true }
crossterm.workspace = true
instability.workspace = true
[dev-dependencies]
rstest.workspace = true

View File

@@ -0,0 +1,681 @@
//! This module provides the [`CrosstermBackend`] implementation for the [`Backend`] trait. It uses
//! the [Crossterm] crate to interact with the terminal.
//!
//! [Crossterm]: https://crates.io/crates/crossterm
use std::io::{self, Write};
#[cfg(feature = "underline-color")]
use crossterm::style::SetUnderlineColor;
use crossterm::{
cursor::{Hide, MoveTo, Show},
execute, queue,
style::{
Attribute as CrosstermAttribute, Attributes as CrosstermAttributes,
Color as CrosstermColor, Colors, ContentStyle, Print, SetAttribute, SetBackgroundColor,
SetColors, SetForegroundColor,
},
terminal::{self, Clear},
};
use ratatui_core::{
backend::{Backend, ClearType, WindowSize},
buffer::Cell,
layout::{Position, Size},
style::{Color, Modifier, Style},
};
/// A [`Backend`] implementation that uses [Crossterm] to render to the terminal.
///
/// The `CrosstermBackend` struct is a wrapper around a writer implementing [`Write`], which is
/// used to send commands to the terminal. It provides methods for drawing content, manipulating
/// the cursor, and clearing the terminal screen.
///
/// Most applications should not call the methods on `CrosstermBackend` directly, but will instead
/// use the [`Terminal`] struct, which provides a more ergonomic interface.
///
/// Usually applications will enable raw mode and switch to alternate screen mode after creating
/// a `CrosstermBackend`. This is done by calling [`crossterm::terminal::enable_raw_mode`] and
/// [`crossterm::terminal::EnterAlternateScreen`] (and the corresponding disable/leave functions
/// when the application exits). This is not done automatically by the backend because it is
/// possible that the application may want to use the terminal for other purposes (like showing
/// help text) before entering alternate screen mode.
///
/// # Example
///
/// ```rust,no_run
/// use std::io::{stderr, stdout};
///
/// use ratatui::{
/// crossterm::{
/// terminal::{
/// disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
/// },
/// ExecutableCommand,
/// },
/// prelude::*,
/// };
///
/// let mut backend = CrosstermBackend::new(stdout());
/// // or
/// let backend = CrosstermBackend::new(stderr());
/// let mut terminal = Terminal::new(backend)?;
///
/// enable_raw_mode()?;
/// stdout().execute(EnterAlternateScreen)?;
///
/// terminal.clear()?;
/// terminal.draw(|frame| {
/// // -- snip --
/// })?;
///
/// stdout().execute(LeaveAlternateScreen)?;
/// disable_raw_mode()?;
///
/// # std::io::Result::Ok(())
/// ```
///
/// See the the [Examples] directory for more examples. See the [`backend`] module documentation
/// for more details on raw mode and alternate screen.
///
/// [`Write`]: std::io::Write
/// [`Terminal`]: crate::terminal::Terminal
/// [`backend`]: crate::backend
/// [Crossterm]: https://crates.io/crates/crossterm
/// [Examples]: https://github.com/ratatui/ratatui/tree/main/examples/README.md
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct CrosstermBackend<W: Write> {
/// The writer used to send commands to the terminal.
writer: W,
}
impl<W> CrosstermBackend<W>
where
W: Write,
{
/// Creates a new `CrosstermBackend` with the given writer.
///
/// Most applications will use either [`stdout`](std::io::stdout) or
/// [`stderr`](std::io::stderr) as writer. See the [FAQ] to determine which one to use.
///
/// [FAQ]: https://ratatui.rs/faq/#should-i-use-stdout-or-stderr
///
/// # Example
///
/// ```rust,no_run
/// # use std::io::stdout;
/// # use ratatui::prelude::*;
/// let backend = CrosstermBackend::new(stdout());
/// ```
pub const fn new(writer: W) -> Self {
Self { writer }
}
/// Gets the writer.
#[instability::unstable(
feature = "backend-writer",
issue = "https://github.com/ratatui/ratatui/pull/991"
)]
pub const fn writer(&self) -> &W {
&self.writer
}
/// Gets the writer as a mutable reference.
///
/// Note: writing to the writer may cause incorrect output after the write. This is due to the
/// way that the Terminal implements diffing Buffers.
#[instability::unstable(
feature = "backend-writer",
issue = "https://github.com/ratatui/ratatui/pull/991"
)]
pub fn writer_mut(&mut self) -> &mut W {
&mut self.writer
}
}
impl<W> Write for CrosstermBackend<W>
where
W: Write,
{
/// Writes a buffer of bytes to the underlying buffer.
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.writer.write(buf)
}
/// Flushes the underlying buffer.
fn flush(&mut self) -> io::Result<()> {
self.writer.flush()
}
}
impl<W> Backend for CrosstermBackend<W>
where
W: Write,
{
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
where
I: Iterator<Item = (u16, u16, &'a Cell)>,
{
let mut fg = Color::Reset;
let mut bg = Color::Reset;
#[cfg(feature = "underline-color")]
let mut underline_color = Color::Reset;
let mut modifier = Modifier::empty();
let mut last_pos: Option<Position> = None;
for (x, y, cell) in content {
// Move the cursor if the previous location was not (x - 1, y)
if !matches!(last_pos, Some(p) if x == p.x + 1 && y == p.y) {
queue!(self.writer, MoveTo(x, y))?;
}
last_pos = Some(Position { x, y });
if cell.modifier != modifier {
let diff = ModifierDiff {
from: modifier,
to: cell.modifier,
};
diff.queue(&mut self.writer)?;
modifier = cell.modifier;
}
if cell.fg != fg || cell.bg != bg {
queue!(
self.writer,
SetColors(Colors::new(
from_ratatui_color(cell.fg),
from_ratatui_color(cell.bg)
))
)?;
fg = cell.fg;
bg = cell.bg;
}
#[cfg(feature = "underline-color")]
if cell.underline_color != underline_color {
let color = from_ratatui_color(cell.underline_color);
queue!(self.writer, SetUnderlineColor(color))?;
underline_color = cell.underline_color;
}
queue!(self.writer, Print(cell.symbol()))?;
}
#[cfg(feature = "underline-color")]
return queue!(
self.writer,
SetForegroundColor(CrosstermColor::Reset),
SetBackgroundColor(CrosstermColor::Reset),
SetUnderlineColor(CrosstermColor::Reset),
SetAttribute(CrosstermAttribute::Reset),
);
#[cfg(not(feature = "underline-color"))]
return queue!(
self.writer,
SetForegroundColor(CrosstermColor::Reset),
SetBackgroundColor(CrosstermColor::Reset),
SetAttribute(CrosstermAttribute::Reset),
);
}
fn hide_cursor(&mut self) -> io::Result<()> {
execute!(self.writer, Hide)
}
fn show_cursor(&mut self) -> io::Result<()> {
execute!(self.writer, Show)
}
fn get_cursor_position(&mut self) -> io::Result<Position> {
crossterm::cursor::position()
.map(|(x, y)| Position { x, y })
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
}
fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> {
let Position { x, y } = position.into();
execute!(self.writer, MoveTo(x, y))
}
fn clear(&mut self) -> io::Result<()> {
self.clear_region(ClearType::All)
}
fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
execute!(
self.writer,
Clear(match clear_type {
ClearType::All => crossterm::terminal::ClearType::All,
ClearType::AfterCursor => crossterm::terminal::ClearType::FromCursorDown,
ClearType::BeforeCursor => crossterm::terminal::ClearType::FromCursorUp,
ClearType::CurrentLine => crossterm::terminal::ClearType::CurrentLine,
ClearType::UntilNewLine => crossterm::terminal::ClearType::UntilNewLine,
})
)
}
fn append_lines(&mut self, n: u16) -> io::Result<()> {
for _ in 0..n {
queue!(self.writer, Print("\n"))?;
}
self.writer.flush()
}
fn size(&self) -> io::Result<Size> {
let (width, height) = terminal::size()?;
Ok(Size { width, height })
}
fn window_size(&mut self) -> io::Result<WindowSize> {
let crossterm::terminal::WindowSize {
columns,
rows,
width,
height,
} = terminal::window_size()?;
Ok(WindowSize {
columns_rows: Size {
width: columns,
height: rows,
},
pixels: Size { width, height },
})
}
fn flush(&mut self) -> io::Result<()> {
self.writer.flush()
}
}
fn from_ratatui_color(color: Color) -> CrosstermColor {
match color {
Color::Reset => CrosstermColor::Reset,
Color::Black => CrosstermColor::Black,
Color::Red => CrosstermColor::DarkRed,
Color::Green => CrosstermColor::DarkGreen,
Color::Yellow => CrosstermColor::DarkYellow,
Color::Blue => CrosstermColor::DarkBlue,
Color::Magenta => CrosstermColor::DarkMagenta,
Color::Cyan => CrosstermColor::DarkCyan,
Color::Gray => CrosstermColor::Grey,
Color::DarkGray => CrosstermColor::DarkGrey,
Color::LightRed => CrosstermColor::Red,
Color::LightGreen => CrosstermColor::Green,
Color::LightBlue => CrosstermColor::Blue,
Color::LightYellow => CrosstermColor::Yellow,
Color::LightMagenta => CrosstermColor::Magenta,
Color::LightCyan => CrosstermColor::Cyan,
Color::White => CrosstermColor::White,
Color::Indexed(i) => CrosstermColor::AnsiValue(i),
Color::Rgb(r, g, b) => CrosstermColor::Rgb { r, g, b },
}
}
fn from_crossterm_color(value: CrosstermColor) -> Color {
match value {
CrosstermColor::Reset => Color::Reset,
CrosstermColor::Black => Color::Black,
CrosstermColor::DarkRed => Color::Red,
CrosstermColor::DarkGreen => Color::Green,
CrosstermColor::DarkYellow => Color::Yellow,
CrosstermColor::DarkBlue => Color::Blue,
CrosstermColor::DarkMagenta => Color::Magenta,
CrosstermColor::DarkCyan => Color::Cyan,
CrosstermColor::Grey => Color::Gray,
CrosstermColor::DarkGrey => Color::DarkGray,
CrosstermColor::Red => Color::LightRed,
CrosstermColor::Green => Color::LightGreen,
CrosstermColor::Blue => Color::LightBlue,
CrosstermColor::Yellow => Color::LightYellow,
CrosstermColor::Magenta => Color::LightMagenta,
CrosstermColor::Cyan => Color::LightCyan,
CrosstermColor::White => Color::White,
CrosstermColor::Rgb { r, g, b } => Color::Rgb(r, g, b),
CrosstermColor::AnsiValue(v) => Color::Indexed(v),
}
}
/// The `ModifierDiff` struct is used to calculate the difference between two `Modifier`
/// values. This is useful when updating the terminal display, as it allows for more
/// efficient updates by only sending the necessary changes.
struct ModifierDiff {
pub from: Modifier,
pub to: Modifier,
}
impl ModifierDiff {
fn queue<W>(self, mut w: W) -> io::Result<()>
where
W: io::Write,
{
//use crossterm::Attribute;
let removed = self.from - self.to;
if removed.contains(Modifier::REVERSED) {
queue!(w, SetAttribute(CrosstermAttribute::NoReverse))?;
}
if removed.contains(Modifier::BOLD) {
queue!(w, SetAttribute(CrosstermAttribute::NormalIntensity))?;
if self.to.contains(Modifier::DIM) {
queue!(w, SetAttribute(CrosstermAttribute::Dim))?;
}
}
if removed.contains(Modifier::ITALIC) {
queue!(w, SetAttribute(CrosstermAttribute::NoItalic))?;
}
if removed.contains(Modifier::UNDERLINED) {
queue!(w, SetAttribute(CrosstermAttribute::NoUnderline))?;
}
if removed.contains(Modifier::DIM) {
queue!(w, SetAttribute(CrosstermAttribute::NormalIntensity))?;
}
if removed.contains(Modifier::CROSSED_OUT) {
queue!(w, SetAttribute(CrosstermAttribute::NotCrossedOut))?;
}
if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
queue!(w, SetAttribute(CrosstermAttribute::NoBlink))?;
}
let added = self.to - self.from;
if added.contains(Modifier::REVERSED) {
queue!(w, SetAttribute(CrosstermAttribute::Reverse))?;
}
if added.contains(Modifier::BOLD) {
queue!(w, SetAttribute(CrosstermAttribute::Bold))?;
}
if added.contains(Modifier::ITALIC) {
queue!(w, SetAttribute(CrosstermAttribute::Italic))?;
}
if added.contains(Modifier::UNDERLINED) {
queue!(w, SetAttribute(CrosstermAttribute::Underlined))?;
}
if added.contains(Modifier::DIM) {
queue!(w, SetAttribute(CrosstermAttribute::Dim))?;
}
if added.contains(Modifier::CROSSED_OUT) {
queue!(w, SetAttribute(CrosstermAttribute::CrossedOut))?;
}
if added.contains(Modifier::SLOW_BLINK) {
queue!(w, SetAttribute(CrosstermAttribute::SlowBlink))?;
}
if added.contains(Modifier::RAPID_BLINK) {
queue!(w, SetAttribute(CrosstermAttribute::RapidBlink))?;
}
Ok(())
}
}
fn from_crossterm_attribute(value: CrosstermAttribute) -> Modifier {
// `Attribute*s*` (note the *s*) contains multiple `Attribute`
// We convert `Attribute` to `Attribute*s*` (containing only 1 value) to avoid implementing
// the conversion again
from_crossterm_attributes(CrosstermAttributes::from(value))
}
fn from_crossterm_attributes(value: CrosstermAttributes) -> Modifier {
let mut res = Modifier::empty();
if value.has(CrosstermAttribute::Bold) {
res |= Modifier::BOLD;
}
if value.has(CrosstermAttribute::Dim) {
res |= Modifier::DIM;
}
if value.has(CrosstermAttribute::Italic) {
res |= Modifier::ITALIC;
}
if value.has(CrosstermAttribute::Underlined)
|| value.has(CrosstermAttribute::DoubleUnderlined)
|| value.has(CrosstermAttribute::Undercurled)
|| value.has(CrosstermAttribute::Underdotted)
|| value.has(CrosstermAttribute::Underdashed)
{
res |= Modifier::UNDERLINED;
}
if value.has(CrosstermAttribute::SlowBlink) {
res |= Modifier::SLOW_BLINK;
}
if value.has(CrosstermAttribute::RapidBlink) {
res |= Modifier::RAPID_BLINK;
}
if value.has(CrosstermAttribute::Reverse) {
res |= Modifier::REVERSED;
}
if value.has(CrosstermAttribute::Hidden) {
res |= Modifier::HIDDEN;
}
if value.has(CrosstermAttribute::CrossedOut) {
res |= Modifier::CROSSED_OUT;
}
res
}
fn from_crossterm_style(value: ContentStyle) -> Style {
let mut sub_modifier = Modifier::empty();
if value.attributes.has(CrosstermAttribute::NoBold) {
sub_modifier |= Modifier::BOLD;
}
if value.attributes.has(CrosstermAttribute::NoItalic) {
sub_modifier |= Modifier::ITALIC;
}
if value.attributes.has(CrosstermAttribute::NotCrossedOut) {
sub_modifier |= Modifier::CROSSED_OUT;
}
if value.attributes.has(CrosstermAttribute::NoUnderline) {
sub_modifier |= Modifier::UNDERLINED;
}
if value.attributes.has(CrosstermAttribute::NoHidden) {
sub_modifier |= Modifier::HIDDEN;
}
if value.attributes.has(CrosstermAttribute::NoBlink) {
sub_modifier |= Modifier::RAPID_BLINK | Modifier::SLOW_BLINK;
}
if value.attributes.has(CrosstermAttribute::NoReverse) {
sub_modifier |= Modifier::REVERSED;
}
Style {
fg: value.foreground_color.map(from_crossterm_color),
bg: value.background_color.map(from_crossterm_color),
#[cfg(feature = "underline-color")]
underline_color: value.underline_color.map(from_crossterm_color),
add_modifier: from_crossterm_attributes(value.attributes),
sub_modifier,
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case(CrosstermColor::Reset, Color::Reset)]
#[case(CrosstermColor::Black, Color::Black)]
#[case(CrosstermColor::DarkGrey, Color::DarkGray)]
#[case(CrosstermColor::Red, Color::LightRed)]
#[case(CrosstermColor::DarkRed, Color::Red)]
#[case(CrosstermColor::Green, Color::LightGreen)]
#[case(CrosstermColor::DarkGreen, Color::Green)]
#[case(CrosstermColor::Yellow, Color::LightYellow)]
#[case(CrosstermColor::DarkYellow, Color::Yellow)]
#[case(CrosstermColor::Blue, Color::LightBlue)]
#[case(CrosstermColor::DarkBlue, Color::Blue)]
#[case(CrosstermColor::Magenta, Color::LightMagenta)]
#[case(CrosstermColor::DarkMagenta, Color::Magenta)]
#[case(CrosstermColor::Cyan, Color::LightCyan)]
#[case(CrosstermColor::DarkCyan, Color::Cyan)]
#[case(CrosstermColor::White, Color::White)]
#[case(CrosstermColor::Grey, Color::Gray)]
#[case(CrosstermColor::Rgb { r: 0, g: 0, b: 0 }, Color::Rgb(0, 0, 0))]
#[case(CrosstermColor::Rgb { r: 10, g: 20, b: 30 }, Color::Rgb(10, 20, 30))]
#[case(CrosstermColor::AnsiValue(32), Color::Indexed(32))]
#[case(CrosstermColor::AnsiValue(37), Color::Indexed(37))]
fn convert_from_crossterm_color(#[case] value: CrosstermColor, #[case] expected: Color) {
assert_eq!(from_crossterm_color(value), expected);
}
mod modifier {
use super::*;
#[rstest]
#[rstest]
#[case(CrosstermAttribute::Reset, Modifier::empty())]
#[case(CrosstermAttribute::Bold, Modifier::BOLD)]
#[case(CrosstermAttribute::Italic, Modifier::ITALIC)]
#[case(CrosstermAttribute::Underlined, Modifier::UNDERLINED)]
#[case(CrosstermAttribute::DoubleUnderlined, Modifier::UNDERLINED)]
#[case(CrosstermAttribute::Underdotted, Modifier::UNDERLINED)]
#[case(CrosstermAttribute::Dim, Modifier::DIM)]
#[case(CrosstermAttribute::NormalIntensity, Modifier::empty())]
#[case(CrosstermAttribute::CrossedOut, Modifier::CROSSED_OUT)]
#[case(CrosstermAttribute::NoUnderline, Modifier::empty())]
#[case(CrosstermAttribute::OverLined, Modifier::empty())]
#[case(CrosstermAttribute::SlowBlink, Modifier::SLOW_BLINK)]
#[case(CrosstermAttribute::RapidBlink, Modifier::RAPID_BLINK)]
#[case(CrosstermAttribute::Hidden, Modifier::HIDDEN)]
#[case(CrosstermAttribute::NoHidden, Modifier::empty())]
#[case(CrosstermAttribute::Reverse, Modifier::REVERSED)]
fn convert_from_crossterm_attribute(
#[case] value: CrosstermAttribute,
#[case] expected: Modifier,
) {
assert_eq!(from_crossterm_attribute(value), expected);
}
#[rstest]
#[case(&[CrosstermAttribute::Bold], Modifier::BOLD)]
#[case(
&[CrosstermAttribute::Bold, CrosstermAttribute::Italic],
Modifier::BOLD | Modifier::ITALIC
)]
#[case(
&[CrosstermAttribute::Bold, CrosstermAttribute::NotCrossedOut],
Modifier::BOLD
)]
#[case(
&[CrosstermAttribute::Dim, CrosstermAttribute::Underdotted],
Modifier::DIM | Modifier::UNDERLINED
)]
#[case(
&[CrosstermAttribute::Dim, CrosstermAttribute::SlowBlink, CrosstermAttribute::Italic],
Modifier::DIM | Modifier::SLOW_BLINK | Modifier::ITALIC
)]
#[case(
&[CrosstermAttribute::Hidden, CrosstermAttribute::NoUnderline, CrosstermAttribute::NotCrossedOut],
Modifier::HIDDEN
)]
#[case(
&[CrosstermAttribute::Reverse],
Modifier::REVERSED
)]
#[case(
&[CrosstermAttribute::Reset],
Modifier::empty()
)]
#[case(
&[CrosstermAttribute::RapidBlink, CrosstermAttribute::CrossedOut],
Modifier::RAPID_BLINK | Modifier::CROSSED_OUT
)]
#[case(
&[CrosstermAttribute::DoubleUnderlined, CrosstermAttribute::OverLined],
Modifier::UNDERLINED
)]
#[case(
&[CrosstermAttribute::Undercurled, CrosstermAttribute::Underdashed],
Modifier::UNDERLINED
)]
#[case(
&[CrosstermAttribute::NoBold, CrosstermAttribute::NoItalic],
Modifier::empty()
)]
#[case(
&[CrosstermAttribute::NoBlink, CrosstermAttribute::NoReverse],
Modifier::empty()
)]
fn convert_from_crossterm_attributes(
#[case] value: &[CrosstermAttribute],
#[case] expected: Modifier,
) {
assert_eq!(
from_crossterm_attributes(CrosstermAttributes::from(value)),
expected
);
}
}
#[rstest]
#[case(ContentStyle::default(), Style::default())]
#[case(
ContentStyle {
foreground_color: Some(CrosstermColor::DarkYellow),
..Default::default()
},
Style::default().fg(Color::Yellow)
)]
#[case(
ContentStyle {
background_color: Some(CrosstermColor::DarkYellow),
..Default::default()
},
Style::default().bg(Color::Yellow)
)]
#[case(
ContentStyle {
attributes: CrosstermAttributes::from(CrosstermAttribute::Bold),
..Default::default()
},
Style::default().add_modifier(Modifier::BOLD)
)]
#[case(
ContentStyle {
attributes: CrosstermAttributes::from(CrosstermAttribute::NoBold),
..Default::default()
},
Style::default().remove_modifier(Modifier::BOLD)
)]
#[case(
ContentStyle {
attributes: CrosstermAttributes::from(CrosstermAttribute::Italic),
..Default::default()
},
Style::default().add_modifier(Modifier::ITALIC)
)]
#[case(
ContentStyle {
attributes: CrosstermAttributes::from(CrosstermAttribute::NoItalic),
..Default::default()
},
Style::default().remove_modifier(Modifier::ITALIC)
)]
#[case(
ContentStyle {
attributes: CrosstermAttributes::from(
[CrosstermAttribute::Bold, CrosstermAttribute::Italic].as_ref()
),
..Default::default()
},
Style::default()
.add_modifier(Modifier::BOLD)
.add_modifier(Modifier::ITALIC)
)]
#[case(
ContentStyle {
attributes: CrosstermAttributes::from(
[CrosstermAttribute::NoBold, CrosstermAttribute::NoItalic].as_ref()
),
..Default::default()
},
Style::default()
.remove_modifier(Modifier::BOLD)
.remove_modifier(Modifier::ITALIC)
)]
#[cfg(feature = "underline-color")]
#[case(
ContentStyle {
underline_color: Some(CrosstermColor::DarkRed),
..Default::default()
},
Style::default().underline_color(Color::Red)
)]
fn convert_from_crossterm_content_style(#[case] value: ContentStyle, #[case] expected: Style) {
assert_eq!(from_crossterm_style(value), expected);
}
}

View File

@@ -0,0 +1,9 @@
[package]
name = "ratatui-termion"
version = "0.1.0"
edition = "2021"
[dependencies]
instability = { workspace = true }
ratatui-core = { workspace = true }
termion.workspace = true

View File

@@ -9,13 +9,13 @@ use std::{
io::{self, Write},
};
use crate::{
use ratatui_core::{
backend::{Backend, ClearType, WindowSize},
buffer::Cell,
layout::{Position, Size},
style::{Color, Modifier, Style},
termion::{self, color as tcolor, color::Color as _, style as tstyle},
};
use termion::{color as tcolor, color::Color as _, style as tstyle};
/// A [`Backend`] implementation that uses [Termion] to render to the terminal.
///
@@ -76,6 +76,11 @@ where
{
/// Creates a new Termion backend with the given writer.
///
/// Most applications will use either [`stdout`](std::io::stdout) or
/// [`stderr`](std::io::stderr) as writer. See the [FAQ] to determine which one to use.
///
/// [FAQ]: https://ratatui.rs/faq/#should-i-use-stdout-or-stderr
///
/// # Example
///
/// ```rust,no_run

View File

@@ -0,0 +1,7 @@
[package]
name = "ratatui-termwiz"
version = "0.1.0"
edition = "2021"
[dependencies]
ratatui-core = { workspace = true }

View File

@@ -0,0 +1,40 @@
[package]
name = "ratatui-widgets"
version = "0.1.0"
edition = "2021"
[features]
# TODO: remove unstable-widget-ref, consider whether to keep all-widgets
default = ["all-widgets", "unstable-widget-ref"]
## enables serialization and deserialization of style and color types using the [`serde`] crate.
## This is useful if you want to save themes to a file.
serde = ["dep:serde", "bitflags/serde"]
## enables all widgets.
all-widgets = ["widget-calendar"]
#! Widgets that add dependencies are gated behind feature flags to prevent unused transitive
#! dependencies. The available features are:
## enables the [`calendar`](widgets::calendar) widget module and adds a dependency on [`time`].
widget-calendar = ["dep:time"]
unstable-widget-ref = ["ratatui-core/unstable-widget-ref"]
[dependencies]
bitflags.workspace = true
instability.workspace = true
itertools.workspace = true
ratatui-core.workspace = true
serde = { workspace = true, optional = true }
strum.workspace = true
time = { workspace = true, optional = true }
unicode-segmentation.workspace = true
unicode-width.workspace = true
[dev-dependencies]
rstest.workspace = true
indoc.workspace = true
pretty_assertions.workspace = true
time.workspace = true

View File

@@ -1,4 +1,8 @@
use crate::{prelude::*, style::Styled, widgets::Block};
use ratatui_core::{
prelude::{symbols, Buffer, Direction, Line, Rect, Style, Stylize, Widget},
style::Styled,
widgets::WidgetRef,
};
mod bar;
mod bar_group;
@@ -6,6 +10,8 @@ mod bar_group;
pub use bar::Bar;
pub use bar_group::BarGroup;
use crate::{block::BlockExt, Block};
/// A chart showing values as [bars](Bar).
///
/// Here is a possible `BarChart` output.
@@ -613,9 +619,14 @@ impl<'a> Styled for BarChart<'a> {
#[cfg(test)]
mod tests {
use itertools::iproduct;
use ratatui_core::{
layout::Alignment,
style::{Color, Modifier},
text::Span,
};
use super::*;
use crate::widgets::BorderType;
use crate::BorderType;
#[test]
fn default() {

View File

@@ -1,8 +1,7 @@
use ratatui_core::prelude::{Buffer, Line, Rect, Style, Widget};
use unicode_width::UnicodeWidthStr;
use crate::prelude::*;
/// A bar to be shown by the [`BarChart`](crate::widgets::BarChart) widget.
/// A bar to be shown by the [`BarChart`](crate::BarChart) widget.
///
/// Here is an explanation of a `Bar`'s components.
/// ```plain
@@ -61,7 +60,7 @@ impl<'a> Bar<'a> {
/// display the label **under** the bar.
/// For [`Horizontal`](crate::layout::Direction::Horizontal) bars,
/// display the label **in** the bar.
/// See [`BarChart::direction`](crate::widgets::BarChart::direction) to set the direction.
/// See [`BarChart::direction`](crate::BarChart::direction) to set the direction.
#[must_use = "method moves the value of self and returns the modified value"]
pub fn label(mut self, label: Line<'a>) -> Self {
self.label = Some(label);

View File

@@ -1,5 +1,6 @@
use ratatui_core::prelude::*;
use super::Bar;
use crate::prelude::*;
/// A group of bars to be shown by the Barchart.
///

View File

@@ -6,15 +6,23 @@
//! [title](Block::title) and [padding](Block::padding).
use itertools::Itertools;
use ratatui_core::{
prelude::{Alignment, Buffer, Line, Rect, Style, Stylize, Widget},
style::Styled,
symbols::border,
widgets::WidgetRef,
};
use strum::{Display, EnumString};
use crate::{prelude::*, style::Styled, symbols::border, widgets::Borders};
use self::title::Position;
mod padding;
pub mod title;
pub use padding::Padding;
pub use title::{Position, Title};
pub use title::Title;
use crate::Borders;
/// Base widget to be used to display a box border around all [upper level ones](crate::widgets).
///
@@ -108,7 +116,7 @@ pub use title::{Position, Title};
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct Block<'a> {
/// List of titles
titles: Vec<Title<'a>>,
titles: Vec<(Option<Position>, Line<'a>)>,
/// The style to be patched to all titles of the block
titles_style: Style,
/// The default alignment of the titles that don't have one
@@ -243,9 +251,9 @@ impl<'a> Block<'a> {
/// [spans](crate::text::Span) (`Vec<Span>`).
///
/// By default, the titles will avoid being rendered in the corners of the block but will align
/// against the left or right edge of the block if there is no border on that edge.
/// The following demonstrates this behavior, notice the second title is one character off to
/// the left.
/// against the left or right edge of the block if there is no border on that edge. The
/// following demonstrates this behavior, notice the second title is one character off to the
/// left.
///
/// ```plain
/// ┌With at least a left border───
@@ -274,9 +282,9 @@ impl<'a> Block<'a> {
///
/// Block::new()
/// .title("Title") // By default in the top left corner
/// .title(Title::from("Left").alignment(Alignment::Left)) // also on the left
/// .title(Title::from("Right").alignment(Alignment::Right))
/// .title(Title::from("Center").alignment(Alignment::Center));
/// .title(Line::from("Left").left_aligned()) // also on the left
/// .title(Line::from("Right").right_aligned())
/// .title(Line::from("Center").centered());
/// // Renders
/// // ┌Title─Left────Center─────────Right┐
/// ```
@@ -288,13 +296,26 @@ impl<'a> Block<'a> {
/// - [`Block::title_alignment`]
/// - [`Block::title_position`]
///
/// # Future improvements
///
/// In a future release of Ratatui this method will be changed to accept `Into<Line>` instead of
/// `Into<Title>`. This allows us to remove the unnecessary `Title` struct and store the
/// position in the block itself. For more information see
/// <https://github.com/ratatui/ratatui/issues/738>.
///
/// [Block example]: https://github.com/ratatui/ratatui/blob/main/examples/README.md#block
#[must_use = "method moves the value of self and returns the modified value"]
pub fn title<T>(mut self, title: T) -> Self
where
T: Into<Title<'a>>,
{
self.titles.push(title.into());
let title = title.into();
let position = title.position;
let mut content = title.content;
if let Some(alignment) = title.alignment {
content = content.alignment(alignment);
}
self.titles.push((position, content));
self
}
@@ -321,8 +342,8 @@ impl<'a> Block<'a> {
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
pub fn title_top<T: Into<Line<'a>>>(mut self, title: T) -> Self {
let title = Title::from(title).position(Position::Top);
self.titles.push(title);
let line = title.into();
self.titles.push((Some(Position::Top), line));
self
}
@@ -349,8 +370,8 @@ impl<'a> Block<'a> {
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
pub fn title_bottom<T: Into<Line<'a>>>(mut self, title: T) -> Self {
let title = Title::from(title).position(Position::Bottom);
self.titles.push(title);
let line = title.into();
self.titles.push((Some(Position::Bottom), line));
self
}
@@ -387,7 +408,7 @@ impl<'a> Block<'a> {
/// Block::new()
/// .title_alignment(Alignment::Center)
/// // This title won't be aligned in the center
/// .title(Title::from("right").alignment(Alignment::Right))
/// .title(Line::from("right").right_aligned())
/// .title("foo")
/// .title("bar");
/// ```
@@ -417,7 +438,7 @@ impl<'a> Block<'a> {
/// Block::new()
/// .title_position(Position::Bottom)
/// // This title won't be aligned in the center
/// .title(Title::from("top").position(Position::Top))
/// .title_top("top")
/// .title("foo")
/// .title("bar");
/// ```
@@ -482,7 +503,7 @@ impl<'a> Block<'a> {
/// .style(Style::new().white().not_bold()); // will be white, and italic
/// ```
///
/// [`Paragraph`]: crate::widgets::Paragraph
/// [`Paragraph`]: crate::Paragraph
#[must_use = "method moves the value of self and returns the modified value"]
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
self.style = style.into();
@@ -595,7 +616,7 @@ impl<'a> Block<'a> {
/// let outer_block = Block::bordered().title("Outer");
/// let inner_block = Block::bordered().title("Inner");
///
/// let outer_area = frame.size();
/// let outer_area = frame.area();
/// let inner_area = outer_block.inner(outer_area);
///
/// frame.render_widget(outer_block, outer_area);
@@ -642,7 +663,7 @@ impl<'a> Block<'a> {
fn has_title_at_position(&self, position: Position) -> bool {
self.titles
.iter()
.any(|title| title.position.unwrap_or(self.titles_position) == position)
.any(|(pos, _)| pos.unwrap_or(self.titles_position) == position)
}
}
@@ -798,7 +819,7 @@ impl Block<'_> {
if titles_area.is_empty() {
break;
}
let title_width = title.content.width() as u16;
let title_width = title.width() as u16;
let title_area = Rect {
x: titles_area
.right()
@@ -808,7 +829,7 @@ impl Block<'_> {
..titles_area
};
buf.set_style(title_area, self.titles_style);
title.content.render_ref(title_area, buf);
title.render_ref(title_area, buf);
// bump the width of the titles area to the left
titles_area.width = titles_area
@@ -830,7 +851,7 @@ impl Block<'_> {
.collect_vec();
let total_width = titles
.iter()
.map(|title| title.content.width() as u16 + 1) // space between titles
.map(|title| title.width() as u16 + 1) // space between titles
.sum::<u16>()
.saturating_sub(1); // no space for the last title
@@ -843,13 +864,13 @@ impl Block<'_> {
if titles_area.is_empty() {
break;
}
let title_width = title.content.width() as u16;
let title_width = title.width() as u16;
let title_area = Rect {
width: title_width.min(titles_area.width),
..titles_area
};
buf.set_style(title_area, self.titles_style);
title.content.render_ref(title_area, buf);
title.render_ref(title_area, buf);
// bump the titles area to the right and reduce its width
titles_area.x = titles_area.x.saturating_add(title_width + 1);
@@ -866,13 +887,13 @@ impl Block<'_> {
if titles_area.is_empty() {
break;
}
let title_width = title.content.width() as u16;
let title_width = title.width() as u16;
let title_area = Rect {
width: title_width.min(titles_area.width),
..titles_area
};
buf.set_style(title_area, self.titles_style);
title.content.render_ref(title_area, buf);
title.render_ref(title_area, buf);
// bump the titles area to the right and reduce its width
titles_area.x = titles_area.x.saturating_add(title_width + 1);
@@ -885,11 +906,12 @@ impl Block<'_> {
&self,
position: Position,
alignment: Alignment,
) -> impl DoubleEndedIterator<Item = &Title> {
self.titles.iter().filter(move |title| {
title.position.unwrap_or(self.titles_position) == position
&& title.alignment.unwrap_or(self.titles_alignment) == alignment
})
) -> impl DoubleEndedIterator<Item = &Line> {
self.titles
.iter()
.filter(move |(pos, _)| pos.unwrap_or(self.titles_position) == position)
.filter(move |(_, line)| line.alignment.unwrap_or(self.titles_alignment) == alignment)
.map(|(_, line)| line)
}
/// An area that is one line tall and spans the width of the block excluding the borders and
@@ -972,6 +994,7 @@ impl<'a> Styled for Block<'a> {
#[cfg(test)]
mod tests {
use ratatui_core::style::{Color, Modifier};
use rstest::rstest;
use strum::ParseError;
@@ -1023,24 +1046,17 @@ mod tests {
let area = Rect::new(0, 0, 0, 1);
let expected = Rect::new(0, 1, 0, 0);
let block = Block::new().title(Title::from("Test").alignment(alignment));
let block = Block::new().title(Line::from("Test").alignment(alignment));
assert_eq!(block.inner(area), expected);
}
#[rstest]
#[case::top_top(Borders::TOP, Position::Top, Rect::new(0, 1, 0, 1))]
#[case::top_bot(Borders::BOTTOM, Position::Top, Rect::new(0, 1, 0, 0))]
#[case::bot_top(Borders::TOP, Position::Bottom, Rect::new(0, 1, 0, 0))]
#[case::top_top(Borders::BOTTOM, Position::Bottom, Rect::new(0, 0, 0, 1))]
fn inner_takes_into_account_border_and_title(
#[case] borders: Borders,
#[case] position: Position,
#[case] expected: Rect,
) {
#[case::top_top(Block::new().title_top("Test").borders(Borders::TOP), Rect::new(0, 1, 0, 1))]
#[case::top_bot(Block::new().title_top("Test").borders(Borders::BOTTOM), Rect::new(0, 1, 0, 0))]
#[case::bot_top(Block::new().title_bottom("Test").borders(Borders::TOP), Rect::new(0, 1, 0, 0))]
#[case::bot_bot(Block::new().title_bottom("Test").borders(Borders::BOTTOM), Rect::new(0, 0, 0, 1))]
fn inner_takes_into_account_border_and_title(#[case] block: Block, #[case] expected: Rect) {
let area = Rect::new(0, 0, 0, 2);
let block = Block::new()
.borders(borders)
.title(Title::from("Test").position(position));
assert_eq!(block.inner(area), expected);
}
@@ -1050,32 +1066,33 @@ mod tests {
assert!(!block.has_title_at_position(Position::Top));
assert!(!block.has_title_at_position(Position::Bottom));
let block = Block::new().title(Title::from("Test").position(Position::Top));
let block = Block::new().title_top("test");
assert!(block.has_title_at_position(Position::Top));
assert!(!block.has_title_at_position(Position::Bottom));
let block = Block::new().title(Title::from("Test").position(Position::Bottom));
let block = Block::new().title_bottom("test");
assert!(!block.has_title_at_position(Position::Top));
assert!(block.has_title_at_position(Position::Bottom));
#[allow(deprecated)] // until Title is removed
let block = Block::new()
.title(Title::from("Test").position(Position::Top))
.title_position(Position::Bottom);
assert!(block.has_title_at_position(Position::Top));
assert!(!block.has_title_at_position(Position::Bottom));
#[allow(deprecated)] // until Title is removed
let block = Block::new()
.title(Title::from("Test").position(Position::Bottom))
.title_position(Position::Top);
assert!(!block.has_title_at_position(Position::Top));
assert!(block.has_title_at_position(Position::Bottom));
let block = Block::new()
.title(Title::from("Test").position(Position::Top))
.title(Title::from("Test").position(Position::Bottom));
let block = Block::new().title_top("test").title_bottom("test");
assert!(block.has_title_at_position(Position::Top));
assert!(block.has_title_at_position(Position::Bottom));
#[allow(deprecated)] // until Title is removed
let block = Block::new()
.title(Title::from("Test").position(Position::Top))
.title(Title::from("Test"))
@@ -1083,6 +1100,7 @@ mod tests {
assert!(block.has_title_at_position(Position::Top));
assert!(block.has_title_at_position(Position::Bottom));
#[allow(deprecated)] // until Title is removed
let block = Block::new()
.title(Title::from("Test"))
.title(Title::from("Test").position(Position::Bottom))
@@ -1130,16 +1148,10 @@ mod tests {
#[test]
fn vertical_space_takes_into_account_titles() {
let block = Block::new()
.title_position(Position::Top)
.title(Title::from("Test"));
let block = Block::new().title_top("Test");
assert_eq!(block.vertical_space(), (1, 0));
let block = Block::new()
.title_position(Position::Bottom)
.title(Title::from("Test"));
let block = Block::new().title_bottom("Test");
assert_eq!(block.vertical_space(), (0, 1));
}
@@ -1158,10 +1170,7 @@ mod tests {
#[case] pos: Position,
#[case] vertical_space: (u16, u16),
) {
let block = block
.borders(borders)
.title_position(pos)
.title(Title::from("Test"));
let block = block.borders(borders).title_position(pos).title("Test");
assert_eq!(block.vertical_space(), vertical_space);
}
@@ -1244,7 +1253,7 @@ mod tests {
// .border_style(_DEFAULT_STYLE) // no longer const
// .title_style(_DEFAULT_STYLE) // no longer const
.title_alignment(Alignment::Left)
.title_position(Position::Top)
.title_position(crate::block::Position::Top)
.padding(_DEFAULT_PADDING);
}
@@ -1310,6 +1319,7 @@ mod tests {
use Alignment::*;
use Position::*;
let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 3));
#[allow(deprecated)] // until Title is removed
Block::bordered()
.title(Title::from("A").position(Top).alignment(Left))
.title(Title::from("B").position(Top).alignment(Center))
@@ -1375,7 +1385,7 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 1));
Block::new()
.title_alignment(block_title_alignment)
.title(Title::from("test").alignment(alignment))
.title(Line::from("test").alignment(alignment))
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines([expected]));
}

View File

@@ -19,8 +19,8 @@
/// Padding::symmetric(5, 6);
/// ```
///
/// [`Block`]: crate::widgets::Block
/// [`padding`]: crate::widgets::Block::padding
/// [`Block`]: crate::Block
/// [`padding`]: crate::Block::padding
/// [CSS padding]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Padding {

View File

@@ -1,14 +1,24 @@
//! This module holds the [`Title`] element and its related configuration types.
//! A title is a piece of [`Block`](crate::widgets::Block) configuration.
//! A title is a piece of [`Block`](crate::Block) configuration.
use ratatui_core::{layout::Alignment, text::Line};
use strum::{Display, EnumString};
use crate::{layout::Alignment, text::Line};
/// A [`Block`](crate::widgets::Block) title.
/// A [`Block`](crate::Block) title.
///
/// It can be aligned (see [`Alignment`]) and positioned (see [`Position`]).
///
/// # Future Deprecation
///
/// This type is deprecated and will be removed in a future release. The reason for this is that the
/// position of the title should be stored in the block itself, not in the title. The `Line` type
/// has an alignment method that can be used to align the title. For more information see
/// <https://github.com/ratatui/ratatui/issues/738>.
///
/// Use [`Line`] instead, when the position is not defined as part of the title. When a specific
/// position is needed, use [`Block::title_top`](crate::Block::title_top) or
/// [`Block::title_bottom`](crate::Block::title_bottom) instead.
///
/// # Example
///
/// Title with no style.
@@ -53,19 +63,19 @@ pub struct Title<'a> {
/// Title alignment
///
/// If [`None`], defaults to the alignment defined with
/// [`Block::title_alignment`](crate::widgets::Block::title_alignment) in the associated
/// [`Block`](crate::widgets::Block).
/// [`Block::title_alignment`](crate::Block::title_alignment) in the associated
/// [`Block`](crate::Block).
pub alignment: Option<Alignment>,
/// Title position
///
/// If [`None`], defaults to the position defined with
/// [`Block::title_position`](crate::widgets::Block::title_position) in the associated
/// [`Block`](crate::widgets::Block).
/// [`Block::title_position`](crate::Block::title_position) in the associated
/// [`Block`](crate::Block).
pub position: Option<Position>,
}
/// Defines the [title](crate::widgets::block::Title) position.
/// Defines the [title](crate::block::Title) position.
///
/// The title can be positioned on top or at the bottom of the block.
/// Defaults to [`Position::Top`].
@@ -88,6 +98,7 @@ pub enum Position {
Bottom,
}
#[deprecated = "use Block::title_top() or Block::title_bottom() instead. This will be removed in a future release."]
impl<'a> Title<'a> {
/// Set the title content.
#[must_use = "method moves the value of self and returns the modified value"]

View File

@@ -55,7 +55,7 @@ impl fmt::Debug for Borders {
/// and RIGHT.
///
/// When used with NONE you should consider omitting this completely. For ALL you should consider
/// [`Block::bordered()`](crate::widgets::Block::bordered) instead.
/// [`Block::bordered()`](crate::Block::bordered) instead.
///
/// ## Examples
///

View File

@@ -10,9 +10,13 @@
//! [`Monthly`] has several controls for what should be displayed
use std::collections::HashMap;
use ratatui_core::{
prelude::{Alignment, Buffer, Color, Constraint, Layout, Line, Rect, Span, Style, Widget},
widgets::WidgetRef,
};
use time::{Date, Duration, OffsetDateTime};
use crate::{prelude::*, widgets::Block};
use crate::{block::BlockExt, Block};
/// Display a month calendar for the month containing `display_date`
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
@@ -177,7 +181,9 @@ impl<DS: DateStyler> Monthly<'_, DS> {
spans.push(self.format_date(curr_day));
curr_day += Duration::DAY;
}
buf.set_line(days_area.x, y, &spans.into(), area.width);
if buf.area.height > y {
buf.set_line(days_area.x, y, &spans.into(), area.width);
}
y += 1;
}
}

View File

@@ -22,6 +22,12 @@ mod world;
use std::{fmt, iter::zip};
use itertools::Itertools;
use ratatui_core::{
prelude::{Buffer, Color, Rect, Style, Widget},
symbols::{self, Marker},
text::Line as TextLine,
widgets::WidgetRef,
};
pub use self::{
circle::Circle,
@@ -30,7 +36,7 @@ pub use self::{
points::Points,
rectangle::Rectangle,
};
use crate::{prelude::*, symbols::Marker, text::Line as TextLine, widgets::Block};
use crate::block::{Block, BlockExt};
/// Something that can be drawn on a [`Canvas`].
///
@@ -425,7 +431,7 @@ impl<'a, 'b> From<&'a mut Context<'b>> for Painter<'a, 'b> {
/// This is used by the [`Canvas`] widget to draw shapes on the grid. It can be useful to think of
/// this as similar to the [`Frame`] struct that is used to draw widgets on the terminal.
///
/// [`Frame`]: crate::prelude::Frame
/// [`Frame`]: ratatui_core::prelude::Frame
#[derive(Debug)]
pub struct Context<'a> {
x_bounds: [f64; 2],
@@ -809,9 +815,9 @@ where
#[cfg(test)]
mod tests {
use indoc::indoc;
use ratatui_core::buffer::Cell;
use super::*;
use crate::buffer::Cell;
// helper to test the canvas checks that drawing a vertical and horizontal line
// results in the expected output

View File

@@ -1,7 +1,6 @@
use crate::{
style::Color,
widgets::canvas::{Painter, Shape},
};
use ratatui_core::style::Color;
use crate::canvas::{Painter, Shape};
/// A circle with a given center and radius and with a given color
#[derive(Debug, Default, Clone, PartialEq)]
@@ -31,17 +30,12 @@ impl Shape for Circle {
#[cfg(test)]
mod tests {
use crate::{
buffer::Buffer,
layout::Rect,
style::Color,
symbols::Marker,
widgets::{
canvas::{Canvas, Circle},
Widget,
},
use ratatui_core::{
buffer::Buffer, layout::Rect, style::Color, symbols::Marker, widgets::Widget,
};
use crate::canvas::{Canvas, Circle};
#[test]
fn test_it_draws_a_circle() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 5));

View File

@@ -1,7 +1,6 @@
use crate::{
style::Color,
widgets::canvas::{Painter, Shape},
};
use ratatui_core::style::Color;
use crate::canvas::{Painter, Shape};
/// A line from `(x1, y1)` to `(x2, y2)` with the given color
#[derive(Debug, Default, Clone, PartialEq)]
@@ -112,16 +111,18 @@ fn draw_line_high(painter: &mut Painter, x1: usize, y1: usize, x2: usize, y2: us
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
use crate::{
use ratatui_core::{
buffer::Buffer,
layout::Rect,
style::{Style, Stylize},
symbols::Marker,
widgets::{canvas::Canvas, Widget},
text,
widgets::Widget,
};
use rstest::rstest;
use super::*;
use crate::canvas::Canvas;
#[rstest]
#[case::off_grid(&Line::new(-1.0, -1.0, 10.0, 10.0, Color::Red), [" "; 10])]
@@ -207,7 +208,7 @@ mod tests {
fn tests<'expected_line, ExpectedLines>(#[case] line: &Line, #[case] expected: ExpectedLines)
where
ExpectedLines: IntoIterator,
ExpectedLines::Item: Into<crate::text::Line<'expected_line>>,
ExpectedLines::Item: Into<text::Line<'expected_line>>,
{
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
let canvas = Canvas::default()

View File

@@ -1,13 +1,10 @@
use ratatui_core::style::Color;
use strum::{Display, EnumString};
use crate::{
style::Color,
widgets::canvas::{
world::{WORLD_HIGH_RESOLUTION, WORLD_LOW_RESOLUTION},
Painter, Shape,
},
use crate::canvas::{
world::{WORLD_HIGH_RESOLUTION, WORLD_LOW_RESOLUTION},
Painter, Shape,
};
/// Defines how many points are going to be used to draw a [`Map`].
///
/// You generally want a [high](MapResolution::High) resolution map.
@@ -62,10 +59,14 @@ impl Shape for Map {
#[cfg(test)]
mod tests {
use ratatui_core::{
prelude::{Buffer, Color, Rect, Widget},
symbols::Marker,
};
use strum::ParseError;
use super::*;
use crate::{prelude::*, symbols::Marker, widgets::canvas::Canvas};
use crate::canvas::Canvas;
#[test]
fn map_resolution_to_string() {

View File

@@ -1,7 +1,6 @@
use crate::{
style::Color,
widgets::canvas::{Painter, Shape},
};
use ratatui_core::style::Color;
use crate::canvas::{Painter, Shape};
/// A group of points with a given color
#[derive(Debug, Default, Clone, PartialEq)]

View File

@@ -1,7 +1,6 @@
use crate::{
style::Color,
widgets::canvas::{Line, Painter, Shape},
};
use ratatui_core::style::Color;
use crate::canvas::{Line, Painter, Shape};
/// A rectangle to draw on a [`Canvas`](super::Canvas)
///
@@ -65,8 +64,10 @@ impl Shape for Rectangle {
#[cfg(test)]
mod tests {
use ratatui_core::{prelude::*, symbols::Marker};
use super::*;
use crate::{prelude::*, symbols::Marker, widgets::canvas::Canvas};
use crate::canvas::Canvas;
#[test]
fn draw_block_lines() {

View File

@@ -1,17 +1,13 @@
use std::{cmp::max, ops::Not};
use ratatui_core::{layout::Flex, prelude::*, style::Styled, widgets::WidgetRef};
use strum::{Display, EnumString};
use crate::{
layout::Flex,
prelude::*,
style::Styled,
widgets::{
canvas::{Canvas, Line as CanvasLine, Points},
Block,
},
block::BlockExt,
canvas::{Canvas, Line as CanvasLine, Points},
Block,
};
/// An X or Y axis for the [`Chart`] widget
///
/// An axis can have a [title](Axis::title) which will be displayed at the end of the axis. For an
@@ -1074,7 +1070,7 @@ impl WidgetRef for Chart<'_> {
for (i, (dataset_name, dataset_style)) in self
.datasets
.iter()
.filter_map(|ds| Some((ds.name.as_ref()?, ds.style())))
.filter_map(|ds| Some((ds.name.as_ref()?, ds.style)))
.enumerate()
{
let name = dataset_name.clone().patch_style(dataset_style);

View File

@@ -1,4 +1,4 @@
use crate::prelude::*;
use ratatui_core::{prelude::*, widgets::WidgetRef};
/// A widget to clear/reset a certain area to allow overdrawing (e.g. for popups).
///

View File

@@ -1,4 +1,6 @@
use crate::{prelude::*, style::Styled, widgets::Block};
use ratatui_core::{prelude::*, style::Styled, widgets::WidgetRef};
use crate::{block::BlockExt, Block};
/// A widget to display a progress bar.
///

View File

@@ -0,0 +1,31 @@
mod barchart;
pub mod block;
mod borders;
#[cfg(feature = "widget-calendar")]
pub mod calendar;
pub mod canvas;
mod chart;
mod clear;
mod gauge;
mod list;
mod paragraph;
mod reflow;
mod scrollbar;
mod sparkline;
mod table;
mod tabs;
pub use self::{
barchart::{Bar, BarChart, BarGroup},
block::{Block, BorderType, Padding},
borders::*,
chart::{Axis, Chart, Dataset, GraphType, LegendPosition},
clear::Clear,
gauge::{Gauge, LineGauge},
list::{List, ListDirection, ListItem, ListState},
paragraph::{Paragraph, Wrap},
scrollbar::{ScrollDirection, Scrollbar, ScrollbarOrientation, ScrollbarState},
sparkline::{RenderDirection, Sparkline},
table::{Cell, HighlightSpacing, Row, Table, TableState},
tabs::Tabs,
};

View File

@@ -1,4 +1,4 @@
use crate::prelude::*;
use ratatui_core::prelude::*;
/// A single item in a [`List`]
///
@@ -55,7 +55,7 @@ use crate::prelude::*;
/// ListItem::new(Text::from("foo").alignment(Alignment::Right));
/// ```
///
/// [`List`]: crate::widgets::List
/// [`List`]: crate::List
/// [`Stylize`]: crate::style::Stylize
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct ListItem<'a> {
@@ -94,8 +94,8 @@ impl<'a> ListItem<'a> {
///
/// # See also
///
/// - [`List::new`](crate::widgets::List::new) to create a list of items that can be converted
/// to [`ListItem`]
/// - [`List::new`](crate::List::new) to create a list of items that can be converted to
/// [`ListItem`]
pub fn new<T>(content: T) -> Self
where
T: Into<Text<'a>>,
@@ -132,7 +132,7 @@ impl<'a> ListItem<'a> {
/// ```
///
/// [`Styled`]: crate::style::Styled
/// [`ListState`]: crate::widgets::list::ListState
/// [`ListState`]: crate::list::ListState
#[must_use = "method moves the value of self and returns the modified value"]
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
self.style = style.into();

View File

@@ -1,11 +1,8 @@
use ratatui_core::{prelude::Style, style::Styled};
use strum::{Display, EnumString};
use super::ListItem;
use crate::{
prelude::*,
style::Styled,
widgets::{Block, HighlightSpacing},
};
use crate::{Block, HighlightSpacing};
/// A widget to display several items among which one can be selected (optional)
///
@@ -15,7 +12,7 @@ use crate::{
/// the item's height is automatically determined. A `List` can also be put in reverse order (i.e.
/// *bottom to top*) whereas a [`Table`] cannot.
///
/// [`Table`]: crate::widgets::Table
/// [`Table`]: crate::Table
///
/// List items can be aligned using [`Text::alignment`], for more details see [`ListItem`].
///
@@ -85,9 +82,9 @@ use crate::{
/// (0..5).map(|i| format!("Item{i}")).collect::<List>();
/// ```
///
/// [`ListState`]: crate::widgets::list::ListState
/// [scroll]: crate::widgets::list::ListState::offset
/// [select]: crate::widgets::list::ListState::select
/// [`ListState`]: crate::list::ListState
/// [scroll]: crate::list::ListState::offset
/// [select]: crate::list::ListState::select
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub struct List<'a> {
/// An optional block to wrap the widget in
@@ -421,6 +418,7 @@ where
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use ratatui_core::style::{Color, Modifier, Stylize};
use super::*;

View File

@@ -1,9 +1,10 @@
use ratatui_core::{
prelude::{Buffer, Rect, StatefulWidget, Widget},
widgets::{StatefulWidgetRef, WidgetRef},
};
use unicode_width::UnicodeWidthStr;
use crate::{
prelude::{Buffer, Rect, StatefulWidget, StatefulWidgetRef, Widget, WidgetRef},
widgets::{block::BlockExt, List, ListDirection, ListState},
};
use crate::{block::BlockExt, List, ListDirection, ListState};
impl Widget for List<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
@@ -269,13 +270,11 @@ impl List<'_> {
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use ratatui_core::prelude::*;
use rstest::{fixture, rstest};
use super::*;
use crate::{
prelude::*,
widgets::{Block, HighlightSpacing, ListItem},
};
use crate::{Block, HighlightSpacing, ListItem};
#[fixture]
fn single_line_buf() -> Buffer {

View File

@@ -36,7 +36,7 @@
/// # }
/// ```
///
/// [`List`]: crate::widgets::List
/// [`List`]: crate::List
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ListState {
@@ -258,7 +258,7 @@ impl ListState {
mod tests {
use pretty_assertions::assert_eq;
use crate::widgets::ListState;
use crate::ListState;
#[test]
fn selected() {

View File

@@ -1,13 +1,10 @@
use ratatui_core::{prelude::*, style::Styled, text::StyledGrapheme, widgets::WidgetRef};
use unicode_width::UnicodeWidthStr;
use crate::{
prelude::*,
style::Styled,
text::StyledGrapheme,
widgets::{
reflow::{LineComposer, LineTruncator, WordWrapper, WrappedLine},
Block,
},
block::BlockExt,
reflow::{LineComposer, LineTruncator, WordWrapper, WrappedLine},
Block,
};
const fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment) -> u16 {
@@ -470,11 +467,10 @@ impl<'a> Styled for Paragraph<'a> {
#[cfg(test)]
mod test {
use ratatui_core::backend::TestBackend;
use super::*;
use crate::{
backend::TestBackend,
widgets::{block::Position, Borders},
};
use crate::{block::title::Position, Borders};
/// Tests the [`Paragraph`] widget against the expected [`Buffer`] by rendering it onto an equal
/// area and comparing the rendered and expected content.

View File

@@ -1,10 +1,9 @@
use std::{collections::VecDeque, mem};
use ratatui_core::{layout::Alignment, text::StyledGrapheme};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use crate::{layout::Alignment, text::StyledGrapheme};
/// A state machine to pack styled symbols into lines.
/// Cannot implement it as Iterator since it yields slices of the internal buffer (need streaming
/// iterators for that).
@@ -51,7 +50,7 @@ where
O: Iterator<Item = (I, Alignment)>,
I: Iterator<Item = StyledGrapheme<'a>>,
{
pub fn new(lines: O, max_line_width: u16, trim: bool) -> Self {
pub const fn new(lines: O, max_line_width: u16, trim: bool) -> Self {
Self {
input_lines: lines,
max_line_width,
@@ -250,7 +249,7 @@ where
O: Iterator<Item = (I, Alignment)>,
I: Iterator<Item = StyledGrapheme<'a>>,
{
pub fn new(lines: O, max_line_width: u16) -> Self {
pub const fn new(lines: O, max_line_width: u16) -> Self {
Self {
input_lines: lines,
max_line_width,
@@ -344,12 +343,13 @@ fn trim_offset(src: &str, mut offset: usize) -> &str {
#[cfg(test)]
mod test {
use super::*;
use crate::{
use ratatui_core::{
style::Style,
text::{Line, Text},
};
use super::*;
#[derive(Clone, Copy)]
enum Composer {
WordWrapper { trim: bool },

View File

@@ -8,13 +8,12 @@
use std::iter;
use strum::{Display, EnumString};
use unicode_width::UnicodeWidthStr;
use crate::{
use ratatui_core::{
prelude::*,
symbols::scrollbar::{Set, DOUBLE_HORIZONTAL, DOUBLE_VERTICAL},
};
use strum::{Display, EnumString};
use unicode_width::UnicodeWidthStr;
/// A widget to display a scrollbar
///
@@ -59,7 +58,7 @@ use crate::{
///
/// let mut scrollbar_state = ScrollbarState::new(items.len()).position(vertical_scroll);
///
/// let area = frame.size();
/// let area = frame.area();
/// // Note we render the paragraph
/// frame.render_widget(paragraph, area);
/// // and the scrollbar, those are separate widgets

View File

@@ -1,8 +1,9 @@
use std::cmp::min;
use ratatui_core::{prelude::*, style::Styled, widgets::WidgetRef};
use strum::{Display, EnumString};
use crate::{prelude::*, style::Styled, widgets::Block};
use crate::{block::BlockExt, Block};
/// Widget to render a sparkline over one or more lines.
///
@@ -208,10 +209,10 @@ impl Sparkline<'_> {
#[cfg(test)]
mod tests {
use ratatui_core::buffer::Cell;
use strum::ParseError;
use super::*;
use crate::buffer::Cell;
#[test]
fn render_direction_to_string() {
@@ -237,7 +238,7 @@ mod tests {
// Helper function to render a sparkline to a buffer with a given width
// filled with x symbols to make it easier to assert on the result
fn render(widget: Sparkline, width: u16) -> Buffer {
fn render(widget: Sparkline<'_>, width: u16) -> Buffer {
let area = Rect::new(0, 0, width, 1);
let mut buffer = Buffer::filled(area, Cell::new("x"));
widget.render(area, &mut buffer);
@@ -253,6 +254,8 @@ mod tests {
#[test]
fn it_does_not_panic_if_max_is_set_to_zero() {
// see https://github.com/rust-lang/rust-clippy/issues/13191
#[allow(clippy::unnecessary_min_or_max)]
let widget = Sparkline::default().data(&[0, 1, 2]).max(0);
let buffer = render(widget, 6);
assert_eq!(buffer, Buffer::with_lines([" xxx"]));

View File

@@ -1,4 +1,4 @@
use crate::{prelude::*, style::Styled};
use ratatui_core::{prelude::*, style::Styled, widgets::WidgetRef};
/// A [`Cell`] contains the [`Text`] to be displayed in a [`Row`] of a [`Table`].
///

View File

@@ -1,5 +1,6 @@
use ratatui_core::{prelude::*, style::Styled};
use super::Cell;
use crate::{prelude::*, style::Styled};
/// A single row of data to be displayed in a [`Table`] widget.
///

View File

@@ -1,9 +1,15 @@
use itertools::Itertools;
use ratatui_core::{
layout::Flex,
prelude::*,
style::Styled,
widgets::{StatefulWidgetRef, WidgetRef},
};
#[allow(unused_imports)] // `Cell` is used in the doc comment but not the code
use super::Cell;
use super::{HighlightSpacing, Row, TableState};
use crate::{layout::Flex, prelude::*, style::Styled, widgets::Block};
use crate::{block::BlockExt, Block};
/// A widget to display data in formatted columns.
///
@@ -867,8 +873,10 @@ where
mod tests {
use std::vec;
use ratatui_core::{layout::Constraint::*, style::Style, text::Line};
use super::*;
use crate::{layout::Constraint::*, style::Style, text::Line, widgets::Cell};
use crate::table::Cell;
#[test]
fn new() {
@@ -1031,14 +1039,15 @@ mod tests {
#[cfg(test)]
mod state {
use ratatui_core::{
buffer::Buffer,
layout::{Constraint, Rect},
widgets::StatefulWidget,
};
use rstest::{fixture, rstest};
use super::TableState;
use crate::{
buffer::Buffer,
layout::{Constraint, Rect},
widgets::{Row, StatefulWidget, Table},
};
use crate::{Row, Table};
#[fixture]
fn table_buf() -> Buffer {

View File

@@ -41,8 +41,8 @@
/// Note that if [`Table::widths`] is not called before rendering, the rendered columns will have
/// equal width.
///
/// [`Table`]: crate::widgets::Table
/// [`Table::widths`]: crate::widgets::Table::widths
/// [`Table`]: crate::Table
/// [`Table::widths`]: crate::Table::widths
/// [`Frame::render_stateful_widget`]: crate::Frame::render_stateful_widget
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]

View File

@@ -1,4 +1,6 @@
use crate::{prelude::*, style::Styled, widgets::Block};
use ratatui_core::{prelude::*, style::Styled, widgets::WidgetRef};
use crate::{block::BlockExt, Block};
const DEFAULT_HIGHLIGHT_STYLE: Style = Style::new().add_modifier(Modifier::REVERSED);

386
ratatui/Cargo.toml Normal file
View File

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

View File

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

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