Compare commits

..

85 Commits

Author SHA1 Message Date
Jagoda Estera Ślązak
e602369afe wip: attempt to fix constraint solving infinite loop 2025-07-23 00:25:00 +02:00
dependabot[bot]
bce6604379 build(deps): bump taiki-e/install-action from 2.56.13 to 2.56.19 (#2006)
Bumps
[taiki-e/install-action](https://github.com/taiki-e/install-action) from
2.56.13 to 2.56.19.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/taiki-e/install-action/releases">taiki-e/install-action's
releases</a>.</em></p>
<blockquote>
<h2>2.56.19</h2>
<ul>
<li>Update <code>cargo-llvm-cov@latest</code> to 0.6.18.</li>
</ul>
<h2>2.56.18</h2>
<ul>
<li>Update <code>just@latest</code> to 1.42.3.</li>
</ul>
<h2>2.56.17</h2>
<ul>
<li>Update <code>wasmtime@latest</code> to 34.0.2.</li>
</ul>
<h2>2.56.16</h2>
<ul>
<li>
<p>Update <code>cargo-zigbuild@latest</code> to 0.20.1.</p>
</li>
<li>
<p>Update <code>cargo-lambda@latest</code> to 1.8.6.</p>
</li>
<li>
<p>Update <code>vacuum@latest</code> to 0.17.6.</p>
</li>
<li>
<p>Update <code>earthly@latest</code> to 0.8.16.</p>
</li>
</ul>
<h2>2.56.15</h2>
<ul>
<li>
<p>Fix <code>cargo-valgrind</code> installation error due to their tag
rename.</p>
</li>
<li>
<p>Update <code>cargo-valgrind@latest</code> to 2.3.2.</p>
</li>
<li>
<p>Update <code>just@latest</code> to 1.42.2.</p>
</li>
</ul>
<h2>2.56.14</h2>
<ul>
<li>
<p>Update <code>zola@latest</code> to 0.21.0.</p>
</li>
<li>
<p>Update <code>wait-for-them@latest</code> to 0.5.1.</p>
</li>
<li>
<p>Update <code>mdbook@latest</code> to 0.4.52.</p>
</li>
<li>
<p>Update <code>just@latest</code> to 1.42.1.</p>
</li>
<li>
<p>Update <code>cargo-shear@latest</code> to 1.4.0.</p>
</li>
<li>
<p>Update <code>cyclonedx@latest</code> to 0.29.0.</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md">taiki-e/install-action's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this project will be documented in this
file.</p>
<p>This project adheres to <a href="https://semver.org">Semantic
Versioning</a>.</p>
<!-- raw HTML omitted -->
<h2>[Unreleased]</h2>
<h2>[2.56.19] - 2025-07-19</h2>
<ul>
<li>Update <code>cargo-llvm-cov@latest</code> to 0.6.18.</li>
</ul>
<h2>[2.56.18] - 2025-07-19</h2>
<ul>
<li>Update <code>just@latest</code> to 1.42.3.</li>
</ul>
<h2>[2.56.17] - 2025-07-18</h2>
<ul>
<li>Update <code>wasmtime@latest</code> to 34.0.2.</li>
</ul>
<h2>[2.56.16] - 2025-07-18</h2>
<ul>
<li>
<p>Update <code>cargo-zigbuild@latest</code> to 0.20.1.</p>
</li>
<li>
<p>Update <code>cargo-lambda@latest</code> to 1.8.6.</p>
</li>
<li>
<p>Update <code>vacuum@latest</code> to 0.17.6.</p>
</li>
<li>
<p>Update <code>earthly@latest</code> to 0.8.16.</p>
</li>
</ul>
<h2>[2.56.15] - 2025-07-16</h2>
<ul>
<li>
<p>Fix <code>cargo-valgrind</code> installation error due to their tag
rename.</p>
</li>
<li>
<p>Update <code>cargo-valgrind@latest</code> to 2.3.2.</p>
</li>
<li>
<p>Update <code>just@latest</code> to 1.42.2.</p>
</li>
</ul>
<h2>[2.56.14] - 2025-07-15</h2>
<ul>
<li>
<p>Update <code>zola@latest</code> to 0.21.0.</p>
</li>
<li>
<p>Update <code>wait-for-them@latest</code> to 0.5.1.</p>
</li>
<li>
<p>Update <code>mdbook@latest</code> to 0.4.52.</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="c99cc51b30"><code>c99cc51</code></a>
Release 2.56.19</li>
<li><a
href="35aa282e0f"><code>35aa282</code></a>
Update <code>cargo-llvm-cov@latest</code> to 0.6.18</li>
<li><a
href="8962e8bc90"><code>8962e8b</code></a>
Release 2.56.18</li>
<li><a
href="86ed27786e"><code>86ed277</code></a>
Update <code>just@latest</code> to 1.42.3</li>
<li><a
href="d8fcd11e5f"><code>d8fcd11</code></a>
Release 2.56.17</li>
<li><a
href="03efa19be6"><code>03efa19</code></a>
Update readme</li>
<li><a
href="14dc975de9"><code>14dc975</code></a>
ci: Fix debian 10 setup</li>
<li><a
href="00c7072f52"><code>00c7072</code></a>
Update wasmtime manifest</li>
<li><a
href="8520ed0913"><code>8520ed0</code></a>
Release 2.56.16</li>
<li><a
href="56de642f63"><code>56de642</code></a>
Update <code>cargo-zigbuild@latest</code> to 0.20.1</li>
<li>Additional commits viewable in <a
href="c07504cae0...c99cc51b30">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=taiki-e/install-action&package-manager=github_actions&previous-version=2.56.13&new-version=2.56.19)](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>
2025-07-21 13:27:03 -07:00
dependabot[bot]
c8d5d385b3 build(deps): bump release-plz/action from 0.5.108 to 0.5.109 (#2008)
Bumps [release-plz/action](https://github.com/release-plz/action) from
0.5.108 to 0.5.109.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/release-plz/action/releases">release-plz/action's
releases</a>.</em></p>
<blockquote>
<h2>v0.5.109</h2>
<h2>What's Changed</h2>
<ul>
<li>Update to 0.3.138 by <a
href="https://github.com/marcoieni"><code>@​marcoieni</code></a> in <a
href="https://redirect.github.com/release-plz/action/pull/210">release-plz/action#210</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/release-plz/action/compare/v0.5...v0.5.109">https://github.com/release-plz/action/compare/v0.5...v0.5.109</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="068d76d2aa"><code>068d76d</code></a>
Update to 0.3.138 (<a
href="https://redirect.github.com/release-plz/action/issues/210">#210</a>)</li>
<li>See full diff in <a
href="8724d33cd9...068d76d2aa">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=release-plz/action&package-manager=github_actions&previous-version=0.5.108&new-version=0.5.109)](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>
2025-07-21 13:26:38 -07:00
dependabot[bot]
3fe3fe316e build(deps): bump marocchino/sticky-pull-request-comment from 2.9.3 to 2.9.4 (#2011)
Bumps
[marocchino/sticky-pull-request-comment](https://github.com/marocchino/sticky-pull-request-comment)
from 2.9.3 to 2.9.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/marocchino/sticky-pull-request-comment/releases">marocchino/sticky-pull-request-comment's
releases</a>.</em></p>
<blockquote>
<h2>v2.9.4</h2>
<h2>What's Changed</h2>
<ul>
<li>build(deps-dev): Bump <code>@​biomejs/biome</code> from 2.0.0 to
2.0.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/pull/1554">marocchino/sticky-pull-request-comment#1554</a></li>
<li>build(deps-dev): Bump <code>@​types/node</code> from 24.0.3 to
24.0.11 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/pull/1561">marocchino/sticky-pull-request-comment#1561</a></li>
<li>build(deps-dev): Bump <code>@​biomejs/biome</code> from 2.0.4 to
2.1.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/pull/1562">marocchino/sticky-pull-request-comment#1562</a></li>
<li>build(deps-dev): Bump <code>@​types/node</code> from 24.0.11 to
24.0.12 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/pull/1563">marocchino/sticky-pull-request-comment#1563</a></li>
<li>build(deps-dev): Bump <code>@​types/node</code> from 24.0.12 to
24.0.13 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/pull/1564">marocchino/sticky-pull-request-comment#1564</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/marocchino/sticky-pull-request-comment/compare/v2.9.3...v2.9.4">https://github.com/marocchino/sticky-pull-request-comment/compare/v2.9.3...v2.9.4</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="773744901b"><code>7737449</code></a>
📦️ Build</li>
<li><a
href="8b423c6fac"><code>8b423c6</code></a>
Merge pull request <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/issues/1564">#1564</a>
from marocchino/dependabot/npm_and_yarn/types/node-2...</li>
<li><a
href="3ac8a744e4"><code>3ac8a74</code></a>
build(deps-dev): Bump <code>@​types/node</code> from 24.0.12 to
24.0.13</li>
<li><a
href="e430cfc2fc"><code>e430cfc</code></a>
Merge pull request <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/issues/1563">#1563</a>
from marocchino/dependabot/npm_and_yarn/types/node-2...</li>
<li><a
href="99f9378b47"><code>99f9378</code></a>
build(deps-dev): Bump <code>@​types/node</code> from 24.0.11 to
24.0.12</li>
<li><a
href="2216b3aa26"><code>2216b3a</code></a>
Merge pull request <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/issues/1562">#1562</a>
from marocchino/dependabot/npm_and_yarn/biomejs/biom...</li>
<li><a
href="482d7fd7d6"><code>482d7fd</code></a>
build(deps-dev): Bump <code>@​biomejs/biome</code> from 2.0.4 to
2.1.1</li>
<li><a
href="c2da581914"><code>c2da581</code></a>
Merge pull request <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/issues/1561">#1561</a>
from marocchino/dependabot/npm_and_yarn/types/node-2...</li>
<li><a
href="76f84622bc"><code>76f8462</code></a>
build(deps-dev): Bump <code>@​types/node</code> from 24.0.3 to
24.0.11</li>
<li><a
href="246151aa30"><code>246151a</code></a>
⬆️ Update biome</li>
<li>Additional commits viewable in <a
href="d2ad0de260...773744901b">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=marocchino/sticky-pull-request-comment&package-manager=github_actions&previous-version=2.9.3&new-version=2.9.4)](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>
2025-07-21 13:25:49 -07:00
dependabot[bot]
0f10944ca0 build(deps): bump strum from 0.27.1 to 0.27.2 (#2009)
Bumps [strum](https://github.com/Peternator7/strum) from 0.27.1 to
0.27.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/Peternator7/strum/releases">strum's
releases</a>.</em></p>
<blockquote>
<h2>v0.27.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Adding support for doc comments on <code>EnumDiscriminants</code>
generated type… by <a
href="https://github.com/linclelinkpart5"><code>@​linclelinkpart5</code></a>
in <a
href="https://redirect.github.com/Peternator7/strum/pull/141">Peternator7/strum#141</a></li>
<li>Drop needless <code>rustversion</code> dependency by <a
href="https://github.com/paolobarbolini"><code>@​paolobarbolini</code></a>
in <a
href="https://redirect.github.com/Peternator7/strum/pull/446">Peternator7/strum#446</a></li>
<li>Upgrade <code>phf</code> to v0.12 by <a
href="https://github.com/paolobarbolini"><code>@​paolobarbolini</code></a>
in <a
href="https://redirect.github.com/Peternator7/strum/pull/448">Peternator7/strum#448</a></li>
<li>allow discriminants on empty enum by <a
href="https://github.com/crop2000"><code>@​crop2000</code></a> in <a
href="https://redirect.github.com/Peternator7/strum/pull/435">Peternator7/strum#435</a></li>
<li>Remove broken link to EnumTable docs by <a
href="https://github.com/schneems"><code>@​schneems</code></a> in <a
href="https://redirect.github.com/Peternator7/strum/pull/427">Peternator7/strum#427</a></li>
<li>Change enum table callbacks to FnMut. by <a
href="https://github.com/ClaytonKnittel"><code>@​ClaytonKnittel</code></a>
in <a
href="https://redirect.github.com/Peternator7/strum/pull/443">Peternator7/strum#443</a></li>
<li>Add <code>#[automatically_derived]</code> to the <code>impl</code>s
by <a
href="https://github.com/dandedotdev"><code>@​dandedotdev</code></a> in
<a
href="https://redirect.github.com/Peternator7/strum/pull/444">Peternator7/strum#444</a></li>
<li>Implement a <code>suffix</code> attribute for serialization of enum
variants by <a
href="https://github.com/amogh-dambal"><code>@​amogh-dambal</code></a>
in <a
href="https://redirect.github.com/Peternator7/strum/pull/440">Peternator7/strum#440</a></li>
<li>Expound upon use_phf docs by <a
href="https://github.com/Peternator7"><code>@​Peternator7</code></a> in
<a
href="https://redirect.github.com/Peternator7/strum/pull/449">Peternator7/strum#449</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/paolobarbolini"><code>@​paolobarbolini</code></a>
made their first contribution in <a
href="https://redirect.github.com/Peternator7/strum/pull/446">Peternator7/strum#446</a></li>
<li><a href="https://github.com/crop2000"><code>@​crop2000</code></a>
made their first contribution in <a
href="https://redirect.github.com/Peternator7/strum/pull/435">Peternator7/strum#435</a></li>
<li><a href="https://github.com/schneems"><code>@​schneems</code></a>
made their first contribution in <a
href="https://redirect.github.com/Peternator7/strum/pull/427">Peternator7/strum#427</a></li>
<li><a
href="https://github.com/ClaytonKnittel"><code>@​ClaytonKnittel</code></a>
made their first contribution in <a
href="https://redirect.github.com/Peternator7/strum/pull/443">Peternator7/strum#443</a></li>
<li><a
href="https://github.com/dandedotdev"><code>@​dandedotdev</code></a>
made their first contribution in <a
href="https://redirect.github.com/Peternator7/strum/pull/444">Peternator7/strum#444</a></li>
<li><a
href="https://github.com/amogh-dambal"><code>@​amogh-dambal</code></a>
made their first contribution in <a
href="https://redirect.github.com/Peternator7/strum/pull/440">Peternator7/strum#440</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/Peternator7/strum/compare/v0.27.1...v0.27.2">https://github.com/Peternator7/strum/compare/v0.27.1...v0.27.2</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/Peternator7/strum/blob/master/CHANGELOG.md">strum's
changelog</a>.</em></p>
<blockquote>
<h2>0.27.2</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/Peternator7/strum/pull/141">#141</a>:
Adding support for doc comments on <code>EnumDiscriminants</code>
generated type.</p>
<ul>
<li>The doc comment will be copied from the variant on the type
itself.</li>
</ul>
</li>
<li>
<p><a
href="https://redirect.github.com/Peternator7/strum/pull/435">#435</a>:allow
discriminants on empty enum.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/Peternator7/strum/pull/443">#443</a>:
Change enum table callbacks to FnMut.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/Peternator7/strum/pull/444">#444</a>:
Add <code>#[automatically_derived]</code> to the <code>impl</code>s by
<a href="https://github.com/dandedotdev"><code>@​dandedotdev</code></a>
in <a
href="https://redirect.github.com/Peternator7/strum/pull/444">Peternator7/strum#444</a></p>
<ul>
<li>This should make the linter less noisy with warnings in generated
code.</li>
</ul>
</li>
<li>
<p><a
href="https://redirect.github.com/Peternator7/strum/pull/440">#440</a>:
Implement a <code>suffix</code> attribute for serialization of enum
variants.</p>
<pre lang="rust"><code>#[derive(strum::Display)]
#[strum(suffix=&quot;.json&quot;)]
#[strum(serialize_all=&quot;snake_case&quot;)]
enum StorageConfiguration {
  PostgresProvider,
  S3StorageProvider,
  AzureStorageProvider,
}
<p>fn main() {
let response = SurveyResponse::Other(&quot;It was good&quot;.into());
println!(&quot;Loading configuration from: {}&quot;,
StorageConfiguration::PostgresProvider);
// prints: Loaded Configuration from: postgres_provider.json
}
</code></pre></p>
</li>
<li>
<p><a
href="https://redirect.github.com/Peternator7/strum/pull/446">#446</a>:
Drop needless <code>rustversion</code> dependency.</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="38f66210e7"><code>38f6621</code></a>
Expound upon use_phf docs (<a
href="https://redirect.github.com/Peternator7/strum/issues/449">#449</a>)</li>
<li><a
href="bb1339026b"><code>bb13390</code></a>
Implement a <code>suffix</code> attribute for serialization of enum
variants (<a
href="https://redirect.github.com/Peternator7/strum/issues/440">#440</a>)</li>
<li><a
href="c9e52bfd28"><code>c9e52bf</code></a>
Add <code>#[automatically_derived]</code> to the <code>impl</code>s (<a
href="https://redirect.github.com/Peternator7/strum/issues/444">#444</a>)</li>
<li><a
href="1b00f899e5"><code>1b00f89</code></a>
Change enum table callbacks to FnMut. (<a
href="https://redirect.github.com/Peternator7/strum/issues/443">#443</a>)</li>
<li><a
href="6e2ca25fba"><code>6e2ca25</code></a>
Remove broken link to EnumTable docs (<a
href="https://redirect.github.com/Peternator7/strum/issues/427">#427</a>)</li>
<li><a
href="9503781141"><code>9503781</code></a>
allow discriminants on empty enum (<a
href="https://redirect.github.com/Peternator7/strum/issues/435">#435</a>)</li>
<li><a
href="8553ba2845"><code>8553ba2</code></a>
Upgrade <code>phf</code> to v0.12 (<a
href="https://redirect.github.com/Peternator7/strum/issues/448">#448</a>)</li>
<li><a
href="2eba5c2a5c"><code>2eba5c2</code></a>
Drop needless <code>rustversion</code> dependency (<a
href="https://redirect.github.com/Peternator7/strum/issues/446">#446</a>)</li>
<li><a
href="f301b67d91"><code>f301b67</code></a>
Merge branch 'linclelinkpart5-master-2'</li>
<li><a
href="455b2bf859"><code>455b2bf</code></a>
Merge branch 'master' of <a
href="https://github.com/linclelinkpart5/strum">https://github.com/linclelinkpart5/strum</a>
into lincle...</li>
<li>See full diff in <a
href="https://github.com/Peternator7/strum/compare/v0.27.1...v0.27.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=strum&package-manager=cargo&previous-version=0.27.1&new-version=0.27.2)](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>
2025-07-21 21:55:41 +02:00
dependabot[bot]
1bd3181d8e build(deps): bump instability from 0.3.7 to 0.3.9 (#2007)
Bumps [instability](https://github.com/ratatui/instability) from 0.3.7
to 0.3.9.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ratatui/instability/releases">instability's
releases</a>.</em></p>
<blockquote>
<h2>instability-example-v0.3.9</h2>
<h3>Other</h3>
<ul>
<li>Add #[allow(unused_imports)] lint to unstable reexports (<a
href="https://redirect.github.com/ratatui/instability/pull/21">#21</a>)</li>
</ul>
<h2>instability-v0.3.9</h2>
<h3>Other</h3>
<ul>
<li>split release jobs, fixup permissions (<a
href="https://redirect.github.com/ratatui/instability/pull/28">#28</a>)</li>
</ul>
<h2>instability-example-v0.3.8</h2>
<h3>Other</h3>
<ul>
<li>Add #[allow(unused_imports)] lint to unstable reexports (<a
href="https://redirect.github.com/ratatui/instability/pull/21">#21</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ratatui/instability/blob/main/CHANGELOG.md">instability's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/ratatui/instability/compare/instability-v0.3.8...instability-v0.3.9">0.3.9</a>
- 2025-07-18</h2>
<h3>Other</h3>
<ul>
<li>split release jobs, fixup permissions (<a
href="https://redirect.github.com/ratatui/instability/pull/28">#28</a>)</li>
</ul>
<h2><a
href="https://github.com/ratatui/instability/compare/instability-v0.3.7...instability-v0.3.8">0.3.8</a>
- 2025-07-11</h2>
<h3>Added</h3>
<ul>
<li>Add <code>instability_disable_unstable_docs</code> RUSTFLAGS (<a
href="https://redirect.github.com/ratatui/instability/pull/23">#23</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="4cf23b4370"><code>4cf23b4</code></a>
chore: release v0.3.9 (<a
href="https://redirect.github.com/ratatui/instability/issues/29">#29</a>)</li>
<li><a
href="d88afd18b7"><code>d88afd1</code></a>
ci: split release jobs, fixup permissions (<a
href="https://redirect.github.com/ratatui/instability/issues/28">#28</a>)</li>
<li><a
href="f88acb150e"><code>f88acb1</code></a>
ci: configure trusted publishing (<a
href="https://redirect.github.com/ratatui/instability/issues/27">#27</a>)</li>
<li><a
href="5bf7467800"><code>5bf7467</code></a>
chore: replace ratatui-org with ratatui everywhere (<a
href="https://redirect.github.com/ratatui/instability/issues/26">#26</a>)</li>
<li><a
href="d0fc40644f"><code>d0fc406</code></a>
chore: release v0.3.8 (<a
href="https://redirect.github.com/ratatui/instability/issues/24">#24</a>)</li>
<li><a
href="4d899fea50"><code>4d899fe</code></a>
feat: Add <code>instability_disable_unstable_docs</code> RUSTFLAGS (<a
href="https://redirect.github.com/ratatui/instability/issues/23">#23</a>)</li>
<li>See full diff in <a
href="https://github.com/ratatui/instability/compare/instability-v0.3.7...instability-v0.3.9">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=instability&package-manager=cargo&previous-version=0.3.7&new-version=0.3.9)](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>
2025-07-21 21:53:00 +02:00
dependabot[bot]
e04fef7354 build(deps): bump serde_json from 1.0.140 to 1.0.141 (#2005)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.140 to
1.0.141.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/serde-rs/json/releases">serde_json's
releases</a>.</em></p>
<blockquote>
<h2>v1.0.141</h2>
<ul>
<li>Optimize string escaping during serialization (<a
href="https://redirect.github.com/serde-rs/json/issues/1273">#1273</a>,
thanks <a
href="https://github.com/conradludgate"><code>@​conradludgate</code></a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="6843c3660e"><code>6843c36</code></a>
Release 1.0.141</li>
<li><a
href="6e2c21063a"><code>6e2c210</code></a>
Touch up PR 1273</li>
<li><a
href="623d9b47cf"><code>623d9b4</code></a>
Merge pull request <a
href="https://redirect.github.com/serde-rs/json/issues/1273">#1273</a>
from conradludgate/optimise-string-escaping</li>
<li><a
href="de70b7db1f"><code>de70b7d</code></a>
use unreachable_unchecked for escape table. use a second match to
roundtrip E...</li>
<li><a
href="f2d940dd54"><code>f2d940d</code></a>
replace start index with bytes slice reference</li>
<li><a
href="cd55b5a0ff"><code>cd55b5a</code></a>
Ignore mismatched_lifetime_syntaxes lint</li>
<li><a
href="c1826ebccc"><code>c1826eb</code></a>
Pin nightly toolchain used for miri job</li>
<li><a
href="8a56cfa6d0"><code>8a56cfa</code></a>
Merge pull request <a
href="https://redirect.github.com/serde-rs/json/issues/1248">#1248</a>
from jimmycathy/master</li>
<li><a
href="af3d80de56"><code>af3d80d</code></a>
chore: fix typo</li>
<li>See full diff in <a
href="https://github.com/serde-rs/json/compare/v1.0.140...v1.0.141">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=serde_json&package-manager=cargo&previous-version=1.0.140&new-version=1.0.141)](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>
2025-07-21 22:34:26 +03:00
dependabot[bot]
bbca3870d5 build(deps): bump rand from 0.9.1 to 0.9.2 (#2010)
Bumps [rand](https://github.com/rust-random/rand) from 0.9.1 to 0.9.2.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rust-random/rand/blob/master/CHANGELOG.md">rand's
changelog</a>.</em></p>
<blockquote>
<h2>[0.9.2 — 2025-07-20]</h2>
<h3>Deprecated</h3>
<ul>
<li>Deprecate <code>rand::rngs::mock</code> module and
<code>StepRng</code> generator (<a
href="https://redirect.github.com/rust-random/rand/issues/1634">#1634</a>)</li>
</ul>
<h3>Additions</h3>
<ul>
<li>Enable <code>WeightedIndex&lt;usize&gt;</code> (de)serialization (<a
href="https://redirect.github.com/rust-random/rand/issues/1646">#1646</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="d3dd415705"><code>d3dd415</code></a>
Prepare rand_core 0.9.2 (<a
href="https://redirect.github.com/rust-random/rand/issues/1605">#1605</a>)</li>
<li><a
href="99fabd20e9"><code>99fabd2</code></a>
Prepare rand_core 0.9.2</li>
<li><a
href="c7fe1c43b5"><code>c7fe1c4</code></a>
rand: re-export <code>rand_core</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1604">#1604</a>)</li>
<li><a
href="db2b1e0bb4"><code>db2b1e0</code></a>
rand: re-export <code>rand_core</code></li>
<li><a
href="ee1d96f9f5"><code>ee1d96f</code></a>
rand_core: implement reborrow for <code>UnwrapMut</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1595">#1595</a>)</li>
<li><a
href="e0eb2ee0fc"><code>e0eb2ee</code></a>
rand_core: implement reborrow for <code>UnwrapMut</code></li>
<li><a
href="975f602f5d"><code>975f602</code></a>
fixup clippy 1.85 warnings</li>
<li><a
href="775b05be1b"><code>775b05b</code></a>
Relax <code>Sized</code> requirements for blanket impls (<a
href="https://redirect.github.com/rust-random/rand/issues/1593">#1593</a>)</li>
<li>See full diff in <a
href="https://github.com/rust-random/rand/compare/rand_core-0.9.1...rand_core-0.9.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rand&package-manager=cargo&previous-version=0.9.1&new-version=0.9.2)](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>
2025-07-21 22:33:35 +03:00
Austin Schey
1fe64de09a fix: include underline color in anstyle conversion (#2004)
Underline color wasn't included in the style conversion logic.
2025-07-20 17:07:29 -07:00
kenji
72334ed61c docs(layout): Update documentation to point to kasuari solver (#2003) 2025-07-20 13:07:20 -07:00
Jagoda Estera Ślązak
c1b8528b69 fix: panic when rendering widgets on too small buffer (#1996)
Fixes panic on overflow on horizontal `Barchart` and `RatatuiMascot` and adds proper tests to all widgets.

---------

Co-authored-by: Orhun Parmaksız <orhun@archlinux.org>
2025-07-20 21:39:18 +02:00
Dheepak Krishnamurthy
cfebd68e18 chore: Fix typo in CONTRIBUTING.md (#2001)
Removes a hanging opening code block:

````
```suggestion
````
2025-07-20 06:11:27 +02:00
Josh McKinney
572749f388 chore: update contributing guidelines and add copilot-instructions (#1998)
See
https://docs.github.com/en/copilot/how-tos/agents/copilot-code-review/using-copilot-code-review

These instructions will be used by copilot when it performs automated PR
reviews, and helps provide guardrails for our standards. Over time we
might grow these to capture any consistent problems that we start seeing
when reviewing.

---------

Co-authored-by: Orhun Parmaksız <orhun@archlinux.org>
2025-07-19 16:02:13 -07:00
David Tolnay
cdf189e15a build(deps): allow using ratatui with unicode-width 0.2.1 (#1999)
According to the issue linked from Cargo.toml, the intention has been to
manually upgrade unicode-width every now and then, after verifying that
the new version does not disrupt Ratatui. See
https://github.com/ratatui/ratatui/issues/1271#issuecomment-2373917195.

This PR unblocks using Ratatui in a dependency graph where some other
crate requires some of the fixes that landed in unicode-width 0.2.1.
Without this PR, Cargo blocks Ratatui from being used.

```console
error: failed to select a version for `unicode-width`.
    ... required by package `ratatui v0.29.0`
    ... which satisfies dependency `ratatui = "^0.29"` of package `cargo v0.91.0`
    ... which satisfies path dependency `cargo` (locked to 0.91.0) of package `repro v0.0.0`
versions that meet the requirements `=0.2.0` are: 0.2.0

all possible versions conflict with previously selected packages.

  previously selected package `unicode-width v0.2.1`
    ... which satisfies dependency `unicode-width = "^0.2.1"` of package `cargo v0.91.0`
    ... which satisfies path dependency `cargo` (locked to 0.91.0) of package `repro v0.0.0`

failed to select a version for `unicode-width` which could resolve this conflict
```
2025-07-19 15:23:20 -07:00
Tyler Breisacher
98f85b8650 docs: update link to scrollable widgets RFC (#1994) 2025-07-16 07:19:46 +02:00
Josh McKinney
0148b62f0c chore: remove cargo_metadata dep from xtask (#1993)
Removed due to hard N-2 MSRV requirement, whereas we use a soft N-2
(only update when necessary). This makes it painful to be able to test
our actual msrv (as the xtask has to be built with the version that its
deps support, while still wanting to check the msrv version, so we'd end
up with 2 versions in the one CI task and this would get annoying to
check).

See https://github.com/rust-lang/cargo/issues/15746 for more details.

Partially implements #1820 - mainly as a problem solution rather than a
specific goal to use cargo-hack
2025-07-15 15:05:18 -07:00
Josh McKinney
9bc573931c chore: remove clap-cargo from xtask (#1992)
Removed to avoid needing to bump our msrv. See
https://github.com/rust-lang/cargo/issues/15746\#issuecomment-3071774343
for more details.
2025-07-14 21:54:38 -07:00
dependabot[bot]
64e6d1953c build(deps): bump rust-lang/crates-io-auth-action from 1.0.0 to 1.0.1 (#1989)
[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rust-lang/crates-io-auth-action&package-manager=github_actions&previous-version=1.0.0&new-version=1.0.1)](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>
Co-authored-by: Orhun Parmaksız <orhun@archlinux.org>
2025-07-14 13:38:55 -07:00
dependabot[bot]
c0148a00fe build(deps): bump taiki-e/install-action from 2.56.7 to 2.56.13 (#1991)
[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=taiki-e/install-action&package-manager=github_actions&previous-version=2.56.7&new-version=2.56.13)](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>
2025-07-14 13:30:49 -07:00
dependabot[bot]
44727c7b4a build(deps): bump cargo_metadata from 0.20.0 to 0.21.0 (#1987)
Bumps [cargo_metadata](https://github.com/oli-obk/cargo_metadata) from
0.20.0 to 0.21.0.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="70018a8681"><code>70018a8</code></a>
Update main.yml</li>
<li><a
href="523cfbe7f0"><code>523cfbe</code></a>
bump msrv</li>
<li><a
href="b1a61d390f"><code>b1a61d3</code></a>
bump version</li>
<li><a
href="0f300d5bff"><code>0f300d5</code></a>
Merge pull request <a
href="https://redirect.github.com/oli-obk/cargo_metadata/issues/295">#295</a>
from smoelius/update-cargo-util-schemas</li>
<li><a
href="4b28226e26"><code>4b28226</code></a>
Merge pull request <a
href="https://redirect.github.com/oli-obk/cargo_metadata/issues/300">#300</a>
from joeferner/fix-node-dep-name</li>
<li><a
href="bcd2373560"><code>bcd2373</code></a>
fix clippy errors</li>
<li><a
href="73aaebb077"><code>73aaebb</code></a>
fixes &quot;package name cannot be empty&quot; under
&quot;resolve/nodes/deps/name&quot;</li>
<li><a
href="f9a67c2023"><code>f9a67c2</code></a>
Merge pull request <a
href="https://redirect.github.com/oli-obk/cargo_metadata/issues/298">#298</a>
from kornelski/result</li>
<li><a
href="289e1173da"><code>289e117</code></a>
Make Result alias wildcard-import-proof</li>
<li><a
href="a9147a68d0"><code>a9147a6</code></a>
Merge pull request <a
href="https://redirect.github.com/oli-obk/cargo_metadata/issues/297">#297</a>
from ranger-ross/build-dir-unstable-support</li>
<li>Additional commits viewable in <a
href="https://github.com/oli-obk/cargo_metadata/compare/0.20.0...0.21.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=cargo_metadata&package-manager=cargo&previous-version=0.20.0&new-version=0.21.0)](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>
2025-07-14 11:35:41 -07:00
dependabot[bot]
1e9d1253f9 build(deps): bump clap from 4.5.40 to 4.5.41 (#1988)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.40 to 4.5.41.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/clap-rs/clap/blob/master/CHANGELOG.md">clap's
changelog</a>.</em></p>
<blockquote>
<h2>[4.5.41] - 2025-07-09</h2>
<h3>Features</h3>
<ul>
<li>Add <code>Styles::context</code> and
<code>Styles::context_value</code> to customize the styling of
<code>[default: value]</code> like notes in the <code>--help</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="92fcd83b76"><code>92fcd83</code></a>
chore: Release</li>
<li><a
href="aca91b99c1"><code>aca91b9</code></a>
docs: Update changelog</li>
<li><a
href="8434510cee"><code>8434510</code></a>
Merge pull request <a
href="https://redirect.github.com/clap-rs/clap/issues/5869">#5869</a>
from tw4452852/patch-1</li>
<li><a
href="33b1fc304e"><code>33b1fc3</code></a>
fix(complete): Fix env leakage in elvish dynamic completion</li>
<li><a
href="e5f1f4884c"><code>e5f1f48</code></a>
chore: Release</li>
<li><a
href="9466a552fb"><code>9466a55</code></a>
docs: Update changelog</li>
<li><a
href="d74b793512"><code>d74b793</code></a>
Merge pull request <a
href="https://redirect.github.com/clap-rs/clap/issues/5865">#5865</a>
from gifnksm/nushell-completion-value-types</li>
<li><a
href="ecbc775d3b"><code>ecbc775</code></a>
fix(nu): Set argument type based on <code>ValueHint</code></li>
<li><a
href="6784054536"><code>6784054</code></a>
Merge pull request <a
href="https://redirect.github.com/clap-rs/clap/issues/5857">#5857</a>
from epage/empty</li>
<li><a
href="cca5f32b3a"><code>cca5f32</code></a>
test(complete): Show empty option-value behavior</li>
<li>Additional commits viewable in <a
href="https://github.com/clap-rs/clap/compare/clap_complete-v4.5.40...clap_complete-v4.5.41">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=clap&package-manager=cargo&previous-version=4.5.40&new-version=4.5.41)](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>
2025-07-14 11:25:23 -07:00
dependabot[bot]
aae46032bc build(deps): bump trybuild from 1.0.105 to 1.0.106 (#1986)
Bumps [trybuild](https://github.com/dtolnay/trybuild) from 1.0.105 to
1.0.106.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dtolnay/trybuild/releases">trybuild's
releases</a>.</em></p>
<blockquote>
<h2>1.0.106</h2>
<ul>
<li>Update <code>toml</code> dependency to 0.9 (<a
href="https://redirect.github.com/dtolnay/trybuild/issues/306">#306</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="9e3eb039ed"><code>9e3eb03</code></a>
Release 1.0.106</li>
<li><a
href="6d6d3e83ad"><code>6d6d3e8</code></a>
Merge pull request <a
href="https://redirect.github.com/dtolnay/trybuild/issues/306">#306</a>
from dtolnay/toml</li>
<li><a
href="6b77d62236"><code>6b77d62</code></a>
Update to toml 0.9</li>
<li><a
href="f7a9a80fc4"><code>f7a9a80</code></a>
Ignore mismatched_lifetime_syntaxes lint</li>
<li><a
href="51a1a41487"><code>51a1a41</code></a>
Resolve manual_let_else pedantic clippy lint</li>
<li>See full diff in <a
href="https://github.com/dtolnay/trybuild/compare/1.0.105...1.0.106">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=trybuild&package-manager=cargo&previous-version=1.0.105&new-version=1.0.106)](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>
2025-07-14 11:24:57 -07:00
Josh McKinney
0e10170e19 chore: change Borders::NONE to a proper const (#1985)
https://docs.rs/bitflags/latest/bitflags/#zero-bit-flags

> Flags with no bits set should be avoided because they interact
strangely with
[Flags::contains](https://docs.rs/bitflags/latest/bitflags/trait.Flags.html#method.contains)
and
[Flags::intersects](https://docs.rs/bitflags/latest/bitflags/trait.Flags.html#method.intersects).
A zero-bit flag is always contained, but is never intersected. The names
of zero-bit flags can be parsed, but are never formatted.

Removing this simplifies the manual Debug impl that previously had to
check for Borders::NONE and now does not.
2025-07-13 22:56:21 -07:00
Josh McKinney
40e96a2a04 docs(block): add collapsed border example (#1899) 2025-07-13 01:30:57 -07:00
Josh McKinney
02ca5870c5 ci: add environment to release workflow (#1983)
https://github.com/rust-lang/crates.io/issues/11564#issuecomment-3066696820
2025-07-13 01:29:23 -07:00
LitoMore
8e3bd11d60 chore: add svg logo asset (#1967)
Added a shields.io style svg logo and a nicer version of the original logo.
The simplified icon will be added and available directly from shields.io via:
- https://github.com/simple-icons/simple-icons/pull/13593
2025-07-12 17:24:38 -07:00
Jagoda Estera Ślązak
017af11b2b feat: preserve block titles when merging borders (#1977)
Resolves #1939
2025-07-12 15:45:56 -07:00
Josh McKinney
821611f76f ci: use trusted publishing (#1981)
https://blog.rust-lang.org/2025/07/11/crates-io-development-update-2025-07/
https://crates.io/docs/trusted-publishing
2025-07-12 14:11:34 -07:00
Josh McKinney
ae43ea796a chore(cell): use Option instead of space (" ") for symbol (#1974)
This change makes the `Cell::symbol` field an `Option<CompactString>`,
allowing it to represent an empty cell as `None` instead of an empty
string. The rationale for this is to later allow the merge symbol
functionality to act differently based on whether a cell has previously
held a symbol or not, rather than always merging with an empty string.
This will help make it possible to merge borders with titles with spaces
and other symbols, without assuming that an empty string is always
equivalent to no symbol.

- Default is now derived as `Option::None` works correctly.
- PartialEq and Eq implementations are updated to treat `None` the same
  as an empty string.
- merge_symbol against an empty cell will now just set the symbol
  rather than calling MergeStrategy::merge with an empty string.
- PartialEq, and Hash are manually implementated instead of being
  derived, and are updated to treat `None` equal to an empty string.
2025-07-11 21:43:23 +03:00
Shingo OKAWA
5fa342cc52 fix(widgets): fix centered block title truncation (#1973)
Previously block titles that were aligned center were
truncated poorly (aligned to the left, and the last
non-fitting title would be truncated on the left and right.
This now truncates the titles more obviously centered.
2025-07-11 00:19:07 -07:00
John T. Wodder II
08b21fa55c fix: Fix panic when rendering a Paragraph out of bounds (#1670)
Fixes #1667.
2025-07-09 17:37:20 -07:00
dependabot[bot]
32ccf38606 build(deps): bump marocchino/sticky-pull-request-comment from 2.9.1 to 2.9.3 (#1970)
Bumps
[marocchino/sticky-pull-request-comment](https://github.com/marocchino/sticky-pull-request-comment)
from 2.9.1 to 2.9.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/marocchino/sticky-pull-request-comment/releases">marocchino/sticky-pull-request-comment's
releases</a>.</em></p>
<blockquote>
<h2>v2.9.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Update deps (including security issues)</li>
<li>Test with vitest instead of jest</li>
<li>Use biome</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/marocchino/sticky-pull-request-comment/compare/v2.9.2...v2.9.3">https://github.com/marocchino/sticky-pull-request-comment/compare/v2.9.2...v2.9.3</a></p>
<h2>v2.9.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Update <code>@​octokit/graphql-schema</code> &amp; use biome by <a
href="https://github.com/marocchino"><code>@​marocchino</code></a> in <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/pull/1517">marocchino/sticky-pull-request-comment#1517</a></li>
<li>build(deps): Bump undici from 5.28.4 to 5.28.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/pull/1476">marocchino/sticky-pull-request-comment#1476</a></li>
<li>build(deps-dev): Bump <code>@​types/node</code> from 22.10.7 to
22.14.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/pull/1515">marocchino/sticky-pull-request-comment#1515</a></li>
<li>build(deps): Bump <code>@​octokit/request-error</code> from 5.0.1 to
5.1.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/pull/1490">marocchino/sticky-pull-request-comment#1490</a></li>
<li>build(deps): Bump <code>@​octokit/request</code> from 8.1.4 to 8.4.1
by <a href="https://github.com/dependabot"><code>@​dependabot</code></a>
in <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/pull/1494">marocchino/sticky-pull-request-comment#1494</a></li>
<li>build(deps): Bump <code>@​octokit/plugin-paginate-rest</code> from
9.1.2 to 9.2.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/pull/1493">marocchino/sticky-pull-request-comment#1493</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/marocchino/sticky-pull-request-comment/compare/v2.9.1...v2.9.2">https://github.com/marocchino/sticky-pull-request-comment/compare/v2.9.1...v2.9.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="d2ad0de260"><code>d2ad0de</code></a>
📦️ Build</li>
<li><a
href="c6b90f93cb"><code>c6b90f9</code></a>
Merge pull request <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/issues/1553">#1553</a>
from marocchino/dependabot/npm_and_yarn/brace-expans...</li>
<li><a
href="20665dd134"><code>20665dd</code></a>
Merge pull request <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/issues/1550">#1550</a>
from marocchino/dependabot/npm_and_yarn/types/node-2...</li>
<li><a
href="8a03a65ab9"><code>8a03a65</code></a>
build(deps): Bump brace-expansion from 1.1.11 to 1.1.12</li>
<li><a
href="8d4420ab75"><code>8d4420a</code></a>
build(deps-dev): Bump <code>@​types/node</code> from 22.15.30 to
24.0.3</li>
<li><a
href="14ca6a4b49"><code>14ca6a4</code></a>
📦️ Build</li>
<li><a
href="283f17cccc"><code>283f17c</code></a>
🔧 Update biome setting</li>
<li><a
href="0607099d6a"><code>0607099</code></a>
Merge pull request <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/issues/1552">#1552</a>
from marocchino/dependabot/npm_and_yarn/vitest-3.2.4</li>
<li><a
href="9bff39e005"><code>9bff39e</code></a>
Merge pull request <a
href="https://redirect.github.com/marocchino/sticky-pull-request-comment/issues/1551">#1551</a>
from marocchino/dependabot/npm_and_yarn/biomejs/biom...</li>
<li><a
href="f02e40f8ac"><code>f02e40f</code></a>
build(deps-dev): Bump vitest from 3.2.2 to 3.2.4</li>
<li>Additional commits viewable in <a
href="52423e0164...d2ad0de260">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=marocchino/sticky-pull-request-comment&package-manager=github_actions&previous-version=2.9.1&new-version=2.9.3)](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>
2025-07-07 16:41:11 -07:00
dependabot[bot]
04428f6f74 build(deps): bump taiki-e/install-action from 2.55.1 to 2.56.7 (#1971)
Bumps
[taiki-e/install-action](https://github.com/taiki-e/install-action) from
2.55.1 to 2.56.7.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/taiki-e/install-action/releases">taiki-e/install-action's
releases</a>.</em></p>
<blockquote>
<h2>2.56.7</h2>
<ul>
<li>Update <code>shfmt@latest</code> to 3.12.0.</li>
</ul>
<h2>2.56.6</h2>
<ul>
<li>
<p>Update <code>cargo-llvm-cov@latest</code> to 0.6.17.</p>
</li>
<li>
<p>Update <code>cargo-udeps@latest</code> to 0.1.57.</p>
</li>
</ul>
<h2>2.56.5</h2>
<ul>
<li>Update <code>cargo-semver-checks@latest</code> to 0.42.0.</li>
</ul>
<h2>2.56.4</h2>
<ul>
<li>Update <code>cargo-shear@latest</code> to 1.3.3.</li>
</ul>
<h2>2.56.3</h2>
<ul>
<li>Update <code>trivy@latest</code> to 0.64.1.</li>
</ul>
<h2>2.56.2</h2>
<ul>
<li>
<p>Update <code>vacuum@latest</code> to 0.17.2.</p>
</li>
<li>
<p>Update <code>syft@latest</code> to 1.28.0.</p>
</li>
</ul>
<h2>2.56.1</h2>
<ul>
<li>Update <code>release-plz@latest</code> to 0.3.137.</li>
</ul>
<h2>2.56.0</h2>
<ul>
<li>Support <code>coreutils</code>. (<a
href="https://redirect.github.com/taiki-e/install-action/pull/1017">#1017</a>,
thanks <a
href="https://github.com/jayvdb"><code>@​jayvdb</code></a>)</li>
</ul>
<h2>2.55.4</h2>
<ul>
<li>
<p>Update <code>trivy@latest</code> to 0.64.0.</p>
</li>
<li>
<p>Update <code>just@latest</code> to 1.41.0.</p>
</li>
</ul>
<h2>2.55.3</h2>
<ul>
<li>Update <code>dprint@latest</code> to 0.50.1.</li>
</ul>
<h2>2.55.2</h2>
<ul>
<li>
<p>Update <code>zizmor@latest</code> to 1.11.0.</p>
</li>
<li>
<p>Update <code>cargo-dinghy@latest</code> to 0.8.1.</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md">taiki-e/install-action's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this project will be documented in this
file.</p>
<p>This project adheres to <a href="https://semver.org">Semantic
Versioning</a>.</p>
<!-- raw HTML omitted -->
<h2>[Unreleased]</h2>
<ul>
<li>Update <code>vacuum@latest</code> to 0.17.3.</li>
</ul>
<h2>[2.56.7] - 2025-07-07</h2>
<ul>
<li>Update <code>shfmt@latest</code> to 3.12.0.</li>
</ul>
<h2>[2.56.6] - 2025-07-06</h2>
<ul>
<li>
<p>Update <code>cargo-llvm-cov@latest</code> to 0.6.17.</p>
</li>
<li>
<p>Update <code>cargo-udeps@latest</code> to 0.1.57.</p>
</li>
</ul>
<h2>[2.56.5] - 2025-07-06</h2>
<ul>
<li>Update <code>cargo-semver-checks@latest</code> to 0.42.0.</li>
</ul>
<h2>[2.56.4] - 2025-07-05</h2>
<ul>
<li>Update <code>cargo-shear@latest</code> to 1.3.3.</li>
</ul>
<h2>[2.56.3] - 2025-07-03</h2>
<ul>
<li>Update <code>trivy@latest</code> to 0.64.1.</li>
</ul>
<h2>[2.56.2] - 2025-07-02</h2>
<ul>
<li>
<p>Update <code>vacuum@latest</code> to 0.17.2.</p>
</li>
<li>
<p>Update <code>syft@latest</code> to 1.28.0.</p>
</li>
</ul>
<h2>[2.56.1] - 2025-07-01</h2>
<ul>
<li>Update <code>release-plz@latest</code> to 0.3.137.</li>
</ul>
<h2>[2.56.0] - 2025-07-01</h2>
<ul>
<li>Support <code>coreutils</code>. (<a
href="https://redirect.github.com/taiki-e/install-action/pull/1017">#1017</a>,
thanks <a
href="https://github.com/jayvdb"><code>@​jayvdb</code></a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f3a27926ea"><code>f3a2792</code></a>
Release 2.56.7</li>
<li><a
href="fc0c1ad363"><code>fc0c1ad</code></a>
Update <code>shfmt@latest</code> to 3.12.0</li>
<li><a
href="cb148fe1fc"><code>cb148fe</code></a>
Release 2.56.6</li>
<li><a
href="95c7e0aefa"><code>95c7e0a</code></a>
Update <code>cargo-llvm-cov@latest</code> to 0.6.17</li>
<li><a
href="d86483f9ef"><code>d86483f</code></a>
Update <code>cargo-udeps@latest</code> to 0.1.57</li>
<li><a
href="d32ce31795"><code>d32ce31</code></a>
Release 2.56.5</li>
<li><a
href="2119a1ef9a"><code>2119a1e</code></a>
Replace tab with space</li>
<li><a
href="0ae016a18f"><code>0ae016a</code></a>
Update jq to 1.8.1</li>
<li><a
href="9b1aeb740e"><code>9b1aeb7</code></a>
Update <code>cargo-semver-checks@latest</code> to 0.42.0</li>
<li><a
href="e6d2212084"><code>e6d2212</code></a>
Release 2.56.4</li>
<li>Additional commits viewable in <a
href="491d37bbaa...f3a27926ea">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=taiki-e/install-action&package-manager=github_actions&previous-version=2.55.1&new-version=2.56.7)](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>
2025-07-07 16:40:29 -07:00
dependabot[bot]
84427c2857 build(deps): bump tokio from 1.45.1 to 1.46.1 (#1968) 2025-07-07 20:01:54 +02:00
Orhun Parmaksız
ca5a9bb019 chore(changelog): skip dependency updates in changelog (#1966) 2025-07-05 16:29:09 +03:00
Josh McKinney
1f0c2ee18e ci: audit github workflows with zizmor (#1961)
Fixes https://github.com/ratatui/ratatui/issues/1950
2025-07-02 14:51:41 -07:00
dependabot[bot]
6b3323fb10 build(deps): bump kasuari from 0.4.6 to 0.4.7 (#1960)
Bumps [kasuari](https://github.com/ratatui/kasuari) from 0.4.6 to 0.4.7.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ratatui/kasuari/releases">kasuari's
releases</a>.</em></p>
<blockquote>
<h2>v0.4.7</h2>
<h3>Other</h3>
<ul>
<li><em>(gitignore)</em> add <code>.env</code> to
<code>.gitignore</code> (<a
href="https://redirect.github.com/ratatui/kasuari/pull/22">#22</a>)</li>
<li>use variables directly in the <code>format!</code> string (<a
href="https://redirect.github.com/ratatui/kasuari/pull/23">#23</a>)</li>
<li><em>(deps)</em> bump hashbrown from 0.15.3 to 0.15.4 in the
rust-dependencies group (<a
href="https://redirect.github.com/ratatui/kasuari/pull/19">#19</a>)</li>
<li>fix for code scanning alert no. 1: Workflow does not contain
permissions (<a
href="https://redirect.github.com/ratatui/kasuari/pull/20">#20</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ratatui/kasuari/blob/main/CHANGELOG.md">kasuari's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/ratatui/kasuari/compare/v0.4.6...v0.4.7">0.4.7</a>
- 2025-06-27</h2>
<h3>Other</h3>
<ul>
<li><em>(gitignore)</em> add <code>.env</code> to
<code>.gitignore</code> (<a
href="https://redirect.github.com/ratatui/kasuari/pull/22">#22</a>)</li>
<li>use variables directly in the <code>format!</code> string (<a
href="https://redirect.github.com/ratatui/kasuari/pull/23">#23</a>)</li>
<li><em>(deps)</em> bump hashbrown from 0.15.3 to 0.15.4 in the
rust-dependencies group (<a
href="https://redirect.github.com/ratatui/kasuari/pull/19">#19</a>)</li>
<li>fix for code scanning alert no. 1: Workflow does not contain
permissions (<a
href="https://redirect.github.com/ratatui/kasuari/pull/20">#20</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="cf8c01fc18"><code>cf8c01f</code></a>
chore: release v0.4.7 (<a
href="https://redirect.github.com/ratatui/kasuari/issues/21">#21</a>)</li>
<li><a
href="a20b77a8f0"><code>a20b77a</code></a>
chore(gitignore): add <code>.env</code> to <code>.gitignore</code> (<a
href="https://redirect.github.com/ratatui/kasuari/issues/22">#22</a>)</li>
<li><a
href="e4b1bc12c7"><code>e4b1bc1</code></a>
refactor: use variables directly in the <code>format!</code> string (<a
href="https://redirect.github.com/ratatui/kasuari/issues/23">#23</a>)</li>
<li><a
href="78847cf972"><code>78847cf</code></a>
build(deps): bump hashbrown from 0.15.3 to 0.15.4 in the
rust-dependencies gr...</li>
<li><a
href="b918060969"><code>b918060</code></a>
ci: fix for code scanning alert no. 1: Workflow does not contain
permissions ...</li>
<li>See full diff in <a
href="https://github.com/ratatui/kasuari/compare/v0.4.6...v0.4.7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=kasuari&package-manager=cargo&previous-version=0.4.6&new-version=0.4.7)](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>
2025-06-30 15:13:38 -07:00
github-actions[bot]
11c5862728 chore: release (#1866)
## 🤖 New release

* `ratatui-core`: 0.1.0-alpha.5 -> 0.1.0-alpha.6
* `ratatui-crossterm`: 0.1.0-alpha.4 -> 0.1.0-alpha.5
* `ratatui-widgets`: 0.3.0-alpha.4 -> 0.3.0-alpha.5
* `ratatui-macros`: 0.7.0-alpha.3 -> 0.7.0-alpha.4
* `ratatui-termwiz`: 0.1.0-alpha.4 -> 0.1.0-alpha.5
* `ratatui-termion`: 0.1.0-alpha.4 -> 0.1.0-alpha.5
* `ratatui`: 0.30.0-alpha.4 -> 0.30.0-alpha.5

<details><summary><i><b>Changelog</b></i></summary><p>

## `ratatui-core`

<blockquote>

##
[0.30.0-alpha.1](https://github.com/ratatui/ratatui/releases/tag/0.30.0-alpha.1)
- 2025-01-14

0.30.0-alpha.1 is a pre-release of the upcoming 0.30.0 release. It is
intended for testing and
feedback. Please report any issues you encounter. The primary change is
that we've split the crate
into multiple crates. The main crate is now `ratatui-core` and the
widgets are in `ratatui-widgets`.
This allows for widget crates to depend on a stable core crate, and for
the core crate to be used
without widgets.

### Features

-
[56d5e05](56d5e05762)
*(bar)* Update label and text_value to accept Into<> by @Emivvvvv in
[#1471](https://github.com/ratatui/ratatui/pull/1471) [**breaking**]

-
[b76ad3b](b76ad3b02e)
*(bar)* Impl Styled for Bar by @Emivvvvv in
[#1476](https://github.com/ratatui/ratatui/pull/1476)

-
[369b18e](369b18eef2)
*(barchart)* Reduce barchart creation verbosity by @Emivvvvv in
[#1453](https://github.com/ratatui/ratatui/pull/1453)

-
[9275d34](9275d3421c)
*(layout)* Add Offset::new() constructor by @joshka in
[#1547](https://github.com/ratatui/ratatui/pull/1547)

-
[ff729b7](ff729b7607)
*(scrollbar)* Support retrieving the current position of state by @orhun
in [#1552](https://github.com/ratatui/ratatui/pull/1552)

-
[ce4856a](ce4856a65f)
*(widgets)* Add the missing constructor to canvas types by @orhun in
[#1538](https://github.com/ratatui/ratatui/pull/1538)

-
[50ba965](50ba96518f)
*(uncategorized)* Add a new RatatuiMascot widget by @Its-Just-Nans in
[#1584](https://github.com/ratatui/ratatui/pull/1584)

-
[1d28c89](1d28c89fe5)
*(uncategorized)* Add conversions for anstyle by @joshka in
[#1581](https://github.com/ratatui/ratatui/pull/1581)

### Bug Fixes

-
[860e48b](860e48b0f0)
*(buffer)* Buffer::get_pos() now correctly handles index > u16::MAX by
@joshka in [#1447](https://github.com/ratatui/ratatui/pull/1447)

-
[ec30390](ec30390446)
*(canvas)* Round coordinates to nearest grid cell by @joshka in
[#1507](https://github.com/ratatui/ratatui/pull/1507)

-
[afd1ce1](afd1ce179b)
*(canvas)* Lines that start outside the visible grid are now drawn by
@renesat in [#1501](https://github.com/ratatui/ratatui/pull/1501)

-
[18e70d3](18e70d3d51)
*(crossterm)* Terminal should keep Bold when removing Dim by @MarSik in
[#1541](https://github.com/ratatui/ratatui/pull/1541)

-
[a692a6e](a692a6e371)
*(lint)* Apply rust 1.84 clippy suggestions by @joshka in
[#1612](https://github.com/ratatui/ratatui/pull/1612)

-
[3b13240](3b13240728)
*(scrollbar)* Check for area.is_empty() before rendering by @farmeroy in
[#1529](https://github.com/ratatui/ratatui/pull/1529)

-
[f57b696](f57b696fdc)
*(span)* Dont render control characters by @EdJoPaTo in
[#1312](https://github.com/ratatui/ratatui/pull/1312)

-
[2ce958e](2ce958e38c)
*(table)* Allow display of additional table row, if row height > 1 by
@Lunderberg in [#1452](https://github.com/ratatui/ratatui/pull/1452)

-
[2892bdd](2892bddce6)
*(uncategorized)* Rust 1.83 clippy lints by @joshka in
[#1527](https://github.com/ratatui/ratatui/pull/1527)

-
[36e2d1b](36e2d1bda1)
*(uncategorized)* Add feature(doc_cfg) when generating docs by @joshka
in [#1506](https://github.com/ratatui/ratatui/pull/1506)

-
[4d7704f](4d7704fba5)
*(uncategorized)* Make StatefulWidget and Ref work with unsized State by
@thscharler in [#1505](https://github.com/ratatui/ratatui/pull/1505)

-
[7b87509](7b875091e1)
*(uncategorized)* Typo by @marcoieni in
[#1480](https://github.com/ratatui/ratatui/pull/1480)

### Refactor

-
[f132fa1](f132fa1715)
*(table)* Small readability improvements by @joshka in
[#1510](https://github.com/ratatui/ratatui/pull/1510)

-
[904b0aa](904b0aa723)
*(uncategorized)* Move symbols to modules by @joshka in
[#1594](https://github.com/ratatui/ratatui/pull/1594)

-
[7c8573f](7c8573f575)
*(uncategorized)* Rearrange selection_spacing code by @raylu in
[#1540](https://github.com/ratatui/ratatui/pull/1540)

-
[217c57c](217c57cd60)
*(uncategorized)* Modularize backends by @orhun in
[#1508](https://github.com/ratatui/ratatui/pull/1508)

-
[e461b72](e461b724a6)
*(uncategorized)* Move {Stateful,}Widget{,Ref} types into individual
files by @joshka in
[#1479](https://github.com/ratatui/ratatui/pull/1479)

### Documentation

-
[d291042](d291042e69)
*(block)* Revise the block example by @orhun in
[#1520](https://github.com/ratatui/ratatui/pull/1520)

-
[fcde9cb](fcde9cb9c3)
*(changelog)* Fix typo by @orhun in
[#1463](https://github.com/ratatui/ratatui/pull/1463)

-
[3ae6bf1](3ae6bf1d6f)
*(contributing)* Use cargo-xtask for instructions by @orhun in
[#1509](https://github.com/ratatui/ratatui/pull/1509)

-
[04e1b32](04e1b32cd2)
*(layout)* Rename cassowary-rs references to cassowary by @miroim in
[#1448](https://github.com/ratatui/ratatui/pull/1448)

-
[088aac1](088aac136d)
*(readme)* Tweak links and badges by @joshka in
[#1598](https://github.com/ratatui/ratatui/pull/1598)

-
[6e43672](6e436725e4)
*(readme)* Reimagine README.md by @orhun in
[#1569](https://github.com/ratatui/ratatui/pull/1569)

-
[8f28247](8f282473b2)
*(readme)* Correct examples links by @HoKim98 in
[#1484](https://github.com/ratatui/ratatui/pull/1484)

-
[9f90f74](9f90f7495f)
*(readme)* Fix broken link by @nilsmartel in
[#1485](https://github.com/ratatui/ratatui/pull/1485)

-
[260af68](260af68a34)
*(readme)* Include iocraft as an alternative by @kdheepak in
[#1483](https://github.com/ratatui/ratatui/pull/1483)

-
[dafb716](dafb716f9d)
*(widgets)* Add example for grouped barchart by @orhun in
[#1566](https://github.com/ratatui/ratatui/pull/1566)

-
[ed5dd73](ed5dd73084)
*(widgets)* Add example for tabs by @orhun in
[#1559](https://github.com/ratatui/ratatui/pull/1559)

-
[fab5321](fab532171d)
*(widgets)* Add example for scrollbar by @orhun in
[#1545](https://github.com/ratatui/ratatui/pull/1545)

-
[898aef6](898aef6e2f)
*(widgets)* Add example for list by @orhun in
[#1542](https://github.com/ratatui/ratatui/pull/1542)

-
[452366a](452366aa9e)
*(widgets)* Add example for sparkline by @orhun in
[#1556](https://github.com/ratatui/ratatui/pull/1556)

-
[6ddde0e](6ddde0e8a8)
*(widgets)* Add example for table by @orhun in
[#1557](https://github.com/ratatui/ratatui/pull/1557)

-
[93ad6b8](93ad6b828c)
*(widgets)* Update values in chart example by @orhun in
[#1558](https://github.com/ratatui/ratatui/pull/1558)

-
[15f442a](15f442a71e)
*(widgets)* Add example for paragraph by @orhun in
[#1544](https://github.com/ratatui/ratatui/pull/1544)

-
[17bba14](17bba14540)
*(widgets)* Move the logo example to widgets by @orhun in
[#1543](https://github.com/ratatui/ratatui/pull/1543)

-
[f2451e7](f2451e7f1e)
*(widgets)* Add example for gauge by @orhun in
[#1539](https://github.com/ratatui/ratatui/pull/1539)

-
[4f0a8b2](4f0a8b21af)
*(widgets)* Add example for canvas by @orhun in
[#1533](https://github.com/ratatui/ratatui/pull/1533)

-
[91147c4](91147c4d75)
*(widgets)* Add example for chart by @orhun in
[#1536](https://github.com/ratatui/ratatui/pull/1536)

-
[6dd25a3](6dd25a3111)
*(widgets)* Add example for calendar by @orhun in
[#1532](https://github.com/ratatui/ratatui/pull/1532)

-
[99ac005](99ac005b06)
*(widgets)* Add simple barchart example by @joshka in
[#1511](https://github.com/ratatui/ratatui/pull/1511)

-
[da05957](da05957fa0)
*(uncategorized)* Add widget-ref-container example by @joshka in
[#1603](https://github.com/ratatui/ratatui/pull/1603)

-
[1798512](1798512e94)
*(uncategorized)* Fix wording in user_input example by @dawedawe in
[#1611](https://github.com/ratatui/ratatui/pull/1611)

-
[03066d8](03066d81bf)
*(uncategorized)* Fix punctuation in canvas.rs documentation by
@dawedawe in [#1583](https://github.com/ratatui/ratatui/pull/1583)

-
[e411d9e](e411d9ec3e)
*(uncategorized)* Add input form example by @joshka in
[#1551](https://github.com/ratatui/ratatui/pull/1551)

-
[ed071f3](ed071f3723)
*(uncategorized)* Add mouse-drawing example by @joshka in
[#1546](https://github.com/ratatui/ratatui/pull/1546)

-
[46902f5](46902f5587)
*(uncategorized)* Improve docs for workspace crates by @orhun in
[#1490](https://github.com/ratatui/ratatui/pull/1490)

-
[a6b5792](a6b579223f)
*(uncategorized)* Fix example link in readme by @thomas-tacquet in
[#1462](https://github.com/ratatui/ratatui/pull/1462)

### Miscellaneous Tasks

-
[abe2f27](abe2f27328)
*(backend)* Change From<T> impls to new backend specific IntoBackend and
FromBackend traits by @joshka in
[#1464](https://github.com/ratatui/ratatui/pull/1464) [**breaking**]

-
[0a47ebd](0a47ebd94b)
*(bencher)* Update bencher CLI usage by @epompeii in
[#1470](https://github.com/ratatui/ratatui/pull/1470)

-
[a0979d6](a0979d6871)
*(build)* Remove cargo lint by @joshka in
[#1549](https://github.com/ratatui/ratatui/pull/1549)

-
[eaa4038](eaa403856e)
*(ci)* Install pre-built binaries for cargo-rdme by @orhun in
[#1477](https://github.com/ratatui/ratatui/pull/1477)

-
[e5e2316](e5e2316451)
*(ci)* Add check for keeping README.md up-to-date by @orhun in
[#1473](https://github.com/ratatui/ratatui/pull/1473)

-
[2ef3583](2ef3583eff)
*(ci)* Replace cargo-make with a custom cargo-xtask by @joshka in
[#1461](https://github.com/ratatui/ratatui/pull/1461)

-
[98df774](98df774d7f)
*(core)* Move core types to ratatui-core by @joshka in
[#1460](https://github.com/ratatui/ratatui/pull/1460)

-
[35eba76](35eba76b4d)
*(example)* Move demo2 to top level folder by @joshka in
[#1524](https://github.com/ratatui/ratatui/pull/1524)

-
[5f57d35](5f57d35234)
*(examples)* Add colors explorer demo app by @orhun in
[#1580](https://github.com/ratatui/ratatui/pull/1580)

-
[5c021bf](5c021bf344)
*(examples)* Add chart demo app by @orhun in
[#1579](https://github.com/ratatui/ratatui/pull/1579)

-
[9721300](9721300a47)
*(examples)* Add canvas demo app by @orhun in
[#1578](https://github.com/ratatui/ratatui/pull/1578)

-
[a6a1368](a6a1368250)
*(examples)* Add calendar explorer demo app by @orhun in
[#1571](https://github.com/ratatui/ratatui/pull/1571)

-
[819e92c](819e92cd44)
*(examples)* Add weather demo app by @orhun in
[#1567](https://github.com/ratatui/ratatui/pull/1567)

-
[b5f7e44](b5f7e44183)
*(examples)* Move async example to apps by @joshka in
[#1503](https://github.com/ratatui/ratatui/pull/1503)

-
[17316ec](17316ec5d0)
*(github)* Enable sponsorship button by @orhun in
[#1478](https://github.com/ratatui/ratatui/pull/1478)

-
[d3f01eb](d3f01ebf6e)
*(lint)* Ensure lint config is correct by @joshka in
[#1528](https://github.com/ratatui/ratatui/pull/1528)

-
[2b7ec5c](2b7ec5cb7f)
*(widgets)* Enable calendar widget as default by @orhun in
[#1521](https://github.com/ratatui/ratatui/pull/1521)

-
[d201b8e](d201b8e5dd)
*(xtask)* Check lints for only library targets by @orhun in
[#1531](https://github.com/ratatui/ratatui/pull/1531)

-
[6f21319](6f213191ef)
*(uncategorized)* Rename examples with clashing names by @joshka in
[#1597](https://github.com/ratatui/ratatui/pull/1597)

-
[11cbb2b](11cbb2ba87)
*(uncategorized)* Use cargo xtask for bacon clippy command by @joshka in
[#1592](https://github.com/ratatui/ratatui/pull/1592)

-
[b544e39](b544e394c9)
*(uncategorized)* Use clap instead of argh for demo example by @joshka
in [#1591](https://github.com/ratatui/ratatui/pull/1591)

-
[9a54198](9a541981b8)
*(uncategorized)* Make source files non-executable by @orhun in
[#1577](https://github.com/ratatui/ratatui/pull/1577)

-
[357ae7e](357ae7e251)
*(uncategorized)* Move terminal types to ratatui-core by @joshka in
[#1530](https://github.com/ratatui/ratatui/pull/1530) [**breaking**]

-
[21e62d8](21e62d84c2)
*(uncategorized)* Move the demo example to main folder by @joshka in
[#1523](https://github.com/ratatui/ratatui/pull/1523)

-
[fbf6050](fbf6050c86)
*(uncategorized)* Prepare alpha modularization release by @joshka in
[#1525](https://github.com/ratatui/ratatui/pull/1525)

-
[e4e95bc](e4e95bcecf)
*(uncategorized)* Remove --color always flags from bacon.toml by @joshka
in [#1502](https://github.com/ratatui/ratatui/pull/1502)

-
[a41c97b](a41c97b413)
*(uncategorized)* Move unstable widget refs to ratatui by @joshka in
[#1491](https://github.com/ratatui/ratatui/pull/1491) [**breaking**]

-
[e7085e3](e7085e3a3e)
*(uncategorized)* Move widgets into ratatui-widgets crate by @joshka in
[#1474](https://github.com/ratatui/ratatui/pull/1474)

-
[f1d0a18](f1d0a18375)
*(uncategorized)* Move ratatui crate into workspace folder by @joshka in
[#1459](https://github.com/ratatui/ratatui/pull/1459)

-
[55fb2d2](55fb2d2e56)
*(uncategorized)* Update repo links to ratatui instead of ratatui-org by
@joshka in [#1458](https://github.com/ratatui/ratatui/pull/1458)

### Continuous Integration

-
[4a871f9](4a871f993e)
*(uncategorized)* Refactor xtask / toml formatting by @joshka in
[#1602](https://github.com/ratatui/ratatui/pull/1602)

### New Contributors

* @dawedawe made their first contribution in
[#1611](https://github.com/ratatui/ratatui/pull/1611)
* @Its-Just-Nans made their first contribution in
[#1584](https://github.com/ratatui/ratatui/pull/1584)
* @MarSik made their first contribution in
[#1541](https://github.com/ratatui/ratatui/pull/1541)
* @raylu made their first contribution in
[#1540](https://github.com/ratatui/ratatui/pull/1540)
* @renesat made their first contribution in
[#1501](https://github.com/ratatui/ratatui/pull/1501)
* @HoKim98 made their first contribution in
[#1484](https://github.com/ratatui/ratatui/pull/1484)
* @nilsmartel made their first contribution in
[#1485](https://github.com/ratatui/ratatui/pull/1485)
* @marcoieni made their first contribution in
[#1480](https://github.com/ratatui/ratatui/pull/1480)
* @epompeii made their first contribution in
[#1470](https://github.com/ratatui/ratatui/pull/1470)
* @thomas-tacquet made their first contribution in
[#1462](https://github.com/ratatui/ratatui/pull/1462)
* @miroim made their first contribution in
[#1448](https://github.com/ratatui/ratatui/pull/1448)

**Full Changelog**:
https://github.com/ratatui/ratatui/compare/v0.29.0...0.30.0-alpha.1
</blockquote>

## `ratatui-crossterm`

<blockquote>

##
[0.30.0-alpha.1](https://github.com/ratatui/ratatui/releases/tag/0.30.0-alpha.1)
- 2025-01-14

0.30.0-alpha.1 is a pre-release of the upcoming 0.30.0 release. It is
intended for testing and
feedback. Please report any issues you encounter. The primary change is
that we've split the crate
into multiple crates. The main crate is now `ratatui-core` and the
widgets are in `ratatui-widgets`.
This allows for widget crates to depend on a stable core crate, and for
the core crate to be used
without widgets.

### Features

-
[56d5e05](56d5e05762)
*(bar)* Update label and text_value to accept Into<> by @Emivvvvv in
[#1471](https://github.com/ratatui/ratatui/pull/1471) [**breaking**]

-
[b76ad3b](b76ad3b02e)
*(bar)* Impl Styled for Bar by @Emivvvvv in
[#1476](https://github.com/ratatui/ratatui/pull/1476)

-
[369b18e](369b18eef2)
*(barchart)* Reduce barchart creation verbosity by @Emivvvvv in
[#1453](https://github.com/ratatui/ratatui/pull/1453)

-
[9275d34](9275d3421c)
*(layout)* Add Offset::new() constructor by @joshka in
[#1547](https://github.com/ratatui/ratatui/pull/1547)

-
[ff729b7](ff729b7607)
*(scrollbar)* Support retrieving the current position of state by @orhun
in [#1552](https://github.com/ratatui/ratatui/pull/1552)

-
[ce4856a](ce4856a65f)
*(widgets)* Add the missing constructor to canvas types by @orhun in
[#1538](https://github.com/ratatui/ratatui/pull/1538)

-
[50ba965](50ba96518f)
*(uncategorized)* Add a new RatatuiMascot widget by @Its-Just-Nans in
[#1584](https://github.com/ratatui/ratatui/pull/1584)

-
[1d28c89](1d28c89fe5)
*(uncategorized)* Add conversions for anstyle by @joshka in
[#1581](https://github.com/ratatui/ratatui/pull/1581)

### Bug Fixes

-
[860e48b](860e48b0f0)
*(buffer)* Buffer::get_pos() now correctly handles index > u16::MAX by
@joshka in [#1447](https://github.com/ratatui/ratatui/pull/1447)

-
[ec30390](ec30390446)
*(canvas)* Round coordinates to nearest grid cell by @joshka in
[#1507](https://github.com/ratatui/ratatui/pull/1507)

-
[afd1ce1](afd1ce179b)
*(canvas)* Lines that start outside the visible grid are now drawn by
@renesat in [#1501](https://github.com/ratatui/ratatui/pull/1501)

-
[18e70d3](18e70d3d51)
*(crossterm)* Terminal should keep Bold when removing Dim by @MarSik in
[#1541](https://github.com/ratatui/ratatui/pull/1541)

-
[a692a6e](a692a6e371)
*(lint)* Apply rust 1.84 clippy suggestions by @joshka in
[#1612](https://github.com/ratatui/ratatui/pull/1612)

-
[3b13240](3b13240728)
*(scrollbar)* Check for area.is_empty() before rendering by @farmeroy in
[#1529](https://github.com/ratatui/ratatui/pull/1529)

-
[f57b696](f57b696fdc)
*(span)* Dont render control characters by @EdJoPaTo in
[#1312](https://github.com/ratatui/ratatui/pull/1312)

-
[2ce958e](2ce958e38c)
*(table)* Allow display of additional table row, if row height > 1 by
@Lunderberg in [#1452](https://github.com/ratatui/ratatui/pull/1452)

-
[2892bdd](2892bddce6)
*(uncategorized)* Rust 1.83 clippy lints by @joshka in
[#1527](https://github.com/ratatui/ratatui/pull/1527)

-
[36e2d1b](36e2d1bda1)
*(uncategorized)* Add feature(doc_cfg) when generating docs by @joshka
in [#1506](https://github.com/ratatui/ratatui/pull/1506)

-
[4d7704f](4d7704fba5)
*(uncategorized)* Make StatefulWidget and Ref work with unsized State by
@thscharler in [#1505](https://github.com/ratatui/ratatui/pull/1505)

-
[7b87509](7b875091e1)
*(uncategorized)* Typo by @marcoieni in
[#1480](https://github.com/ratatui/ratatui/pull/1480)

### Refactor

-
[f132fa1](f132fa1715)
*(table)* Small readability improvements by @joshka in
[#1510](https://github.com/ratatui/ratatui/pull/1510)

-
[904b0aa](904b0aa723)
*(uncategorized)* Move symbols to modules by @joshka in
[#1594](https://github.com/ratatui/ratatui/pull/1594)

-
[7c8573f](7c8573f575)
*(uncategorized)* Rearrange selection_spacing code by @raylu in
[#1540](https://github.com/ratatui/ratatui/pull/1540)

-
[217c57c](217c57cd60)
*(uncategorized)* Modularize backends by @orhun in
[#1508](https://github.com/ratatui/ratatui/pull/1508)

-
[e461b72](e461b724a6)
*(uncategorized)* Move {Stateful,}Widget{,Ref} types into individual
files by @joshka in
[#1479](https://github.com/ratatui/ratatui/pull/1479)

### Documentation

-
[d291042](d291042e69)
*(block)* Revise the block example by @orhun in
[#1520](https://github.com/ratatui/ratatui/pull/1520)

-
[fcde9cb](fcde9cb9c3)
*(changelog)* Fix typo by @orhun in
[#1463](https://github.com/ratatui/ratatui/pull/1463)

-
[3ae6bf1](3ae6bf1d6f)
*(contributing)* Use cargo-xtask for instructions by @orhun in
[#1509](https://github.com/ratatui/ratatui/pull/1509)

-
[04e1b32](04e1b32cd2)
*(layout)* Rename cassowary-rs references to cassowary by @miroim in
[#1448](https://github.com/ratatui/ratatui/pull/1448)

-
[088aac1](088aac136d)
*(readme)* Tweak links and badges by @joshka in
[#1598](https://github.com/ratatui/ratatui/pull/1598)

-
[6e43672](6e436725e4)
*(readme)* Reimagine README.md by @orhun in
[#1569](https://github.com/ratatui/ratatui/pull/1569)

-
[8f28247](8f282473b2)
*(readme)* Correct examples links by @HoKim98 in
[#1484](https://github.com/ratatui/ratatui/pull/1484)

-
[9f90f74](9f90f7495f)
*(readme)* Fix broken link by @nilsmartel in
[#1485](https://github.com/ratatui/ratatui/pull/1485)

-
[260af68](260af68a34)
*(readme)* Include iocraft as an alternative by @kdheepak in
[#1483](https://github.com/ratatui/ratatui/pull/1483)

-
[dafb716](dafb716f9d)
*(widgets)* Add example for grouped barchart by @orhun in
[#1566](https://github.com/ratatui/ratatui/pull/1566)

-
[ed5dd73](ed5dd73084)
*(widgets)* Add example for tabs by @orhun in
[#1559](https://github.com/ratatui/ratatui/pull/1559)

-
[fab5321](fab532171d)
*(widgets)* Add example for scrollbar by @orhun in
[#1545](https://github.com/ratatui/ratatui/pull/1545)

-
[898aef6](898aef6e2f)
*(widgets)* Add example for list by @orhun in
[#1542](https://github.com/ratatui/ratatui/pull/1542)

-
[452366a](452366aa9e)
*(widgets)* Add example for sparkline by @orhun in
[#1556](https://github.com/ratatui/ratatui/pull/1556)

-
[6ddde0e](6ddde0e8a8)
*(widgets)* Add example for table by @orhun in
[#1557](https://github.com/ratatui/ratatui/pull/1557)

-
[93ad6b8](93ad6b828c)
*(widgets)* Update values in chart example by @orhun in
[#1558](https://github.com/ratatui/ratatui/pull/1558)

-
[15f442a](15f442a71e)
*(widgets)* Add example for paragraph by @orhun in
[#1544](https://github.com/ratatui/ratatui/pull/1544)

-
[17bba14](17bba14540)
*(widgets)* Move the logo example to widgets by @orhun in
[#1543](https://github.com/ratatui/ratatui/pull/1543)

-
[f2451e7](f2451e7f1e)
*(widgets)* Add example for gauge by @orhun in
[#1539](https://github.com/ratatui/ratatui/pull/1539)

-
[4f0a8b2](4f0a8b21af)
*(widgets)* Add example for canvas by @orhun in
[#1533](https://github.com/ratatui/ratatui/pull/1533)

-
[91147c4](91147c4d75)
*(widgets)* Add example for chart by @orhun in
[#1536](https://github.com/ratatui/ratatui/pull/1536)

-
[6dd25a3](6dd25a3111)
*(widgets)* Add example for calendar by @orhun in
[#1532](https://github.com/ratatui/ratatui/pull/1532)

-
[99ac005](99ac005b06)
*(widgets)* Add simple barchart example by @joshka in
[#1511](https://github.com/ratatui/ratatui/pull/1511)

-
[da05957](da05957fa0)
*(uncategorized)* Add widget-ref-container example by @joshka in
[#1603](https://github.com/ratatui/ratatui/pull/1603)

-
[1798512](1798512e94)
*(uncategorized)* Fix wording in user_input example by @dawedawe in
[#1611](https://github.com/ratatui/ratatui/pull/1611)

-
[03066d8](03066d81bf)
*(uncategorized)* Fix punctuation in canvas.rs documentation by
@dawedawe in [#1583](https://github.com/ratatui/ratatui/pull/1583)

-
[e411d9e](e411d9ec3e)
*(uncategorized)* Add input form example by @joshka in
[#1551](https://github.com/ratatui/ratatui/pull/1551)

-
[ed071f3](ed071f3723)
*(uncategorized)* Add mouse-drawing example by @joshka in
[#1546](https://github.com/ratatui/ratatui/pull/1546)

-
[46902f5](46902f5587)
*(uncategorized)* Improve docs for workspace crates by @orhun in
[#1490](https://github.com/ratatui/ratatui/pull/1490)

-
[a6b5792](a6b579223f)
*(uncategorized)* Fix example link in readme by @thomas-tacquet in
[#1462](https://github.com/ratatui/ratatui/pull/1462)

### Miscellaneous Tasks

-
[abe2f27](abe2f27328)
*(backend)* Change From<T> impls to new backend specific IntoBackend and
FromBackend traits by @joshka in
[#1464](https://github.com/ratatui/ratatui/pull/1464) [**breaking**]

-
[0a47ebd](0a47ebd94b)
*(bencher)* Update bencher CLI usage by @epompeii in
[#1470](https://github.com/ratatui/ratatui/pull/1470)

-
[a0979d6](a0979d6871)
*(build)* Remove cargo lint by @joshka in
[#1549](https://github.com/ratatui/ratatui/pull/1549)

-
[eaa4038](eaa403856e)
*(ci)* Install pre-built binaries for cargo-rdme by @orhun in
[#1477](https://github.com/ratatui/ratatui/pull/1477)

-
[e5e2316](e5e2316451)
*(ci)* Add check for keeping README.md up-to-date by @orhun in
[#1473](https://github.com/ratatui/ratatui/pull/1473)

-
[2ef3583](2ef3583eff)
*(ci)* Replace cargo-make with a custom cargo-xtask by @joshka in
[#1461](https://github.com/ratatui/ratatui/pull/1461)

-
[98df774](98df774d7f)
*(core)* Move core types to ratatui-core by @joshka in
[#1460](https://github.com/ratatui/ratatui/pull/1460)

-
[35eba76](35eba76b4d)
*(example)* Move demo2 to top level folder by @joshka in
[#1524](https://github.com/ratatui/ratatui/pull/1524)

-
[5f57d35](5f57d35234)
*(examples)* Add colors explorer demo app by @orhun in
[#1580](https://github.com/ratatui/ratatui/pull/1580)

-
[5c021bf](5c021bf344)
*(examples)* Add chart demo app by @orhun in
[#1579](https://github.com/ratatui/ratatui/pull/1579)

-
[9721300](9721300a47)
*(examples)* Add canvas demo app by @orhun in
[#1578](https://github.com/ratatui/ratatui/pull/1578)

-
[a6a1368](a6a1368250)
*(examples)* Add calendar explorer demo app by @orhun in
[#1571](https://github.com/ratatui/ratatui/pull/1571)

-
[819e92c](819e92cd44)
*(examples)* Add weather demo app by @orhun in
[#1567](https://github.com/ratatui/ratatui/pull/1567)

-
[b5f7e44](b5f7e44183)
*(examples)* Move async example to apps by @joshka in
[#1503](https://github.com/ratatui/ratatui/pull/1503)

-
[17316ec](17316ec5d0)
*(github)* Enable sponsorship button by @orhun in
[#1478](https://github.com/ratatui/ratatui/pull/1478)

-
[d3f01eb](d3f01ebf6e)
*(lint)* Ensure lint config is correct by @joshka in
[#1528](https://github.com/ratatui/ratatui/pull/1528)

-
[2b7ec5c](2b7ec5cb7f)
*(widgets)* Enable calendar widget as default by @orhun in
[#1521](https://github.com/ratatui/ratatui/pull/1521)

-
[d201b8e](d201b8e5dd)
*(xtask)* Check lints for only library targets by @orhun in
[#1531](https://github.com/ratatui/ratatui/pull/1531)

-
[6f21319](6f213191ef)
*(uncategorized)* Rename examples with clashing names by @joshka in
[#1597](https://github.com/ratatui/ratatui/pull/1597)

-
[11cbb2b](11cbb2ba87)
*(uncategorized)* Use cargo xtask for bacon clippy command by @joshka in
[#1592](https://github.com/ratatui/ratatui/pull/1592)

-
[b544e39](b544e394c9)
*(uncategorized)* Use clap instead of argh for demo example by @joshka
in [#1591](https://github.com/ratatui/ratatui/pull/1591)

-
[9a54198](9a541981b8)
*(uncategorized)* Make source files non-executable by @orhun in
[#1577](https://github.com/ratatui/ratatui/pull/1577)

-
[357ae7e](357ae7e251)
*(uncategorized)* Move terminal types to ratatui-core by @joshka in
[#1530](https://github.com/ratatui/ratatui/pull/1530) [**breaking**]

-
[21e62d8](21e62d84c2)
*(uncategorized)* Move the demo example to main folder by @joshka in
[#1523](https://github.com/ratatui/ratatui/pull/1523)

-
[fbf6050](fbf6050c86)
*(uncategorized)* Prepare alpha modularization release by @joshka in
[#1525](https://github.com/ratatui/ratatui/pull/1525)

-
[e4e95bc](e4e95bcecf)
*(uncategorized)* Remove --color always flags from bacon.toml by @joshka
in [#1502](https://github.com/ratatui/ratatui/pull/1502)

-
[a41c97b](a41c97b413)
*(uncategorized)* Move unstable widget refs to ratatui by @joshka in
[#1491](https://github.com/ratatui/ratatui/pull/1491) [**breaking**]

-
[e7085e3](e7085e3a3e)
*(uncategorized)* Move widgets into ratatui-widgets crate by @joshka in
[#1474](https://github.com/ratatui/ratatui/pull/1474)

-
[f1d0a18](f1d0a18375)
*(uncategorized)* Move ratatui crate into workspace folder by @joshka in
[#1459](https://github.com/ratatui/ratatui/pull/1459)

-
[55fb2d2](55fb2d2e56)
*(uncategorized)* Update repo links to ratatui instead of ratatui-org by
@joshka in [#1458](https://github.com/ratatui/ratatui/pull/1458)

### Continuous Integration

-
[4a871f9](4a871f993e)
*(uncategorized)* Refactor xtask / toml formatting by @joshka in
[#1602](https://github.com/ratatui/ratatui/pull/1602)

### New Contributors

* @dawedawe made their first contribution in
[#1611](https://github.com/ratatui/ratatui/pull/1611)
* @Its-Just-Nans made their first contribution in
[#1584](https://github.com/ratatui/ratatui/pull/1584)
* @MarSik made their first contribution in
[#1541](https://github.com/ratatui/ratatui/pull/1541)
* @raylu made their first contribution in
[#1540](https://github.com/ratatui/ratatui/pull/1540)
* @renesat made their first contribution in
[#1501](https://github.com/ratatui/ratatui/pull/1501)
* @HoKim98 made their first contribution in
[#1484](https://github.com/ratatui/ratatui/pull/1484)
* @nilsmartel made their first contribution in
[#1485](https://github.com/ratatui/ratatui/pull/1485)
* @marcoieni made their first contribution in
[#1480](https://github.com/ratatui/ratatui/pull/1480)
* @epompeii made their first contribution in
[#1470](https://github.com/ratatui/ratatui/pull/1470)
* @thomas-tacquet made their first contribution in
[#1462](https://github.com/ratatui/ratatui/pull/1462)
* @miroim made their first contribution in
[#1448](https://github.com/ratatui/ratatui/pull/1448)

**Full Changelog**:
https://github.com/ratatui/ratatui/compare/v0.29.0...0.30.0-alpha.1
</blockquote>

## `ratatui-widgets`

<blockquote>

##
[0.30.0-alpha.1](https://github.com/ratatui/ratatui/releases/tag/0.30.0-alpha.1)
- 2025-01-14

0.30.0-alpha.1 is a pre-release of the upcoming 0.30.0 release. It is
intended for testing and
feedback. Please report any issues you encounter. The primary change is
that we've split the crate
into multiple crates. The main crate is now `ratatui-core` and the
widgets are in `ratatui-widgets`.
This allows for widget crates to depend on a stable core crate, and for
the core crate to be used
without widgets.

### Features

-
[56d5e05](56d5e05762)
*(bar)* Update label and text_value to accept Into<> by @Emivvvvv in
[#1471](https://github.com/ratatui/ratatui/pull/1471) [**breaking**]

-
[b76ad3b](b76ad3b02e)
*(bar)* Impl Styled for Bar by @Emivvvvv in
[#1476](https://github.com/ratatui/ratatui/pull/1476)

-
[369b18e](369b18eef2)
*(barchart)* Reduce barchart creation verbosity by @Emivvvvv in
[#1453](https://github.com/ratatui/ratatui/pull/1453)

-
[9275d34](9275d3421c)
*(layout)* Add Offset::new() constructor by @joshka in
[#1547](https://github.com/ratatui/ratatui/pull/1547)

-
[ff729b7](ff729b7607)
*(scrollbar)* Support retrieving the current position of state by @orhun
in [#1552](https://github.com/ratatui/ratatui/pull/1552)

-
[ce4856a](ce4856a65f)
*(widgets)* Add the missing constructor to canvas types by @orhun in
[#1538](https://github.com/ratatui/ratatui/pull/1538)

-
[50ba965](50ba96518f)
*(uncategorized)* Add a new RatatuiMascot widget by @Its-Just-Nans in
[#1584](https://github.com/ratatui/ratatui/pull/1584)

-
[1d28c89](1d28c89fe5)
*(uncategorized)* Add conversions for anstyle by @joshka in
[#1581](https://github.com/ratatui/ratatui/pull/1581)

### Bug Fixes

-
[860e48b](860e48b0f0)
*(buffer)* Buffer::get_pos() now correctly handles index > u16::MAX by
@joshka in [#1447](https://github.com/ratatui/ratatui/pull/1447)

-
[ec30390](ec30390446)
*(canvas)* Round coordinates to nearest grid cell by @joshka in
[#1507](https://github.com/ratatui/ratatui/pull/1507)

-
[afd1ce1](afd1ce179b)
*(canvas)* Lines that start outside the visible grid are now drawn by
@renesat in [#1501](https://github.com/ratatui/ratatui/pull/1501)

-
[18e70d3](18e70d3d51)
*(crossterm)* Terminal should keep Bold when removing Dim by @MarSik in
[#1541](https://github.com/ratatui/ratatui/pull/1541)

-
[a692a6e](a692a6e371)
*(lint)* Apply rust 1.84 clippy suggestions by @joshka in
[#1612](https://github.com/ratatui/ratatui/pull/1612)

-
[3b13240](3b13240728)
*(scrollbar)* Check for area.is_empty() before rendering by @farmeroy in
[#1529](https://github.com/ratatui/ratatui/pull/1529)

-
[f57b696](f57b696fdc)
*(span)* Dont render control characters by @EdJoPaTo in
[#1312](https://github.com/ratatui/ratatui/pull/1312)

-
[2ce958e](2ce958e38c)
*(table)* Allow display of additional table row, if row height > 1 by
@Lunderberg in [#1452](https://github.com/ratatui/ratatui/pull/1452)

-
[2892bdd](2892bddce6)
*(uncategorized)* Rust 1.83 clippy lints by @joshka in
[#1527](https://github.com/ratatui/ratatui/pull/1527)

-
[36e2d1b](36e2d1bda1)
*(uncategorized)* Add feature(doc_cfg) when generating docs by @joshka
in [#1506](https://github.com/ratatui/ratatui/pull/1506)

-
[4d7704f](4d7704fba5)
*(uncategorized)* Make StatefulWidget and Ref work with unsized State by
@thscharler in [#1505](https://github.com/ratatui/ratatui/pull/1505)

-
[7b87509](7b875091e1)
*(uncategorized)* Typo by @marcoieni in
[#1480](https://github.com/ratatui/ratatui/pull/1480)

### Refactor

-
[f132fa1](f132fa1715)
*(table)* Small readability improvements by @joshka in
[#1510](https://github.com/ratatui/ratatui/pull/1510)

-
[904b0aa](904b0aa723)
*(uncategorized)* Move symbols to modules by @joshka in
[#1594](https://github.com/ratatui/ratatui/pull/1594)

-
[7c8573f](7c8573f575)
*(uncategorized)* Rearrange selection_spacing code by @raylu in
[#1540](https://github.com/ratatui/ratatui/pull/1540)

-
[217c57c](217c57cd60)
*(uncategorized)* Modularize backends by @orhun in
[#1508](https://github.com/ratatui/ratatui/pull/1508)

-
[e461b72](e461b724a6)
*(uncategorized)* Move {Stateful,}Widget{,Ref} types into individual
files by @joshka in
[#1479](https://github.com/ratatui/ratatui/pull/1479)

### Documentation

-
[d291042](d291042e69)
*(block)* Revise the block example by @orhun in
[#1520](https://github.com/ratatui/ratatui/pull/1520)

-
[fcde9cb](fcde9cb9c3)
*(changelog)* Fix typo by @orhun in
[#1463](https://github.com/ratatui/ratatui/pull/1463)

-
[3ae6bf1](3ae6bf1d6f)
*(contributing)* Use cargo-xtask for instructions by @orhun in
[#1509](https://github.com/ratatui/ratatui/pull/1509)

-
[04e1b32](04e1b32cd2)
*(layout)* Rename cassowary-rs references to cassowary by @miroim in
[#1448](https://github.com/ratatui/ratatui/pull/1448)

-
[088aac1](088aac136d)
*(readme)* Tweak links and badges by @joshka in
[#1598](https://github.com/ratatui/ratatui/pull/1598)

-
[6e43672](6e436725e4)
*(readme)* Reimagine README.md by @orhun in
[#1569](https://github.com/ratatui/ratatui/pull/1569)

-
[8f28247](8f282473b2)
*(readme)* Correct examples links by @HoKim98 in
[#1484](https://github.com/ratatui/ratatui/pull/1484)

-
[9f90f74](9f90f7495f)
*(readme)* Fix broken link by @nilsmartel in
[#1485](https://github.com/ratatui/ratatui/pull/1485)

-
[260af68](260af68a34)
*(readme)* Include iocraft as an alternative by @kdheepak in
[#1483](https://github.com/ratatui/ratatui/pull/1483)

-
[dafb716](dafb716f9d)
*(widgets)* Add example for grouped barchart by @orhun in
[#1566](https://github.com/ratatui/ratatui/pull/1566)

-
[ed5dd73](ed5dd73084)
*(widgets)* Add example for tabs by @orhun in
[#1559](https://github.com/ratatui/ratatui/pull/1559)

-
[fab5321](fab532171d)
*(widgets)* Add example for scrollbar by @orhun in
[#1545](https://github.com/ratatui/ratatui/pull/1545)

-
[898aef6](898aef6e2f)
*(widgets)* Add example for list by @orhun in
[#1542](https://github.com/ratatui/ratatui/pull/1542)

-
[452366a](452366aa9e)
*(widgets)* Add example for sparkline by @orhun in
[#1556](https://github.com/ratatui/ratatui/pull/1556)

-
[6ddde0e](6ddde0e8a8)
*(widgets)* Add example for table by @orhun in
[#1557](https://github.com/ratatui/ratatui/pull/1557)

-
[93ad6b8](93ad6b828c)
*(widgets)* Update values in chart example by @orhun in
[#1558](https://github.com/ratatui/ratatui/pull/1558)

-
[15f442a](15f442a71e)
*(widgets)* Add example for paragraph by @orhun in
[#1544](https://github.com/ratatui/ratatui/pull/1544)

-
[17bba14](17bba14540)
*(widgets)* Move the logo example to widgets by @orhun in
[#1543](https://github.com/ratatui/ratatui/pull/1543)

-
[f2451e7](f2451e7f1e)
*(widgets)* Add example for gauge by @orhun in
[#1539](https://github.com/ratatui/ratatui/pull/1539)

-
[4f0a8b2](4f0a8b21af)
*(widgets)* Add example for canvas by @orhun in
[#1533](https://github.com/ratatui/ratatui/pull/1533)

-
[91147c4](91147c4d75)
*(widgets)* Add example for chart by @orhun in
[#1536](https://github.com/ratatui/ratatui/pull/1536)

-
[6dd25a3](6dd25a3111)
*(widgets)* Add example for calendar by @orhun in
[#1532](https://github.com/ratatui/ratatui/pull/1532)

-
[99ac005](99ac005b06)
*(widgets)* Add simple barchart example by @joshka in
[#1511](https://github.com/ratatui/ratatui/pull/1511)

-
[da05957](da05957fa0)
*(uncategorized)* Add widget-ref-container example by @joshka in
[#1603](https://github.com/ratatui/ratatui/pull/1603)

-
[1798512](1798512e94)
*(uncategorized)* Fix wording in user_input example by @dawedawe in
[#1611](https://github.com/ratatui/ratatui/pull/1611)

-
[03066d8](03066d81bf)
*(uncategorized)* Fix punctuation in canvas.rs documentation by
@dawedawe in [#1583](https://github.com/ratatui/ratatui/pull/1583)

-
[e411d9e](e411d9ec3e)
*(uncategorized)* Add input form example by @joshka in
[#1551](https://github.com/ratatui/ratatui/pull/1551)

-
[ed071f3](ed071f3723)
*(uncategorized)* Add mouse-drawing example by @joshka in
[#1546](https://github.com/ratatui/ratatui/pull/1546)

-
[46902f5](46902f5587)
*(uncategorized)* Improve docs for workspace crates by @orhun in
[#1490](https://github.com/ratatui/ratatui/pull/1490)

-
[a6b5792](a6b579223f)
*(uncategorized)* Fix example link in readme by @thomas-tacquet in
[#1462](https://github.com/ratatui/ratatui/pull/1462)

### Miscellaneous Tasks

-
[abe2f27](abe2f27328)
*(backend)* Change From<T> impls to new backend specific IntoBackend and
FromBackend traits by @joshka in
[#1464](https://github.com/ratatui/ratatui/pull/1464) [**breaking**]

-
[0a47ebd](0a47ebd94b)
*(bencher)* Update bencher CLI usage by @epompeii in
[#1470](https://github.com/ratatui/ratatui/pull/1470)

-
[a0979d6](a0979d6871)
*(build)* Remove cargo lint by @joshka in
[#1549](https://github.com/ratatui/ratatui/pull/1549)

-
[eaa4038](eaa403856e)
*(ci)* Install pre-built binaries for cargo-rdme by @orhun in
[#1477](https://github.com/ratatui/ratatui/pull/1477)

-
[e5e2316](e5e2316451)
*(ci)* Add check for keeping README.md up-to-date by @orhun in
[#1473](https://github.com/ratatui/ratatui/pull/1473)

-
[2ef3583](2ef3583eff)
*(ci)* Replace cargo-make with a custom cargo-xtask by @joshka in
[#1461](https://github.com/ratatui/ratatui/pull/1461)

-
[98df774](98df774d7f)
*(core)* Move core types to ratatui-core by @joshka in
[#1460](https://github.com/ratatui/ratatui/pull/1460)

-
[35eba76](35eba76b4d)
*(example)* Move demo2 to top level folder by @joshka in
[#1524](https://github.com/ratatui/ratatui/pull/1524)

-
[5f57d35](5f57d35234)
*(examples)* Add colors explorer demo app by @orhun in
[#1580](https://github.com/ratatui/ratatui/pull/1580)

-
[5c021bf](5c021bf344)
*(examples)* Add chart demo app by @orhun in
[#1579](https://github.com/ratatui/ratatui/pull/1579)

-
[9721300](9721300a47)
*(examples)* Add canvas demo app by @orhun in
[#1578](https://github.com/ratatui/ratatui/pull/1578)

-
[a6a1368](a6a1368250)
*(examples)* Add calendar explorer demo app by @orhun in
[#1571](https://github.com/ratatui/ratatui/pull/1571)

-
[819e92c](819e92cd44)
*(examples)* Add weather demo app by @orhun in
[#1567](https://github.com/ratatui/ratatui/pull/1567)

-
[b5f7e44](b5f7e44183)
*(examples)* Move async example to apps by @joshka in
[#1503](https://github.com/ratatui/ratatui/pull/1503)

-
[17316ec](17316ec5d0)
*(github)* Enable sponsorship button by @orhun in
[#1478](https://github.com/ratatui/ratatui/pull/1478)

-
[d3f01eb](d3f01ebf6e)
*(lint)* Ensure lint config is correct by @joshka in
[#1528](https://github.com/ratatui/ratatui/pull/1528)

-
[2b7ec5c](2b7ec5cb7f)
*(widgets)* Enable calendar widget as default by @orhun in
[#1521](https://github.com/ratatui/ratatui/pull/1521)

-
[d201b8e](d201b8e5dd)
*(xtask)* Check lints for only library targets by @orhun in
[#1531](https://github.com/ratatui/ratatui/pull/1531)

-
[6f21319](6f213191ef)
*(uncategorized)* Rename examples with clashing names by @joshka in
[#1597](https://github.com/ratatui/ratatui/pull/1597)

-
[11cbb2b](11cbb2ba87)
*(uncategorized)* Use cargo xtask for bacon clippy command by @joshka in
[#1592](https://github.com/ratatui/ratatui/pull/1592)

-
[b544e39](b544e394c9)
*(uncategorized)* Use clap instead of argh for demo example by @joshka
in [#1591](https://github.com/ratatui/ratatui/pull/1591)

-
[9a54198](9a541981b8)
*(uncategorized)* Make source files non-executable by @orhun in
[#1577](https://github.com/ratatui/ratatui/pull/1577)

-
[357ae7e](357ae7e251)
*(uncategorized)* Move terminal types to ratatui-core by @joshka in
[#1530](https://github.com/ratatui/ratatui/pull/1530) [**breaking**]

-
[21e62d8](21e62d84c2)
*(uncategorized)* Move the demo example to main folder by @joshka in
[#1523](https://github.com/ratatui/ratatui/pull/1523)

-
[fbf6050](fbf6050c86)
*(uncategorized)* Prepare alpha modularization release by @joshka in
[#1525](https://github.com/ratatui/ratatui/pull/1525)

-
[e4e95bc](e4e95bcecf)
*(uncategorized)* Remove --color always flags from bacon.toml by @joshka
in [#1502](https://github.com/ratatui/ratatui/pull/1502)

-
[a41c97b](a41c97b413)
*(uncategorized)* Move unstable widget refs to ratatui by @joshka in
[#1491](https://github.com/ratatui/ratatui/pull/1491) [**breaking**]

-
[e7085e3](e7085e3a3e)
*(uncategorized)* Move widgets into ratatui-widgets crate by @joshka in
[#1474](https://github.com/ratatui/ratatui/pull/1474)

-
[f1d0a18](f1d0a18375)
*(uncategorized)* Move ratatui crate into workspace folder by @joshka in
[#1459](https://github.com/ratatui/ratatui/pull/1459)

-
[55fb2d2](55fb2d2e56)
*(uncategorized)* Update repo links to ratatui instead of ratatui-org by
@joshka in [#1458](https://github.com/ratatui/ratatui/pull/1458)

### Continuous Integration

-
[4a871f9](4a871f993e)
*(uncategorized)* Refactor xtask / toml formatting by @joshka in
[#1602](https://github.com/ratatui/ratatui/pull/1602)

### New Contributors

* @dawedawe made their first contribution in
[#1611](https://github.com/ratatui/ratatui/pull/1611)
* @Its-Just-Nans made their first contribution in
[#1584](https://github.com/ratatui/ratatui/pull/1584)
* @MarSik made their first contribution in
[#1541](https://github.com/ratatui/ratatui/pull/1541)
* @raylu made their first contribution in
[#1540](https://github.com/ratatui/ratatui/pull/1540)
* @renesat made their first contribution in
[#1501](https://github.com/ratatui/ratatui/pull/1501)
* @HoKim98 made their first contribution in
[#1484](https://github.com/ratatui/ratatui/pull/1484)
* @nilsmartel made their first contribution in
[#1485](https://github.com/ratatui/ratatui/pull/1485)
* @marcoieni made their first contribution in
[#1480](https://github.com/ratatui/ratatui/pull/1480)
* @epompeii made their first contribution in
[#1470](https://github.com/ratatui/ratatui/pull/1470)
* @thomas-tacquet made their first contribution in
[#1462](https://github.com/ratatui/ratatui/pull/1462)
* @miroim made their first contribution in
[#1448](https://github.com/ratatui/ratatui/pull/1448)

**Full Changelog**:
https://github.com/ratatui/ratatui/compare/v0.29.0...0.30.0-alpha.1
</blockquote>

## `ratatui-macros`

<blockquote>

##
[0.30.0-alpha.1](https://github.com/ratatui/ratatui/releases/tag/0.30.0-alpha.1)
- 2025-01-14

0.30.0-alpha.1 is a pre-release of the upcoming 0.30.0 release. It is
intended for testing and
feedback. Please report any issues you encounter. The primary change is
that we've split the crate
into multiple crates. The main crate is now `ratatui-core` and the
widgets are in `ratatui-widgets`.
This allows for widget crates to depend on a stable core crate, and for
the core crate to be used
without widgets.

### Features

-
[56d5e05](56d5e05762)
*(bar)* Update label and text_value to accept Into<> by @Emivvvvv in
[#1471](https://github.com/ratatui/ratatui/pull/1471) [**breaking**]

-
[b76ad3b](b76ad3b02e)
*(bar)* Impl Styled for Bar by @Emivvvvv in
[#1476](https://github.com/ratatui/ratatui/pull/1476)

-
[369b18e](369b18eef2)
*(barchart)* Reduce barchart creation verbosity by @Emivvvvv in
[#1453](https://github.com/ratatui/ratatui/pull/1453)

-
[9275d34](9275d3421c)
*(layout)* Add Offset::new() constructor by @joshka in
[#1547](https://github.com/ratatui/ratatui/pull/1547)

-
[ff729b7](ff729b7607)
*(scrollbar)* Support retrieving the current position of state by @orhun
in [#1552](https://github.com/ratatui/ratatui/pull/1552)

-
[ce4856a](ce4856a65f)
*(widgets)* Add the missing constructor to canvas types by @orhun in
[#1538](https://github.com/ratatui/ratatui/pull/1538)

-
[50ba965](50ba96518f)
*(uncategorized)* Add a new RatatuiMascot widget by @Its-Just-Nans in
[#1584](https://github.com/ratatui/ratatui/pull/1584)

-
[1d28c89](1d28c89fe5)
*(uncategorized)* Add conversions for anstyle by @joshka in
[#1581](https://github.com/ratatui/ratatui/pull/1581)

### Bug Fixes

-
[860e48b](860e48b0f0)
*(buffer)* Buffer::get_pos() now correctly handles index > u16::MAX by
@joshka in [#1447](https://github.com/ratatui/ratatui/pull/1447)

-
[ec30390](ec30390446)
*(canvas)* Round coordinates to nearest grid cell by @joshka in
[#1507](https://github.com/ratatui/ratatui/pull/1507)

-
[afd1ce1](afd1ce179b)
*(canvas)* Lines that start outside the visible grid are now drawn by
@renesat in [#1501](https://github.com/ratatui/ratatui/pull/1501)

-
[18e70d3](18e70d3d51)
*(crossterm)* Terminal should keep Bold when removing Dim by @MarSik in
[#1541](https://github.com/ratatui/ratatui/pull/1541)

-
[a692a6e](a692a6e371)
*(lint)* Apply rust 1.84 clippy suggestions by @joshka in
[#1612](https://github.com/ratatui/ratatui/pull/1612)

-
[3b13240](3b13240728)
*(scrollbar)* Check for area.is_empty() before rendering by @farmeroy in
[#1529](https://github.com/ratatui/ratatui/pull/1529)

-
[f57b696](f57b696fdc)
*(span)* Dont render control characters by @EdJoPaTo in
[#1312](https://github.com/ratatui/ratatui/pull/1312)

-
[2ce958e](2ce958e38c)
*(table)* Allow display of additional table row, if row height > 1 by
@Lunderberg in [#1452](https://github.com/ratatui/ratatui/pull/1452)

-
[2892bdd](2892bddce6)
*(uncategorized)* Rust 1.83 clippy lints by @joshka in
[#1527](https://github.com/ratatui/ratatui/pull/1527)

-
[36e2d1b](36e2d1bda1)
*(uncategorized)* Add feature(doc_cfg) when generating docs by @joshka
in [#1506](https://github.com/ratatui/ratatui/pull/1506)

-
[4d7704f](4d7704fba5)
*(uncategorized)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-30 04:53:20 -07:00
Jagoda Estera Ślązak
055522ef7b docs: add docs for authoring widget crates (#1955)
- added Authoring Widget Libraries sub-section
- moved built-in and third-party widgets sections higher
2025-06-30 07:09:28 +02:00
Jagoda Estera Ślązak
0b025db72b docs(contributing): fix grammar (#1958) 2025-06-28 15:45:21 -07:00
Josh McKinney
617d31851a docs: improve Block docs (#1953)
Co-authored-by: Jagoda Estera Ślązak <128227338+j-g00da@users.noreply.github.com>
2025-06-28 15:28:21 -07:00
Lena
4c301e891d feat(text): implement AddAssign for Text (#1956)
This makes it possible to add a second `Text` instance to a first one using the += operator.

```rust
let mut text = Text::from("line 1");
text += Text::from("line 2");
```

Style and alignment applied to the second text is ignored (though styles and alignment of lines and spans are copied).
2025-06-28 14:53:29 -07:00
Jagoda Estera Ślązak
b9da1926a0 feat(serde): derive Serialize/Deserialize for alignment enums (#1957)
Resolves #1954
2025-06-28 14:48:16 -07:00
Josh McKinney
6dcd53bc6b feat: add ergonomic methods for layouting Rects (#1909)
This commit introduces new methods for the `Rect` struct that simplify
the process of splitting a `Rect` into sub-rects according to a given
`Layout`. By putting these methods on the `Rect` struct, we make it a
bit more natural that a layout is applied to the `Rect` itself, rather
than passing a `Rect` to the `Layout` struct to be split.

Adds:
- `Rect::layout` and `Rect::try_layout` methods that allow splitting a
  `Rect` into an array of sub-rects according to a given `Layout`.
- `Rect::layout_vec` method that returns a `Vec` of sub-rects.
- `Layout::try_areas` method that returns an array of sub-rects, with
  compile-time checks for the number of constraints. This is added
  mainly for consistency with the new `Rect` methods.

```rust
use ratatui_core::layout::{Layout, Constraint, Rect};
let area = Rect::new(0, 0, 10, 10);
let layout = Layout::vertical([Constraint::Fill(1); 2]);

// Rect::layout() infers the number of constraints at compile time:
let [top, main] = area.layout(&layout);

// Rect::try_layout() and Layout::try_areas() do the same, but return a
// Result:
let [top, main] = area.try_layout(&layout)?;
let [top, main] = layout.try_areas(area)?;

// Rect::layout_vec() returns a Vec of sub-rects:
let areas_vec = area.layout_vec(&layout);

// you can also explicitly specify the number of constraints:
let areas = area.layout::<2>(&layout);
let areas = area.try_layout::<2>(&layout)?;
let areas = layout.try_areas::<2>(area)?;
```
2025-06-28 01:23:34 -07:00
Dheepak Krishnamurthy
d99984f1e9 feat(layout)!: Add Flex::SpaceEvenly (#1952)
Resolves https://github.com/ratatui/ratatui/issues/1951

BREAKING CHANGE: Old `Flex::SpaceAround` behavior is available by using
`Flex::SpaceEvenly` and new
`Flex::SpaceAround` now distributes space evenly around each element
except the middle spacers
    are twice the size of first and last elements

With this change, the following variants of `Flex` are supported:

- `Flex::Start`: Aligns items to the start; excess space appears at the
end.
- `Flex::End`: Aligns items to the end; excess space appears at the
start.
- `Flex::Center`: Centers items with equal space on both sides.
- `Flex::SpaceAround` (**new**): Distributes space _around_ items; space
between items is _twice_ the edge spacing.
- `Flex::SpaceBetween`: Distributes space _evenly between_ items except
no space at the edges.
- `Flex::SpaceEvenly` (**previously `Flex::SpaceAround`**): Distributes
space _evenly between_ items and edges.
- `Flex::Legacy`: Preserves legacy behavior, placing all excess space at
the end.

This aligns behavior of `Flex` with CSS flexbox more closely.

The following is a screenshot in action:

<img width="1090" alt="image"
src="https://github.com/user-attachments/assets/2c7cd797-27bd-4242-a824-4565d369227b"
/>

---------

Co-authored-by: Jagoda Estera Ślązak <128227338+j-g00da@users.noreply.github.com>
2025-06-28 03:39:15 -04:00
Josh McKinney
8e2d568428 docs: improve layout related docs (#1948)
Adds module level docs and more comprehensive docs on all the types in
the layout module

Fixes #1937
2025-06-27 14:31:11 -07:00
Jagoda Estera Ślązak
d02995fda1 chore(gitignore): add .env to .gitignore (#1949) 2025-06-27 08:15:44 +03:00
Josh McKinney
4c708ddf8a docs: improve docs for run/init/restore etc. (#1947)
- **docs: document the init module**
- **docs: use the ratatui::run() methods in the main doc**
- **docs: add more intradoc / website links and historical perspective
on Terminal / backend**
- **docs: add notes about new run/init/restore methods and the
defaultterminal type to terminal docs**
2025-06-26 21:52:58 -07:00
Josh McKinney
5620e06b1a docs: add crate organization sections to workspace (#1946)
Adds summary-level crate organization documentation to all crates
explaining the modular workspace structure and when to use each crate.
Links to ARCHITECTURE.md for detailed information.
2025-06-26 15:36:00 -07:00
Josh McKinney
cfb65e64ba docs: add examples for handling state (#1849)
Added comprehensive state management examples covering both immutable
and mutable patterns and documentation to help developers choose the
right approach for their applications.
2025-06-26 13:04:42 -07:00
Jagoda Estera Ślązak
92bb9b2219 chore: remove Title references (#1943) 2025-06-26 09:13:59 +02:00
Josh McKinney
d6647db744 chore: remove some allow attributes for fixed clippy bugs (#1944) 2025-06-25 22:29:56 -07:00
Josh McKinney
3f48bde3c6 chore: remove OpenSSL license (#1942)
Only keep licenses in the allow list that are actually used
2025-06-25 21:59:26 -07:00
Josh McKinney
80bc818723 fix: fix truncation of left aligned block titles (#1931)
truncate the right side of left aligned titles rather than the left side
of right aligned titles. This is more obvious as the left side of text
often contains more important information. And we generally read
left to right.

This change makes centered titles overwrite left aligned titles and
right aligned titles overwrite centered or left aligned titles.

Fixes: https://github.com/ratatui/ratatui/issues/358
2025-06-25 21:56:40 -07:00
Josh McKinney
0c3872f1c5 feat: add Rect::outer() (#1929)
Fixes: https://github.com/ratatui/ratatui/issues/211
2025-06-25 21:56:15 -07:00
dependabot[bot]
9f9ec332b0 build(deps): bump cargo_metadata from 0.19.2 to 0.20.0 (#1887)
Bumps [cargo_metadata](https://github.com/oli-obk/cargo_metadata) from
0.19.2 to 0.20.0.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="fa60b98bc8"><code>fa60b98</code></a>
Merge pull request <a
href="https://redirect.github.com/oli-obk/cargo_metadata/issues/293">#293</a>
from oli-obk/push-orzyoroqpxpu</li>
<li><a
href="61345fb320"><code>61345fb</code></a>
required features have a different format from normal feature names</li>
<li><a
href="19f42039ee"><code>19f4203</code></a>
Bump MSRV</li>
<li><a
href="6861466d25"><code>6861466</code></a>
Fix build on with <code>builder</code> feature</li>
<li><a
href="a21b7a1d01"><code>a21b7a1</code></a>
Update examples</li>
<li><a
href="5895dd6b15"><code>5895dd6</code></a>
Merge pull request <a
href="https://redirect.github.com/oli-obk/cargo_metadata/issues/261">#261</a>
from zetanumbers/docsrs-features</li>
<li><a
href="e3373d02e7"><code>e3373d0</code></a>
refactor: NodeDep::name: PackageName</li>
<li><a
href="2f0142d684"><code>2f0142d</code></a>
Merge pull request <a
href="https://redirect.github.com/oli-obk/cargo_metadata/issues/291">#291</a>
from gear-tech/fix-cross-compilation</li>
<li><a
href="95d4a360fc"><code>95d4a36</code></a>
update changelog</li>
<li><a
href="9cd40e6363"><code>9cd40e6</code></a>
append docs</li>
<li>Additional commits viewable in <a
href="https://github.com/oli-obk/cargo_metadata/compare/0.19.2...0.20.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=cargo_metadata&package-manager=cargo&previous-version=0.19.2&new-version=0.20.0)](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>
Co-authored-by: Josh McKinney <joshka@users.noreply.github.com>
2025-06-25 21:44:39 -07:00
Jagoda Estera Ślązak
0951da52f9 docs(breaking-changes): improve migration guide for Backend::Error (#1908)
Related: https://github.com/fujiapple852/trippy/pull/1588
2025-06-25 19:36:12 -07:00
Josh McKinney
3de41a8249 docs: document widgets module (#1932)
Adds a good overview of the use and implementation of widget traits.

Goals with the doc rewrite:
- document the rationale for the ratatui-widgets crate with info for app
builders and widget makers.
- Show how to use the widgets for rendering as well as implement the
traits- document the differences and reasons for each trait
- document the historical perspective (to make it easy to understand
older Ratatui apps as well as migrate to newer approaches
- give recommended approaches to implementing traits
- explain the differences between Consuming and Shared / Mutable
Reference implementations of Widget
- explain the differences between using StatefulWidget and Mutable
References
- Explain the use case for WidgetRef and StatefulWidgetRef
- Link out to third part widget lists

Fixes: https://github.com/ratatui/ratatui/issues/366

---------

Co-authored-by: hasezoey <hasezoey@gmail.com>
2025-06-25 16:19:54 -07:00
dependabot[bot]
d41b8d6334 build(deps): bump clap from 4.5.39 to 4.5.40 (#1911)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.39 to 4.5.40.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/clap-rs/clap/blob/master/CHANGELOG.md">clap's
changelog</a>.</em></p>
<blockquote>
<h2>[4.5.40] - 2025-06-09</h2>
<h3>Features</h3>
<ul>
<li>Support quoted ids in <code>arg!()</code> macro (e.g.
<code>arg!(&quot;check-config&quot;: ...)</code>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="cff27dbf57"><code>cff27db</code></a>
chore: Release</li>
<li><a
href="4ef41249f1"><code>4ef4124</code></a>
docs: Update changelog</li>
<li><a
href="ca896175c1"><code>ca89617</code></a>
Merge pull request <a
href="https://redirect.github.com/clap-rs/clap/issues/5848">#5848</a>
from jennings/jennings/push-xolwzyoornps</li>
<li><a
href="99b6391ee9"><code>99b6391</code></a>
fix(complete): Fix PowerShell dynamic completion</li>
<li>See full diff in <a
href="https://github.com/clap-rs/clap/compare/clap_complete-v4.5.39...clap_complete-v4.5.40">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=clap&package-manager=cargo&previous-version=4.5.39&new-version=4.5.40)](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>
2025-06-25 15:37:11 -07:00
Josh McKinney
4c86513790 chore: remove block::Title (#1926)
The title alignment is better expressed in the `Line` as this fits more
coherently with the rest of the library.

BREAKING CHANGES:
- `widgets::block` is no longer exported
- `widgets::block::Title` no longer exists
- `widgets::block::Position` is now `widgets::TitlePosition`
- `Block::title()` now accepts `Into::<Line>` instead of `Into<Title>`
- `BlockExt` is now exported at widgets::`BlockExt`

Closes: https://github.com/ratatui/ratatui/issues/738
2025-06-25 15:36:50 -07:00
Josh McKinney
7bc78bca1b feat: add ratatui::run() method (#1707)
This introduces a new `ratatui::run()` method which runs a closure with
a terminal initialized with reasonable defaults for most applications.
This calls `ratatui::init()` before running the closure and
`ratatui::restore()` after the closure completes, and returns the result
of the closure.

A minimal hello world example using the new `ratatui::run()` method:

```rust
fn main() -> Result<(), Box<dyn std::error::Error>> {
    ratatui::run(|terminal| {
        loop {
            terminal.draw(|frame| frame.render_widget("Hello World!", frame.area()))?;
            if crossterm::event::read()?.is_key_press() {
                break Ok(());
            }
        }
    })
}
```

Of course, this also works both with apps that use free methods and
structs:

```rust
fn run(terminal: &mut DefaultTerminal) -> Result<(), AppError> { ... }

ratatui::run(run)?;
```

```rust
struct App { ... }

impl App {
    fn new() -> Self { ... }
    fn run(mut self, terminal: &mut DefaultTerminal) -> Result<(), AppError> { ... }
}

ratatui::run(|terminal| App::new().run(terminal))?;
```
2025-06-25 03:20:42 -07:00
Josh McKinney
b6fbfcdd1c feat: add lifetime to symbol sets (#1935)
This makes it possible to create symbol sets at runtime with non-static
lifetimes.

Fixes: https://github.com/ratatui/ratatui/issues/1722
2025-06-25 01:02:09 -07:00
Josh McKinney
21e3b598ce fix: fix handling of multi-byte chars in bar chart (#1934)
The split_at method requires that the split point is at a valid utf8
character boundary.

Fixes: https://github.com/ratatui/ratatui/issues/1928
2025-06-25 00:55:18 -07:00
Josh McKinney
ca2ad4a1f9 docs: simplify ratatui-macro docs (#1923) 2025-06-25 00:47:27 -07:00
Josh McKinney
272f5c05dc chore: fix new lints (#1922) 2025-06-25 00:38:41 -07:00
dependabot[bot]
dfd0736f3a build(deps): bump anstyle from 1.0.10 to 1.0.11 (#1901)
Bumps [anstyle](https://github.com/rust-cli/anstyle) from 1.0.10 to
1.0.11.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="886539c953"><code>886539c</code></a>
chore: Release</li>
<li><a
href="161bf93c92"><code>161bf93</code></a>
Merge pull request <a
href="https://redirect.github.com/rust-cli/anstyle/issues/255">#255</a>
from Muscraft/anstyle-svg-urls</li>
<li><a
href="70dd95e053"><code>70dd95e</code></a>
feat(anstyle-svg): Add support for rendering Hyperlinks</li>
<li><a
href="96e3f75643"><code>96e3f75</code></a>
test(anstyle-svg): Add tests for Hyperlinks</li>
<li><a
href="abdd703a5c"><code>abdd703</code></a>
refactor(anstyle-svg): Use a concrete retrurn type from adapter</li>
<li><a
href="8d5053079d"><code>8d50530</code></a>
refactor(anstyle-svg): Use a better name for internal adapter</li>
<li><a
href="327c261668"><code>327c261</code></a>
refactor: Use a custom adapter for anstyle-svg</li>
<li><a
href="f1d6c141e2"><code>f1d6c14</code></a>
refactor(anstream): Use a less general name for State</li>
<li><a
href="98ef02efe0"><code>98ef02e</code></a>
test(anstream): Use anstyle for wincon adapter tests</li>
<li><a
href="182053aaa8"><code>182053a</code></a>
Merge pull request <a
href="https://redirect.github.com/rust-cli/anstyle/issues/254">#254</a>
from epage/template</li>
<li>Additional commits viewable in <a
href="https://github.com/rust-cli/anstyle/compare/v1.0.10...v1.0.11">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=anstyle&package-manager=cargo&previous-version=1.0.10&new-version=1.0.11)](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>
2025-06-24 20:10:42 -07:00
dependabot[bot]
a112953c59 build(deps): bump hashbrown from 0.15.3 to 0.15.4 (#1902)
Bumps [hashbrown](https://github.com/rust-lang/hashbrown) from 0.15.3 to
0.15.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rust-lang/hashbrown/releases">hashbrown's
releases</a>.</em></p>
<blockquote>
<h2>v0.15.4</h2>
<h3>Changed</h3>
<ul>
<li>Removed optional dependency on compiler-builtins. This only affects
building as part of <code>std</code>.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rust-lang/hashbrown/blob/master/CHANGELOG.md">hashbrown's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/rust-lang/hashbrown/compare/v0.15.3...v0.15.4">0.15.4</a>
- 2025-06-05</h2>
<h3>Changed</h3>
<ul>
<li>Removed optional dependency on compiler-builtins. This only affects
building as part of <code>std</code>.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8ceeb4045f"><code>8ceeb40</code></a>
Update CHANGELOG.md</li>
<li><a
href="b9be6808d6"><code>b9be680</code></a>
chore: release v0.15.4</li>
<li><a
href="53cf3532ff"><code>53cf353</code></a>
Merge pull request <a
href="https://redirect.github.com/rust-lang/hashbrown/issues/625">#625</a>
from tgross35/no-compiler-builtins</li>
<li><a
href="197a1ebf65"><code>197a1eb</code></a>
Remove optional dependency on compiler-builtins</li>
<li><a
href="d3a2a3d752"><code>d3a2a3d</code></a>
Merge pull request <a
href="https://redirect.github.com/rust-lang/hashbrown/issues/623">#623</a>
from cppbear/add-tests</li>
<li><a
href="165fdcfb58"><code>165fdcf</code></a>
Merge pull request <a
href="https://redirect.github.com/rust-lang/hashbrown/issues/622">#622</a>
from AbeZbm/add-tests</li>
<li><a
href="e52627b92d"><code>e52627b</code></a>
test: add test for HashMap::get_mut with non-existent key</li>
<li><a
href="c1cd0c6827"><code>c1cd0c6</code></a>
Add missing test for set</li>
<li><a
href="62634581de"><code>6263458</code></a>
Merge pull request <a
href="https://redirect.github.com/rust-lang/hashbrown/issues/620">#620</a>
from rust-lang/release-plz-2025-04-29T17-31-59Z</li>
<li>See full diff in <a
href="https://github.com/rust-lang/hashbrown/compare/v0.15.3...v0.15.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=hashbrown&package-manager=cargo&previous-version=0.15.3&new-version=0.15.4)](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>
2025-06-24 20:10:07 -07:00
Tatesa Uradnik
e1e400406c fix: derive copy for list state (#1921) 2025-06-24 10:54:34 -07:00
Jagoda Estera Ślązak
1399d95ae0 feat(no_std): make palette and serde features depends on std (#1919) 2025-06-23 23:00:40 -07:00
Jagoda Estera Ślązak
488e5f020f feat: make border! work without importing Borders (#1918)
Currently using `border!` macro requires explicit import of `Borders`
which is unnecessary.
2025-06-21 18:30:23 -07:00
Jagoda Estera Ślązak
e48aa9ec09 chore(ci): stop publish-alpha from running on forks (#1916)
I can't sleep because every Saturday alpha release fails on my ratatui
fork. This should fix my insomnia.
2025-06-20 22:37:15 -07:00
Hari Chalise
68b9f67f59 docs(readme): add Built with Ratatui badge for downstream projects (#1905) 2025-06-16 15:32:22 -07:00
Jagoda Estera Ślązak
bbe1cf9497 docs(breaking-changes): change MSRV to 1.85 (#1896)
The minimum supported Rust version is now for `ratatui` v0.30 is 1.85
2025-06-05 11:37:01 +03:00
Jagoda Estera Ślązak
671c2b4fd4 feat: support merging the borders of blocks (#1874)
When two borders overlap, they will automatically merge into a single,
clean border instead of overlapping.

This improves visual clarity and reduces rendering glitches around corners.

For example:

```
assert_eq!(Cell::new("┘").merge_symbol("┏", MergeStrategy::Exact).symbol(), "╆");
```

Co-authored-by: pauladam94 <poladam2002@gmail.com>
Co-authored-by: Paul Adam <65903440+pauladam94@users.noreply.github.com>
Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
Co-authored-by: Orhun Parmaksız <orhun@archlinux.org>
Co-authored-by: Josh McKinney <joshka@users.noreply.github.com>
2025-06-04 11:10:26 +02:00
Daksh
12cb5a28fe fix: allow canvas area to exceed u16::MAX (#1891)
This allows Canvas grids where the width * height exceeds u16::MAX by
converting values to usize earlier in several methods.

Fixes: https://github.com/ratatui/ratatui/issues/1449
2025-06-03 16:04:47 -07:00
Jagoda Estera Ślązak
861fbdf5cf docs(examples): fix a typo (#1890)
Makes CI typos check pass again
2025-06-03 14:55:23 +03:00
dependabot[bot]
bf9ecf19de build(deps): bump clap from 4.5.38 to 4.5.39 (#1888)
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some
time.

Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.

---

[//]: # (dependabot-end)

Bumps [clap](https://github.com/clap-rs/clap) from 4.5.38 to 4.5.39.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/clap-rs/clap/releases">clap's
releases</a>.</em></p>
<blockquote>
<h2>v4.5.39</h2>
<h2>[4.5.39] - 2025-05-27</h2>
<h3>Fixes</h3>
<ul>
<li><em>(help)</em> Show short flag aliases before long</li>
<li><em>(help)</em> Merge the short and long flag alias lists</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/clap-rs/clap/blob/master/CHANGELOG.md">clap's
changelog</a>.</em></p>
<blockquote>
<h2>[4.5.39] - 2025-05-27</h2>
<h3>Fixes</h3>
<ul>
<li><em>(help)</em> Show short flag aliases before long</li>
<li><em>(help)</em> Merge the short and long flag alias lists</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ed2360f9cd"><code>ed2360f</code></a>
chore: Release</li>
<li><a
href="196a14b8c9"><code>196a14b</code></a>
docs: Update changelog</li>
<li><a
href="cd622ab63c"><code>cd622ab</code></a>
Merge pull request <a
href="https://redirect.github.com/clap-rs/clap/issues/5846">#5846</a>
from ribru17/alias_fn_dedup</li>
<li><a
href="48ff72be28"><code>48ff72b</code></a>
fix(complete): Deduplicate bash subcmd cases</li>
<li><a
href="b1b6f17f61"><code>b1b6f17</code></a>
Merge pull request <a
href="https://redirect.github.com/clap-rs/clap/issues/5843">#5843</a>
from epage/link</li>
<li><a
href="5900216569"><code>5900216</code></a>
fix(derive): Update link for derive attributes</li>
<li><a
href="fde45f9aea"><code>fde45f9</code></a>
chore: Release</li>
<li><a
href="45d5d7edcb"><code>45d5d7e</code></a>
docs: Update changelog</li>
<li><a
href="4b82b97cd9"><code>4b82b97</code></a>
chore: Release</li>
<li><a
href="a982adfbbd"><code>a982adf</code></a>
docs: Update changelog</li>
<li>Additional commits viewable in <a
href="https://github.com/clap-rs/clap/compare/clap_complete-v4.5.38...clap_complete-v4.5.39">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=clap&package-manager=cargo&previous-version=4.5.38&new-version=4.5.39)](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>
2025-06-02 12:56:59 -07:00
dependabot[bot]
ac22d21deb build(deps): bump color-eyre from 0.6.4 to 0.6.5 (#1889)
Bumps [color-eyre](https://github.com/eyre-rs/eyre) from 0.6.4 to 0.6.5.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f110d78793"><code>f110d78</code></a>
Bump color-eyre to 0.6.5, color-spantrace to 0.3.0</li>
<li><a
href="6534ecdcbb"><code>6534ecd</code></a>
Undo eyre version bump (DO NOT PUBLISH EYRE)</li>
<li><a
href="9f35b8c83f"><code>9f35b8c</code></a>
Exclude images from published crate (<a
href="https://redirect.github.com/eyre-rs/eyre/issues/239">#239</a>)</li>
<li><a
href="6e9ced8865"><code>6e9ced8</code></a>
re-bump eyre version</li>
<li>See full diff in <a
href="https://github.com/eyre-rs/eyre/compare/color-eyre@0.6.4...color-eyre@0.6.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=color-eyre&package-manager=cargo&previous-version=0.6.4&new-version=0.6.5)](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>
2025-06-02 12:56:08 -07:00
Josh McKinney
770cb7c3c3 chore: add tests for combining list styles (#1884)
Co-authored-by: Orhun Parmaksız <orhun@archlinux.org>
2025-06-01 13:30:13 -07:00
Sevki
92b6a16bde docs: fix grammar in ratatui-widgets README (#1885)
Co-authored-by: Josh McKinney <joshka@users.noreply.github.com>
2025-06-01 13:14:59 -07:00
Aurelien Andreo
89b74214d9 feat(serde): derive Serialize/Deserialize for additional structs/enums (#1883)
This PR adds `#[derive(Serialize, Deserialize)]` to the following
structs:
- `Constraint`
- `Direction`
- `Spacing`
- `Layout`
- `AccentedPalette`
- `NonAccentedPalette`
- `Palette`
- `Padding`
- `Borders`
- `BorderType`
- `ListDirection`
- `ScrollbarOrientation`
- `ScrollDirection`
- `RenderDirection`
- `HighlightSpacing`

Fixes #1877
2025-05-31 14:57:13 -07:00
dependabot[bot]
e32a5bf442 build(deps): bump clap-verbosity-flag from 3.0.2 to 3.0.3 (#1870)
Bumps
[clap-verbosity-flag](https://github.com/clap-rs/clap-verbosity-flag)
from 3.0.2 to 3.0.3.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/clap-rs/clap-verbosity-flag/blob/master/CHANGELOG.md">clap-verbosity-flag's
changelog</a>.</em></p>
<blockquote>
<h2>[3.0.3] - 2025-05-20</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="7ac7bff71e"><code>7ac7bff</code></a>
chore: Release clap-verbosity-flag version 3.0.3</li>
<li><a
href="496f3ed5e7"><code>496f3ed</code></a>
docs(readme): Switch to console code blocks</li>
<li><a
href="6ca4f7adc1"><code>6ca4f7a</code></a>
Merge pull request <a
href="https://redirect.github.com/clap-rs/clap-verbosity-flag/issues/143">#143</a>
from joshka/jm/docs-tracing</li>
<li><a
href="352354610f"><code>3523546</code></a>
chore(deps): Update Rust Stable to v1.87 (<a
href="https://redirect.github.com/clap-rs/clap-verbosity-flag/issues/144">#144</a>)</li>
<li><a
href="cda6017809"><code>cda6017</code></a>
docs: Document tracing support in README and lib.rs</li>
<li><a
href="d97ade1611"><code>d97ade1</code></a>
chore(deps): Update Rust Stable to v1.86 (<a
href="https://redirect.github.com/clap-rs/clap-verbosity-flag/issues/141">#141</a>)</li>
<li><a
href="a58fde5b1a"><code>a58fde5</code></a>
chore(deps): Update Rust crate clap to v4.5.31 (<a
href="https://redirect.github.com/clap-rs/clap-verbosity-flag/issues/140">#140</a>)</li>
<li><a
href="d5e646395c"><code>d5e6463</code></a>
chore(deps): Update Rust Stable to v1.85 (<a
href="https://redirect.github.com/clap-rs/clap-verbosity-flag/issues/139">#139</a>)</li>
<li><a
href="e8151159f5"><code>e815115</code></a>
chore(deps): Update Rust Stable to v1.84 (<a
href="https://redirect.github.com/clap-rs/clap-verbosity-flag/issues/137">#137</a>)</li>
<li><a
href="6b0a9d4107"><code>6b0a9d4</code></a>
Merge pull request <a
href="https://redirect.github.com/clap-rs/clap-verbosity-flag/issues/138">#138</a>
from epage/template</li>
<li>Additional commits viewable in <a
href="https://github.com/clap-rs/clap-verbosity-flag/compare/v3.0.2...v3.0.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=clap-verbosity-flag&package-manager=cargo&previous-version=3.0.2&new-version=3.0.3)](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>
2025-05-26 10:17:01 -07:00
dependabot[bot]
3728a0d90a build(deps): bump tokio from 1.45.0 to 1.45.1 (#1871)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.45.0 to 1.45.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tokio-rs/tokio/releases">tokio's
releases</a>.</em></p>
<blockquote>
<h2>Tokio v1.45.1</h2>
<h1>1.45.1 (May 24th, 2025)</h1>
<p>This fixes a regression on the wasm32-unknown-unknown target, where
code that previously did not panic due to calls to
<code>Instant::now()</code> started failing. This is due to the
stabilization of the first time-based metric.</p>
<h3>Fixed</h3>
<ul>
<li>Disable time-based metrics on wasm32-unknown-unknown (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7322">#7322</a>)</li>
</ul>
<p><a
href="https://redirect.github.com/tokio-rs/tokio/issues/7322">#7322</a>:
<a
href="https://redirect.github.com/tokio-rs/tokio/pull/7322">tokio-rs/tokio#7322</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="3768696d92"><code>3768696</code></a>
chore: prepare Tokio v1.45.1 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7359">#7359</a>)</li>
<li><a
href="421a7b001c"><code>421a7b0</code></a>
rt: do not track time-based metrics on wasm32-unknown-unknown (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7322">#7322</a>)</li>
<li><a
href="b1bdb3c57b"><code>b1bdb3c</code></a>
ci: update macros_type_mismatch for Rust 1.87.0 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7339">#7339</a>)</li>
<li>See full diff in <a
href="https://github.com/tokio-rs/tokio/compare/tokio-1.45.0...tokio-1.45.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tokio&package-manager=cargo&previous-version=1.45.0&new-version=1.45.1)](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>
2025-05-26 10:16:03 -07:00
Jagoda Estera Ślązak
b32f78195b feat(no_std): make ratatui-macros no-std (#1865) 2025-05-20 21:22:48 +03:00
dependabot[bot]
7d84d42103 build(deps): bump DavidAnson/markdownlint-cli2-action from 19 to 20 (#1864)
Bumps
[DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action)
from 19 to 20.
<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.18.1, markdownlint
v0.38.0).</h2>
<p>No release notes provided.</p>
<h2>Update markdownlint version (markdownlint-cli2 v0.17.2, markdownlint
v0.37.4).</h2>
<p>No release notes provided.</p>
<h2>Update markdownlint version (markdownlint-cli2 v0.17.0, markdownlint
v0.37.0).</h2>
<p>No release notes provided.</p>
<h2>Update markdownlint version (markdownlint-cli2 v0.15.0, markdownlint
v0.36.1).</h2>
<p>No release notes provided.</p>
<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="992badcdf2"><code>992badc</code></a>
Update to version 20.0.0.</li>
<li><a
href="fafa44c8d4"><code>fafa44c</code></a>
Update dependency: markdownlint-cli2 to 0.18.1.</li>
<li><a
href="052b336194"><code>052b336</code></a>
Bump eslint-plugin-n from 17.17.0 to 17.18.0</li>
<li><a
href="e0b0869c70"><code>e0b0869</code></a>
Bump eslint-plugin-unicorn from 59.0.0 to 59.0.1</li>
<li><a
href="96cc31310c"><code>96cc313</code></a>
Bump <code>@​eslint/js</code> from 9.25.1 to 9.26.0</li>
<li><a
href="66095aa687"><code>66095aa</code></a>
Bump eslint from 9.25.1 to 9.26.0</li>
<li><a
href="51af1d1b7b"><code>51af1d1</code></a>
Bump eslint-plugin-unicorn from 58.0.0 to 59.0.0</li>
<li><a
href="81f6304570"><code>81f6304</code></a>
Bump <code>@​eslint/js</code> from 9.25.0 to 9.25.1</li>
<li><a
href="e2c95fbdf2"><code>e2c95fb</code></a>
Bump eslint from 9.25.0 to 9.25.1</li>
<li><a
href="9f58cc3808"><code>9f58cc3</code></a>
Bump <code>@​eslint/js</code> from 9.24.0 to 9.25.0</li>
<li>Additional commits viewable in <a
href="https://github.com/davidanson/markdownlint-cli2-action/compare/v19...v20">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=19&new-version=20)](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>
2025-05-19 11:53:20 -07:00
182 changed files with 11458 additions and 3221 deletions

81
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,81 @@
# GitHub Copilot Code Review Instructions
## General Review Principles
### Pull Request Size and Scope
- **Flag large PRs**: Comment if a PR changes more than 500 lines or touches many unrelated areas
- **Suggest splitting**: Recommend breaking large changes into smaller, focused PRs
- **Question scope creep**: Ask about unrelated changes that seem outside the PR's stated purpose
### Code Quality and Style
- **Check adherence to style guidelines**: Verify changes follow the project's Rust conventions in [CONTRIBUTING.md](https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md#code-formatting)
- **Verify xtask compliance**: Ensure `cargo xtask format` and `cargo xtask lint` would pass
- **Look for AI-generated patterns**: Be suspicious of verbose, overly-commented, or non-idiomatic code
### Architectural Considerations
- **Reference ARCHITECTURE.md**: Point to [ARCHITECTURE.md](https://github.com/ratatui/ratatui/blob/main/ARCHITECTURE.md) for changes affecting crate boundaries
- **Question fundamental changes**: Flag modifications to core configuration, linting rules, or build setup without clear justification
- **Verify appropriate crate placement**: Ensure changes are in the correct crate per the modular structure
### Breaking Changes and Deprecation
- **Require deprecation**: Insist on deprecation warnings rather than immediate removal of public APIs
- **Ask for migration path**: Request clear upgrade instructions for breaking changes
- **Suggest feature flags**: Recommend feature flags for experimental or potentially disruptive changes
- **Reference versioning policy**: Point to the requirement of at least one version notice before removal
### Testing and Documentation
- **Verify test coverage**: Ensure new functionality includes appropriate tests
- **Check for test removal**: Question any removal of existing tests without clear justification
- **Require documentation**: Ensure public APIs are documented with examples
- **Validate examples**: Check that code examples are minimal and follow project style
### Specific Areas of Concern
#### Configuration Changes
- **Lint configuration**: Question changes to `.clippy.toml`, `rustfmt.toml`, or CI configuration
- **Cargo.toml modifications**: Scrutinize dependency changes or workspace modifications
- **Build system changes**: Require justification for xtask or build process modifications
#### Large Code Additions
- **Question necessity**: Ask if large code additions could be implemented more simply
- **Check for duplication**: Look for code that duplicates existing functionality
- **Verify integration**: Ensure new code integrates well with existing patterns
#### File Organization
- **Validate module structure**: Ensure new modules follow the project's organization
- **Check import organization**: Verify imports follow the std/external/local grouping pattern
- **Review file placement**: Confirm files are in appropriate locations per ARCHITECTURE.md
## Comment Templates
### For Large PRs
```
This PR seems quite large with changes across multiple areas. Consider splitting it into smaller, focused PRs:
- Core functionality changes
- Documentation updates
- Test additions
- Configuration changes
See our [contribution guidelines](https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md#keep-prs-small-intentional-and-focused) for more details.
```
### For Breaking Changes
```
This appears to introduce breaking changes. Please consider:
- Adding deprecation warnings instead of immediate removal
- Providing a clear migration path in the PR description
- Following our [deprecation policy](https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md#deprecation-notice)
```
### For Configuration Changes
```
Changes to project configuration (linting, formatting, build) should be discussed first. Please explain:
- Why this change is necessary
- What problem it solves
- Whether it affects contributor workflow
```
### For Style Issues
```
Please run `cargo xtask format` and `cargo xtask lint` to ensure code follows our style guidelines. See [CONTRIBUTING.md](https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md#code-formatting) for details.
```

View File

@@ -1,6 +1,13 @@
name: Check Pull Requests
# Set the permissions of the github token to the minimum and only enable what is needed
# See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions
permissions: {}
on:
# this workflow is required to be run on pull_request_target as it modifies the PR comments
# care should be taken that the jobs do not run any untrusted input
# zizmor: ignore[dangerous-triggers]
pull_request_target:
types:
- opened
@@ -8,23 +15,21 @@ on:
- synchronize
- labeled
- unlabeled
merge_group:
permissions:
pull-requests: write
jobs:
check-title:
permissions:
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Check PR title
if: github.event_name == 'pull_request_target'
uses: amannn/action-semantic-pull-request@v5
uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5
id: check_pr_title
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Add comment indicating we require pull request titles to follow conventional commits specification
- uses: marocchino/sticky-pull-request-comment@v2
- uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2
if: always() && (steps.check_pr_title.outputs.error_message != null)
with:
header: pr-title-lint-error
@@ -39,40 +44,42 @@ jobs:
# Delete a previous comment when the issue has been resolved
- if: ${{ steps.check_pr_title.outputs.error_message == null }}
uses: marocchino/sticky-pull-request-comment@v2
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2
with:
header: pr-title-lint-error
delete: true
check-breaking-change-label:
permissions:
pull-requests: write
runs-on: ubuntu-latest
env:
# use an environment variable to pass untrusted input to the script
# see https://securitylab.github.com/research/github-actions-untrusted-input/
PR_TITLE: ${{ github.event.pull_request.title }}
steps:
- name: Check breaking change label
id: check_breaking_change
run: |
pattern='^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\(\w+\))?!:'
# Check if pattern matches
if echo "${PR_TITLE}" | grep -qE "$pattern"; then
echo "breaking_change=true" >> $GITHUB_OUTPUT
else
echo "breaking_change=false" >> $GITHUB_OUTPUT
fi
- name: Add label
if: steps.check_breaking_change.outputs.breaking_change == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['Type: Breaking Change']
})
- name: Check breaking change label
id: check_breaking_change
run: |
pattern='^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\(\w+\))?!:'
# Check if pattern matches
if echo "${PR_TITLE}" | grep -qE "$pattern"; then
echo "breaking_change=true" >> $GITHUB_OUTPUT
else
echo "breaking_change=false" >> $GITHUB_OUTPUT
fi
- name: Add label
if: steps.check_breaking_change.outputs.breaking_change == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['Type: Breaking Change']
})
do-not-merge:
if: ${{ contains(github.event.*.labels.*.name, 'do not merge') }}

View File

@@ -1,5 +1,9 @@
name: Check Semver
# Set the permissions of the github token to the minimum and only enable what is needed
# See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions
permissions: {}
on:
pull_request:
branches:
@@ -11,6 +15,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- name: Check semver
uses: obi1kenobi/cargo-semver-checks-action@v2
uses: obi1kenobi/cargo-semver-checks-action@5b298c9520f7096a4683c0bd981a7ac5a7e249ae # v2

View File

@@ -1,5 +1,9 @@
name: Continuous Integration
# Set the permissions of the github token to the minimum and only enable what is needed
# See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions
permissions: {}
on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
@@ -25,11 +29,15 @@ jobs:
name: Check Formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
with: { components: rustfmt }
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@v2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master
with:
toolchain: nightly
components: rustfmt
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
- uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2
with:
tool: taplo-cli
- run: cargo xtask format --check
@@ -40,8 +48,10 @@ jobs:
name: Check Typos
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: crate-ci/typos@master
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: crate-ci/typos@392b78fe18a52790c53f42456e46124f77346842 # master
# Check for any disallowed dependencies in the codebase due to license / security issues.
# See <https://github.com/EmbarkStudios/cargo-deny>
@@ -49,9 +59,15 @@ jobs:
name: Check Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@cargo-deny
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master
with:
toolchain: stable
- uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2
with:
tool: cargo-deny
- run: cargo deny --log-level info --all-features check
# Check for any unused dependencies in the codebase.
@@ -60,8 +76,10 @@ jobs:
name: Check Unused Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bnjbvr/cargo-machete@v0.8.0
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: bnjbvr/cargo-machete@b54422fa3319b3cac180f6030b663fe57af51635 # v0.8.0
# Run cargo clippy.
#
@@ -77,12 +95,14 @@ jobs:
toolchain: ["stable", "beta"]
continue-on-error: ${{ matrix.toolchain == 'beta' }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master
with:
toolchain: ${{ matrix.toolchain }}
components: clippy
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
- run: cargo xtask clippy
# Run markdownlint on all markdown files in the repository.
@@ -90,8 +110,10 @@ jobs:
name: Check Markdown
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DavidAnson/markdownlint-cli2-action@v19
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: DavidAnson/markdownlint-cli2-action@992badcdf24e3b8eb7e87ff9287fe931bcb00c6e # v20
with:
globs: |
'**/*.md'
@@ -103,14 +125,19 @@ jobs:
name: Coverage Report
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master
with:
toolchain: stable
components: llvm-tools
- uses: taiki-e/install-action@cargo-llvm-cov
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2
with:
tool: cargo-llvm-cov
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
- run: cargo xtask coverage
- uses: codecov/codecov-action@v5
- uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
@@ -125,28 +152,35 @@ jobs:
toolchain: ["1.85.0", "stable"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master
with:
toolchain: ${{ matrix.toolchain }}
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
- run: cargo xtask check --all-features
build-no-std:
name: Build No-Std
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master
with:
toolchain: stable
targets: x86_64-unknown-none
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
# This makes it easier to debug the exact versions of the dependencies
- run: cargo tree --target x86_64-unknown-none -p ratatui-core
- run: cargo tree --target x86_64-unknown-none -p ratatui-widgets
- run: cargo tree --target x86_64-unknown-none -p ratatui-macros
- run: cargo tree --target x86_64-unknown-none -p ratatui --no-default-features
- run: cargo build --target x86_64-unknown-none -p ratatui-core
- run: cargo build --target x86_64-unknown-none -p ratatui-widgets
- run: cargo build --target x86_64-unknown-none -p ratatui-macros
- run: cargo build --target x86_64-unknown-none -p ratatui --no-default-features
# Check if README.md is up-to-date with the crate's documentation.
@@ -154,9 +188,13 @@ jobs:
name: Check README
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@cargo-rdme
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
- uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2
with:
tool: cargo-rdme
- run: cargo xtask readme --check
# Run cargo rustdoc with the same options that would be used by docs.rs, taking into account the
@@ -167,10 +205,19 @@ jobs:
env:
RUSTDOCFLAGS: -Dwarnings
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- uses: dtolnay/install@cargo-docs-rs
- uses: Swatinem/rust-cache@v2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master
with:
toolchain: nightly
- uses: dtolnay/install@74f735cdf643820234e37ae1c4089a08fd266d8a # master
with:
crate: cargo-docs-rs
- uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2
with:
tool: cargo-hack
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
- run: cargo xtask docs
# Run cargo test on the documentation of the crate. This will catch any code examples that don't
@@ -179,9 +226,13 @@ jobs:
name: Test Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master
with:
toolchain: stable
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
- run: cargo xtask test-docs
# Run cargo test on the libraries of the crate.
@@ -193,9 +244,13 @@ jobs:
matrix:
toolchain: ["1.85.0", "stable"]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master
with:
toolchain: stable
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
- run: cargo xtask test-libs
# Run cargo test on all the backends.
@@ -212,7 +267,11 @@ jobs:
- os: windows-latest
backend: termion
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master
with:
toolchain: stable
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
- run: cargo xtask test-backend ${{ matrix.backend }}

View File

@@ -1,5 +1,9 @@
name: Release alpha version
# Set the permissions of the github token to the minimum and only enable what is needed
# See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions
permissions: {}
on:
workflow_dispatch:
schedule:
@@ -15,25 +19,30 @@ jobs:
publish-alpha:
name: Create an alpha release
runs-on: ubuntu-latest
if: github.repository == 'ratatui/ratatui'
permissions:
contents: write
steps:
- name: Checkout the repository
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
# probably needs to be set to true for git-cliff to work - check build
persist-credentials: false
- name: Calculate the next release
run: .github/workflows/calculate-alpha-release.bash
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master
with:
toolchain: stable
- name: Publish
run: cargo publish --allow-dirty --token ${{ secrets.CARGO_TOKEN }}
- name: Generate a changelog
uses: orhun/git-cliff-action@v4
uses: orhun/git-cliff-action@4a4a951bc43fafe41cd2348d181853f52356bee7 # v4
with:
config: cliff.toml
args: --unreleased --tag ${{ env.NEXT_TAG }} --strip header
@@ -41,7 +50,7 @@ jobs:
OUTPUT: BODY.md
- name: Publish on GitHub
uses: ncipollo/release-action@v1
uses: ncipollo/release-action@bcfe5470707e8832e12347755757cec0eb3c22af # v1
with:
tag: ${{ env.NEXT_TAG }}
prerelease: true

View File

@@ -1,8 +1,8 @@
name: Release-plz
permissions:
pull-requests: write
contents: write
# Set the permissions of the github token to the minimum and only enable what is needed
# See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions
permissions: {}
on:
push:
@@ -14,26 +14,38 @@ jobs:
# Release unpublished packages.
release-plz-release:
name: Release-plz release
environment: release
permissions:
pull-requests: write
contents: write
id-token: write
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'ratatui' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master
with:
toolchain: stable
- uses: rust-lang/crates-io-auth-action@e919bc7605cde86df457cf5b93c5e103838bd879 # v1
id: auth
- name: Run release-plz
uses: release-plz/action@v0.5
uses: release-plz/action@068d76d2aa32d3c9cd0b1ccdd9ac921e28ba2be9 # v0.5
with:
command: release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
# Create a PR with the new versions and changelog, preparing the next release.
release-plz-pr:
name: Release-plz PR
permissions:
pull-requests: write
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'ratatui' }}
concurrency:
@@ -41,13 +53,16 @@ jobs:
cancel-in-progress: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master
with:
toolchain: stable
- name: Run release-plz
uses: release-plz/action@v0.5
uses: release-plz/action@068d76d2aa32d3c9cd0b1ccdd9ac921e28ba2be9 # v0.5
with:
command: release-pr
env:

View File

@@ -1,5 +1,9 @@
name: Release stable version
# Set the permissions of the github token to the minimum and only enable what is needed
# See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions
permissions: {}
on:
push:
tags:
@@ -13,12 +17,14 @@ jobs:
contents: write
steps:
- name: Checkout the repository
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
# this possibly needs to be set to true for git-cliff to work - check build results
persist-credentials: false
- name: Generate a changelog
uses: orhun/git-cliff-action@v4
uses: orhun/git-cliff-action@4a4a951bc43fafe41cd2348d181853f52356bee7 # v4
with:
config: cliff.toml
args: --latest --strip header
@@ -26,7 +32,7 @@ jobs:
OUTPUT: BODY.md
- name: Publish on GitHub
uses: ncipollo/release-action@v1
uses: ncipollo/release-action@bcfe5470707e8832e12347755757cec0eb3c22af # v1
with:
prerelease: false
bodyFile: BODY.md
@@ -36,10 +42,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master
with:
toolchain: stable
- name: Publish
run: cargo publish --token ${{ secrets.CARGO_TOKEN }}

26
.github/workflows/zizmor.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: GitHub Actions Security Analysis with zizmor 🌈
# docs https://docs.zizmor.sh/integrations/#github-actions
on:
push:
branches: ["main"]
pull_request:
branches: ["**"]
permissions: {}
jobs:
zizmor:
name: Run zizmor 🌈
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Run zizmor 🌈
uses: zizmorcore/zizmor-action@f52a838cfabf134edcbaa7c8b3677dde20045018 # v0.1.1

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ target
*.rs.rustfmt
.gdb_history
.idea/
.env

203
ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,203 @@
# Ratatui Architecture
This document provides a comprehensive overview of Ratatui's architecture and crate
organization, introduced in version 0.30.0.
## Overview
Starting with Ratatui 0.30.0, the project was reorganized from a single monolithic crate into
a modular workspace consisting of multiple specialized crates. This architectural decision was
made to improve modularity, reduce compilation times, enable more flexible dependency
management, and provide better API stability for third-party widget libraries.
## Crate Organization
The Ratatui project is now organized as a Cargo workspace containing the following crates:
### Core Crates
#### `ratatui` (Main Crate)
- **Purpose**: The main entry point that most applications should use
- **Contents**: Re-exports everything from other crates for convenience, plus experimental features
- **Target Users**: Application developers building terminal UIs
- **Key Features**:
- Complete widget ecosystem
- Backend implementations
- Layout system
- Terminal management
- Experimental `WidgetRef` and `StatefulWidgetRef` traits
#### `ratatui-core`
- **Purpose**: Foundational types and traits for the Ratatui ecosystem
- **Contents**: Core widget traits, text types, buffer, layout, style, and symbols
- **Target Users**: Widget library authors, minimalist projects
- **Key Features**:
- `Widget` and `StatefulWidget` traits
- Text rendering (`Text`, `Line`, `Span`)
- Buffer management
- Layout system
- Style and color definitions
- Symbol collections
#### `ratatui-widgets`
- **Purpose**: Built-in widget implementations
- **Contents**: All standard widgets like `Block`, `Paragraph`, `List`, `Chart`, etc.
- **Target Users**: Applications needing standard widgets, widget library authors
- **Key Features**:
- Complete set of built-in widgets
- Optimized implementations
- Comprehensive documentation and examples
### Backend Crates
#### `ratatui-crossterm`
- **Purpose**: Crossterm backend implementation
- **Contents**: Cross-platform terminal backend using the `crossterm` crate
- **Target Users**: Applications targeting multiple platforms
#### `ratatui-termion`
- **Purpose**: Termion backend implementation
- **Contents**: Unix-specific terminal backend using the `termion` crate
- **Target Users**: Unix-specific applications requiring low-level control
#### `ratatui-termwiz`
- **Purpose**: Termwiz backend implementation
- **Contents**: Terminal backend using the `termwiz` crate
- **Target Users**: Applications needing advanced terminal features
### Utility Crates
#### `ratatui-macros`
- **Purpose**: Declarative macros for common patterns and boilerplate reduction
- **Contents**: Macros for common patterns and boilerplate reduction
- **Target Users**: Applications and libraries wanting macro support
## Dependency Relationships
```text
ratatui
├── ratatui-core
├── ratatui-widgets → ratatui-core
├── ratatui-crossterm → ratatui-core
├── ratatui-termion → ratatui-core
├── ratatui-termwiz → ratatui-core
└── ratatui-macros
```
### Key Dependencies
- **ratatui-core**: Foundation for all other crates
- **ratatui-widgets**: Depends on `ratatui-core` for widget traits and types
- **Backend crates**: Each depends on `ratatui-core` for backend traits and types
- **ratatui**: Depends on all other crates and re-exports their public APIs
## Design Principles
### Stability and Compatibility
The modular architecture provides different levels of API stability:
- **ratatui-core**: Designed for maximum stability to minimize breaking changes for widget
libraries
- **ratatui-widgets**: Focused on widget implementations with moderate stability requirements
- **Backend crates**: Isolated from core changes, allowing backend-specific updates
- **ratatui**: Main crate that can evolve more freely while maintaining backward compatibility
through re-exports
### Compilation Performance
The split architecture enables:
- **Reduced compilation times**: Widget libraries only need to compile core types
- **Parallel compilation**: Different crates can be compiled in parallel
- **Selective compilation**: Applications can exclude unused backends or widgets
### Ecosystem Benefits
- **Widget Library Authors**: Can depend on stable `ratatui-core` without frequent updates
- **Application Developers**: Can use the convenient `ratatui` crate with everything included
- **Minimalist Projects**: Can use only `ratatui-core` for lightweight applications
## Migration Guide
### For Application Developers
Most applications should continue using the main `ratatui` crate with minimal changes:
```rust
// No changes needed - everything is re-exported
use ratatui::{
widgets::{Block, Paragraph},
layout::{Layout, Constraint},
Terminal,
};
```
### For Widget Library Authors
Consider migrating to `ratatui-core` for better stability:
```rust
// Before (0.29.x and earlier)
use ratatui::{
widgets::{Widget, StatefulWidget},
buffer::Buffer,
layout::Rect,
};
// After (0.30.0+)
use ratatui_core::{
widgets::{Widget, StatefulWidget},
buffer::Buffer,
layout::Rect,
};
```
### Backwards Compatibility
All existing code using the `ratatui` crate will continue to work unchanged, as the main crate
re-exports all public APIs from the specialized crates.
## Future Considerations
### Potential Enhancements
- **Widget-specific crates**: Further split widgets into individual crates for even more
granular dependencies
- **Plugin system**: Enable dynamic widget loading and third-party widget ecosystems
- **Feature flags**: More granular feature flags for compile-time customization
### Version Synchronization
Currently, all crates are versioned together for simplicity. Future versions may adopt
independent versioning once the API stabilizes further.
## Related Issues and PRs
This architecture was developed through extensive discussion and implementation across multiple
PRs:
- [Issue #1388](https://github.com/ratatui/ratatui/issues/1388): Original RFC for modularization
- [PR #1459](https://github.com/ratatui/ratatui/pull/1459): Move ratatui crate into workspace
folder
- [PR #1460](https://github.com/ratatui/ratatui/pull/1460): Move core types to ratatui-core
- [PR #1474](https://github.com/ratatui/ratatui/pull/1474): Move widgets into ratatui-widgets
crate
## Contributing
When contributing to the Ratatui project, please consider:
- **Core changes**: Submit PRs against `ratatui-core` for fundamental improvements
- **Widget changes**: Submit PRs against `ratatui-widgets` for widget-specific improvements
- **Backend changes**: Submit PRs against the appropriate backend crate
- **Integration changes**: Submit PRs against the main `ratatui` crate
See the [CONTRIBUTING.md](CONTRIBUTING.md) guide for more details on the contribution process.

View File

@@ -11,18 +11,25 @@ GitHub with a [breaking change] label.
This is a quick summary of the sections below:
- [v0.30.0 Unreleased](#v0300-unreleased)
- `Flex::SpaceAround` now mirrors flexbox: space between items is twice the size of the outer gaps
are twice the size of first and last elements
- `block::Title` no longer exists
- The `From` impls for backend types are now replaced with more specific traits
- `FrameExt` trait for `unstable-widget-ref` feature
- `List::highlight_symbol` now accepts `Into<Line>` instead of `&str`
- 'layout::Alignment' is renamed to 'layout::HorizontalAlignment'
- The MSRV is now 1.81.0
- The MSRV is now 1.85.0
- `Backend` now requires an associated `Error` type and `clear_region` method
- `TestBackend` now uses `core::convert::Infallible` for error handling instead of `std::io::Error`
- Disabling `default-features` will now disable layout cache, which can have a negative impact on performance
- `Layout::init_cache` and `Layout::DEFAULT_CACHE_SIZE` are now only available if `layout-cache` feature is enabled
- Disabling `default-features` suppresses the error message if `show_cursor()` fails when dropping `Terminal`
- `Layout::init_cache` and `Layout::DEFAULT_CACHE_SIZE` are now only available if `layout-cache`
feature is enabled
- Disabling `default-features` suppresses the error message if `show_cursor()` fails when dropping
`Terminal`
- Support a broader range for `unicode-width` version
- [v0.29.0](#v0290)
- `Sparkline::data` takes `IntoIterator<Item = SparklineBar>` instead of `&[u64]` and is no longer const
- `Sparkline::data` takes `IntoIterator<Item = SparklineBar>` instead of `&[u64]` and is no longer
const
- Removed public fields from `Rect` iterators
- `Line` now implements `From<Cow<str>`
- `Table::highlight_style` is now `Table::row_highlight_style`
@@ -85,6 +92,60 @@ This is a quick summary of the sections below:
## v0.30.0 Unreleased
### `Flex::SpaceAround` now mirrors flexbox: space between items is twice the size of the outer gaps ([#1952])
[#1952]: https://github.com/ratatui/ratatui/pull/1952
The old `Flex::SpaceAround` behavior has been changed to distribute space evenly around each
element, with the middle spacers being twice the size of the first and last one. The old
behavior can be achieved by using `Flex::SpaceEvenly` instead.
```diff
- let rects = Layout::horizontal([Length(1), Length(2)]).flex(Flex::SpaceAround).split(area);
+ let rects = Layout::horizontal([Length(1), Length(2)]).flex(Flex::SpaceEvenly).split(area);
```
### `block::Title` no longer exists ([#1926])
[#1926]: https://github.com/ratatui/ratatui/pull/1926
The title alignment is better expressed in the `Line` as this fits more coherently with the rest of
the library.
- `widgets::block` is no longer exported
- `widgets::block::Title` no longer exists
- `widgets::block::Position` is now `widgets::TitlePosition`
- `Block::title()` now accepts `Into::<Line>` instead of `Into<Title>`
- `BlockExt` is now exported at widgets::`BlockExt` instead of `widgets::block::BlockExt`
```diff
- use ratatui::widgets::{Block, block::{Title, Position}};
+ use ratatui::widgets::{Block, TitlePosition};
let block = Block::default()
- .title(Title::from("Hello"))
- .title(Title::from("Hello").position(Position::Bottom).alignment(Alignment::Center))
- .title_position(Position::Bottom);
+ .title(Line::from("Hello"))
+ .title_bottom(Line::from("Hello").centered());
+ .title_position(TitlePosition::Bottom);
- use ratatui::widgets::block::BlockExt;
+ use ratatui::widgets::BlockExt;
struct MyWidget {
block: Option<Block>,
}
impl Widget for &MyWidget {
fn render(self, area: Rect, buf: &mut Buffer) {
self.block.as_ref().render(area, buf);
let area = self.block.inner_if_some();
// ...
}
}
```
### `Style` no longer implements `Styled` ([#1572])
[#1572]: https://github.com/ratatui/ratatui/pull/1572
@@ -148,12 +209,37 @@ and implement the `clear_region` method, which no longer has a default implement
This change was made to provide greater flexibility for custom backends, particularly to remove the
explicit dependency on `std::io` for backends that want to support `no_std` targets.
### The MSRV is now 1.81.0 ([#1786])
If your app or library uses the `Backend` trait directly - for example, by providing a generic
implementation for many backends - you may need to update the referenced error type.
[#1786]: https://github.com/ratatui/ratatui/pull/1786
```diff
- fn run<B: Backend>(mut terminal: Terminal<B>) -> io::Result<()> {
+ fn run<B: Backend>(mut terminal: Terminal<B>) -> Result<(), B::Error> {
```
The minimum supported Rust version (MSRV) is now 1.81.0. This is due to the use of `#[expect]` in
the codebase, which is only available in Rust 1.81.0 and later.
Alternatively, you can explicitly require the associated error to be `std::io::Error`. This approach
may require fewer changes in user code but is generally not recommended, as it limits compatibility
with third-party backends. Additionally, the error type used by built-in backends may or may not
change in the future, making this approach less future-proof compared to the previous one.
```diff
- fn run<B: Backend>(mut terminal: Terminal<B>) -> io::Result<()> {
+ fn run<B: Backend<Error = io::Error>>(mut terminal: Terminal<B>) -> io::Result<()> {
```
If your application uses a concrete backend implementation, prefer specifying it explicitly
instead.
```diff
- fn run<B: Backend>(mut terminal: Terminal<B>) -> io::Result<()> {
+ fn run(mut terminal: DefaultTerminal) -> io::Result<()> {
```
### The MSRV is now 1.85.0 ([#1860])
[#1860]: https://github.com/ratatui/ratatui/pull/1860
The minimum supported Rust version (MSRV) is now 1.85.0.
### `layout::Alignment` is renamed to `layout::HorizontalAlignment` ([#1735])
@@ -292,6 +378,17 @@ The `termwiz` backend is upgraded from 0.22.0 to 0.23.0.
This release has a few fixes for hyperlinks and input handling, plus some dependency updates.
See the [commits](https://github.com/wezterm/wezterm/commits/main/termwiz) for more details.
### Support a broader range for `unicode-width` version ([#1999])
[#1999]: https://github.com/ratatui/ratatui/pull/1999
Ratatui's dependency on `unicode-width`, previously pinned to 0.2.0, has
expanded to allow version 0.2.1. This comes with 2 behavior changes described in
[unicode-width#61] and [unicode-width#74].
[unicode-width#61]: https://github.com/unicode-rs/unicode-width/pull/61
[unicode-width#74]: https://github.com/unicode-rs/unicode-width/pull/74
## [v0.29.0](https://github.com/ratatui/ratatui/releases/tag/v0.29.0)
### `Sparkline::data` takes `IntoIterator<Item = SparklineBar>` instead of `&[u64]` and is no longer const ([#1326])

File diff suppressed because it is too large Load Diff

View File

@@ -22,13 +22,33 @@ on the crate or its users should ideally be discussed in a "Feature Request" iss
### Keep PRs small, intentional, and focused
Try to do one pull request per change. The time taken to review a PR grows exponential with the size
of the change. Small focused PRs will generally be much more faster to review. PRs that include both
Try to do one pull request per change. The time taken to review a PR grows exponentially with the size
of the change. Small focused PRs will generally be much faster to review. PRs that include both
refactoring (or reformatting) with actual changes are more difficult to review as every line of the
change becomes a place where a bug may have been introduced. Consider splitting refactoring /
reformatting changes into a separate PR from those that make a behavioral change, as the tests help
guarantee that the behavior is unchanged.
Guidelines for PR size:
- Aim for PRs under 500 lines of changes when possible.
- Split large features into incremental PRs that build on each other.
- Separate refactoring, formatting, and functional changes into different PRs.
- If a large PR is unavoidable, clearly explain why in the PR description.
### Breaking changes and backwards compatibility
We prioritize maintaining backwards compatibility and minimizing disruption to users:
- **Prefer deprecation over removal**: Add deprecation warnings rather than immediately removing
public APIs
- **Provide migration paths**: Include clear upgrade instructions for any breaking changes
- **Follow our deprecation policy**: Wait at least two versions before removing deprecated items
- **Consider feature flags**: Use feature flags for experimental or potentially disruptive changes
- **Document breaking changes**: Clearly mark breaking changes in commit messages and PR descriptions
See our [deprecation notice policy](#deprecation-notice) for more details.
### Code formatting
Run `cargo xtask format` before committing to ensure that code is consistently formatted with
@@ -41,6 +61,10 @@ to be installed when running rustfmt. You can install the nightly version of Rus
rustup install nightly
```
> [!IMPORTANT]
> Do not modify formatting configuration (`rustfmt.toml`, `.clippy.toml`) without
> prior discussion. These changes affect all contributors and should be carefully considered.
### Search `tui-rs` for similar work
The original fork of Ratatui, [`tui-rs`](https://github.com/fdehau/tui-rs/), has a large amount of
@@ -67,6 +91,15 @@ Running `cargo xtask ci` before pushing will perform the same checks that we do
It's not mandatory to do this before pushing, however it may save you time to do so instead of
waiting for GitHub to run the checks.
Available xtask commands:
- `cargo xtask ci` - Run all CI checks
- `cargo xtask format` - Format code
- `cargo xtask lint` - Run linting checks
- `cargo xtask test` - Run all tests
Run `cargo xtask --help` to see all available commands.
### Sign your commits
We use commit signature verification, which will block commits from being merged via the UI unless
@@ -74,6 +107,17 @@ they are signed. To set up your machine to sign commits, see [managing commit si
verification](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification)
in GitHub docs.
### Configuration and build system changes
Changes to project configuration files require special consideration:
- Linting configuration (`.clippy.toml`, `rustfmt.toml`): Affects all contributors.
- CI configuration (`.github/workflows/`): Affects build and deployment.
- Build system (`xtask/`, `Cargo.toml` workspace config): Affects development workflow.
- Dependencies: Consider MSRV compatibility and licensing.
Please discuss these changes in an issue before implementing them.
## Implementation Guidelines
### Setup
@@ -91,6 +135,20 @@ cd ratatui
cargo xtask build
```
### Architecture
For an understanding of the crate organization and design decisions, see [ARCHITECTURE.md]. This
document explains the modular workspace structure introduced in version 0.30.0 and provides
guidance on which crate to use for different use cases.
When making changes, consider:
- Which crate should contain your changes per the modular structure,
- Whether your changes affect the public API of `ratatui-core` (requires extra care),
- And how your changes fit into the overall architecture.
[ARCHITECTURE.md]: https://github.com/ratatui/ratatui/blob/main/ARCHITECTURE.md
### Tests
The [test coverage](https://app.codecov.io/gh/ratatui/ratatui) of the crate is reasonably
@@ -107,6 +165,10 @@ If an area that you're making a change in is not tested, write tests to characte
behavior before changing it. This helps ensure that we don't introduce bugs to existing software
using Ratatui (and helps make it easy to migrate apps still using `tui-rs`).
> [!IMPORTANT]
> Do not remove existing tests without clear justification. If tests need to be
> modified due to API changes, explain why in your PR description.
For coverage, we have two [bacon](https://dystroy.org/bacon/) jobs (one for all tests, and one for
unit tests, keyboard shortcuts `v` and `u` respectively) that run
[cargo-llvm-cov](https://github.com/taiki-e/cargo-llvm-cov) to report the coverage. Several plugins
@@ -174,6 +236,14 @@ We generally want to wait at least two versions before removing deprecated items
time to update. However, if a deprecation is blocking for us to implement a new feature we may
*consider* removing it in a one version notice.
Deprecation process:
1. Add `#[deprecated]` attribute with a clear message.
2. Update documentation to point to the replacement.
3. Add an entry to `BREAKING-CHANGES.md` if applicable.
4. Wait at least two versions before removal.
5. Consider the impact on the ecosystem before removing.
### Use of unsafe for optimization purposes
We don't currently use any unsafe code in Ratatui, and would like to keep it that way. However, there

673
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["ratatui", "ratatui-*", "xtask", "examples/apps/*"]
members = ["ratatui", "ratatui-*", "xtask", "examples/apps/*", "examples/concepts/*"]
default-members = [
"ratatui",
"ratatui-core",
@@ -11,6 +11,7 @@ default-members = [
"ratatui-termwiz",
"ratatui-widgets",
"examples/apps/*",
"examples/concepts/*",
]
[workspace.package]
@@ -40,20 +41,20 @@ hashbrown = "0.15"
indoc = "2"
instability = "0.3"
itertools = { version = "0.14", default-features = false, features = ["use_alloc"] }
kasuari = { version = "0.4", default-features = false }
kasuari = { git = "https://github.com/j-g00da/kasuari.git", branch = "js/cycle-prevention", default-features = false }
line-clipping = "0.3"
lru = "0.14"
palette = "0.7"
pretty_assertions = "1"
rand = "0.9"
rand_chacha = "0.9"
ratatui = { path = "ratatui", version = "0.30.0-alpha.4" }
ratatui-core = { path = "ratatui-core", version = "0.1.0-alpha.5" }
ratatui-crossterm = { path = "ratatui-crossterm", version = "0.1.0-alpha.4" }
ratatui-macros = { path = "ratatui-macros", version = "0.7.0-alpha.3" }
ratatui-termion = { path = "ratatui-termion", version = "0.1.0-alpha.4" }
ratatui-termwiz = { path = "ratatui-termwiz", version = "0.1.0-alpha.4" }
ratatui-widgets = { path = "ratatui-widgets", version = "0.3.0-alpha.4" }
ratatui = { path = "ratatui", version = "0.30.0-alpha.5" }
ratatui-core = { path = "ratatui-core", version = "0.1.0-alpha.6" }
ratatui-crossterm = { path = "ratatui-crossterm", version = "0.1.0-alpha.5" }
ratatui-macros = { path = "ratatui-macros", version = "0.7.0-alpha.4" }
ratatui-termion = { path = "ratatui-termion", version = "0.1.0-alpha.5" }
ratatui-termwiz = { path = "ratatui-termwiz", version = "0.1.0-alpha.5" }
ratatui-widgets = { path = "ratatui-widgets", version = "0.3.0-alpha.5" }
rstest = "0.25"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@@ -70,7 +71,7 @@ trybuild = "1"
unicode-segmentation = "1"
unicode-truncate = { version = "2", default-features = false }
# See <https://github.com/ratatui/ratatui/issues/1271> for information about why we pin unicode-width
unicode-width = "=0.2.0"
unicode-width = ">=0.2.0, <=0.2.1"
# Improve benchmark consistency
[profile.bench]

View File

@@ -73,6 +73,7 @@ fn render(frame: &mut Frame) {
- [Ratatui Forum] - a place to ask questions and discuss the library.
- [Widget Examples] - a collection of examples that demonstrate how to use the library.
- [App Examples] - a collection of more complex examples that demonstrate how to build apps.
- [ARCHITECTURE.md] - explains the crate organization and modular workspace structure.
- [Changelog] - generated by [git-cliff] utilizing [Conventional Commits].
- [Breaking Changes] - a list of breaking changes in the library.
@@ -114,6 +115,14 @@ We rely on GitHub for [bugs][Report a bug] and [feature requests][Request a Feat
Please make sure you read the [contributing](./CONTRIBUTING.md) guidelines before [creating a pull
request][Create a Pull Request].
If you'd like to show your support, you can add the Ratatui badge to your project's README:
```md
[![Built With Ratatui](https://ratatui.rs/built-with-ratatui/badge.svg)](https://ratatui.rs/)
```
[![Built With Ratatui](https://ratatui.rs/built-with-ratatui/badge.svg)](https://ratatui.rs/)
## Acknowledgements
Ratatui was forked from the [tui-rs] crate in 2023 in order to continue its development. None of
@@ -133,6 +142,7 @@ This project is licensed under the [MIT License][License].
[Docs]: https://docs.rs/ratatui
[Widget Examples]: https://github.com/ratatui/ratatui/tree/main/ratatui-widgets/examples
[App Examples]: https://github.com/ratatui/ratatui/tree/main/examples
[ARCHITECTURE.md]: https://github.com/ratatui/ratatui/blob/main/ARCHITECTURE.md
[Changelog]: https://github.com/ratatui/ratatui/blob/main/CHANGELOG.md
[git-cliff]: https://git-cliff.org
[Conventional Commits]: https://www.conventionalcommits.org

1
assets/logo-simple.svg Normal file
View File

@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><title>Ratatui</title><path d="M17 29h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1H5v-1H4v-1H3v-1H2v-1H1v-1H0v-2h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h2zm-2 14h1v1h1v1h1v-2h-1v-1h-1v-1h-1zm0-6h-3v1h1v1h4v-4h-1v-1h-1zm35-21h-1v4h-1v1h-1v1h-1v1h-2v1h-1v1h-1v1h-1v1h-2v9h5v1h1v9h-1v1h-1v2h-1v1h-1v-1h-1v-1h-1v-1h1v-1h1v-1h1v-5h-1v1h-3v1h-1v3h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-2h1v-2h1v-1h-2v1h-4v-1h-1v-1h-1v-1h-1v-2h1v-1h1v-1h2v-1h7v-1h1v-1h1v-1h1v-1h1v-1h1v-1h4v-1h3v-1h10zm-17 2h-1v2h1v1h2v-1h1v-2h-1v-1h-2zM29 1h1v9h1v1h1v2h1v2h-1v1h-1v1h-1v1h-1v1h-1v1h-3v-1h-1v-1h-2v-1h-1v-1h-1v-1h-6v-1h-1V9h1V8h1V7h1V6h1V5h1V4h1V3h1V2h1V1h2V0h6z"/></svg>

After

Width:  |  Height:  |  Size: 831 B

1
assets/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><title>Ratatui</title><path d="M50 16h-1v4h-1v1h-1v1h-1v1h-2v1h-1v1h-1v1h-1v1h-2v9h5v1h1v9h-1v1h-1v2h-1v1h-1v-1h-1v1h-2v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-2v1h-1v1h-1v1h-1v1h-1v1h-1v1h-1v1H9v1H8v1H7v1H6v1H5v1H4v1H3v1H2v2h1v1h1v1h1v1h1v1h1v1H5v-1H4v-1H3v-1H2v-1H1v-1H0v-2h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h2v1h1v-1h-1v-1h-1v-2h1v-1h1v-1h2v-1h7v-1h1v-1h1v-1h1v-1h1v-1h1v-1h4v-1h3v-1h10zM39 49h1v-1h-1zm2-8h-3v1h-1v3h-1v-1h-1v1h1v1h1v1h1v1h1v-1h1v-1h1v-1h1v-5h-1zm-7 3h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-5-5h1v1h1v1h1v1h1v1h1v-2h1v-2h1v-1h-2v1h-4v-1h-1zm14-11h-1v2h1v1h2v-1h1v-2h-1v-1h-2zM18 40h1v1h1v2h-1v-1h-1v-1h-1v-2h1zm0-7h1v4h-4v-1h-1v-1h3v-3h1zM29 1h1v9h1v1h1v2h1v2h-1v1h-1v1h-1v1h-1v1h-1v1h-3v-1h-1v-1h-2v-1h-1v-1h-1v-1h-6v-1h-1V9h1V8h1V7h1V6h1V5h1V4h1V3h1V2h1V1h2V0h6z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -60,6 +60,22 @@ command = ["cargo", "xtask", "hack"]
[jobs.format]
command = ["cargo", "xtask", "format"]
[jobs.zizmor-offline]
# zizmor checks the workflow files for security issues. The offline version is generally faster, but
# checks for fewer issues.
command = ["zizmor", "--color", "always", ".github/workflows", "--offline"]
need_stdout = true
default_watch = false
watch = [".github/workflows/"]
[jobs.zizmor-online]
# zizmor checks the workflow files for security issues. The online version is a bit slower, but it
# checks for more issues
command = ["zizmor", "--color", "always", ".github/workflows"]
need_stdout = true
default_watch = false
watch = [".github/workflows/"]
# You may define here keybindings that would be specific to
# a project, for example a shortcut to launch a specific job.
# Shortcuts to internal functions (scrolling, toggling, etc.)
@@ -74,3 +90,5 @@ ctrl-v = "job:coverage-unit-tests-only"
u = "job:test-unit"
n = "job:nextest"
f = "job:format"
z = "job:zizmor-offline"
shift-z = "job:zizmor-online"

View File

@@ -115,6 +115,7 @@ commit_parsers = [
{ message = "^chore\\(changelog\\)", skip = true },
{ message = "^[cC]hore", group = "<!-- 07 -->Miscellaneous Tasks" },
{ body = ".*security", group = "<!-- 08 -->Security" },
{ message = "^build\\(deps\\)", skip = true },
{ message = "^build", group = "<!-- 09 -->Build" },
{ message = "^ci", group = "<!-- 10 -->Continuous Integration" },
{ message = "^revert", group = "<!-- 11 -->Reverted Commits" },

View File

@@ -9,7 +9,6 @@ allow = [
"BSD-3-Clause",
"ISC",
"MIT",
"OpenSSL",
"Unicode-3.0",
"Unicode-DFS-2016",
"WTFPL",

View File

@@ -21,10 +21,7 @@ use ratatui::widgets::{Widget, WidgetRef};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = App::default().run(terminal);
ratatui::restore();
result
ratatui::run(|terminal| App::default().run(terminal))
}
#[derive(Default)]
@@ -36,15 +33,15 @@ struct App {
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
while !self.should_quit {
self.draw(&mut terminal)?;
self.render(terminal)?;
self.handle_events()?;
}
Ok(())
}
fn draw(&mut self, tui: &mut DefaultTerminal) -> Result<()> {
fn render(&mut self, tui: &mut DefaultTerminal) -> Result<()> {
tui.draw(|frame| frame.render_widget(self, frame.area()))?;
Ok(())
}
@@ -71,7 +68,7 @@ impl App {
impl Widget for &mut App {
fn render(self, area: Rect, buf: &mut Buffer) {
let constraints = Constraint::from_lengths([1, 1, 2, 1]);
let [greeting, timer, squares, position] = Layout::vertical(constraints).areas(area);
let [greeting, timer, squares, position] = area.layout(&Layout::vertical(constraints));
// render an ephemeral greeting widget
Greeting::new("Ratatui!").render(greeting, buf);
@@ -177,9 +174,9 @@ struct BlueSquare;
impl Widget for &BoxedSquares {
fn render(self, area: Rect, buf: &mut Buffer) {
let constraints = vec![Constraint::Length(4); self.squares.len()];
let areas = Layout::horizontal(constraints).split(area);
for (widget, area) in self.squares.iter().zip(areas.iter()) {
widget.render_ref(*area, buf);
let areas = area.layout_vec(&Layout::horizontal(constraints));
for (widget, area) in self.squares.iter().zip(areas) {
widget.render_ref(area, buf);
}
}
}

View File

@@ -14,9 +14,9 @@ edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre = "0.6.3"
color-eyre = "0.6.5"
crossterm = { workspace = true, features = ["event-stream"] }
octocrab = "0.44.0"
ratatui.workspace = true
tokio = { version = "1.44.2", features = ["rt-multi-thread", "macros"] }
tokio = { version = "1.46.1", features = ["rt-multi-thread", "macros"] }
tokio-stream = "0.1.17"

View File

@@ -78,8 +78,8 @@ impl App {
}
fn render(&self, frame: &mut Frame) {
let vertical = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]);
let [title_area, body_area] = vertical.areas(frame.area());
let layout = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]);
let [title_area, body_area] = frame.area().layout(&layout);
let title = Line::from("Ratatui async example").centered().bold();
frame.render_widget(title, title_area);
frame.render_widget(&self.pull_requests, body_area);

View File

@@ -22,21 +22,18 @@ use time::{Date, Month, OffsetDateTime};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
ratatui::run(run)
}
/// Run the application.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
fn run(terminal: &mut DefaultTerminal) -> Result<()> {
let mut selected_date = OffsetDateTime::now_local()?.date();
let mut calendar_style = StyledCalendar::Default;
loop {
terminal.draw(|frame| render(frame, calendar_style, selected_date))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
KeyCode::Char('q') => break Ok(()),
KeyCode::Char('s') => calendar_style = calendar_style.next(),
KeyCode::Char('n') | KeyCode::Tab => selected_date = next_month(selected_date),
KeyCode::Char('p') | KeyCode::BackTab => selected_date = prev_month(selected_date),
@@ -84,11 +81,10 @@ fn render(frame: &mut Frame, calendar_style: StyledCalendar, selected_date: Date
)),
]);
let vertical = Layout::vertical([
let [text_area, area] = frame.area().layout(&Layout::vertical([
Constraint::Length(header.height() as u16),
Constraint::Fill(1),
]);
let [text_area, area] = vertical.areas(frame.area());
]));
frame.render_widget(header.centered(), text_area);
calendar_style
.render_year(frame, area, selected_date)
@@ -136,16 +132,13 @@ impl StyledCalendar {
fn render_year(self, frame: &mut Frame, area: Rect, date: Date) -> Result<()> {
let events = events(date)?;
let area = area.inner(Margin {
vertical: 1,
horizontal: 1,
});
let rows = Layout::vertical([Constraint::Ratio(1, 3); 3]).split(area);
let areas = rows.iter().flat_map(|row| {
Layout::horizontal([Constraint::Ratio(1, 4); 4])
.split(*row)
.to_vec()
});
let vertical = Layout::vertical([Constraint::Ratio(1, 3); 3]);
let horizontal = &Layout::horizontal([Constraint::Ratio(1, 4); 4]);
let areas = area
.inner(Margin::new(1, 1))
.layout_vec(&vertical)
.into_iter()
.flat_map(|row| row.layout_vec(horizontal));
for (i, area) in areas.enumerate() {
let month = date
.replace_day(1)

View File

@@ -153,16 +153,15 @@ impl App {
let vertical = Layout::vertical([
Constraint::Length(header.height() as u16),
Constraint::Percentage(50),
Constraint::Percentage(50),
Constraint::Fill(1),
Constraint::Fill(1),
]);
let [text_area, up, down] = vertical.areas(frame.area());
let [text_area, up, down] = frame.area().layout(&vertical);
frame.render_widget(header.centered(), text_area);
let horizontal =
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]);
let [draw, pong] = horizontal.areas(up);
let [map, boxes] = horizontal.areas(down);
let horizontal = Layout::horizontal([Constraint::Fill(1); 2]);
let [draw, pong] = up.layout(&horizontal);
let [map, boxes] = down.layout(&horizontal);
frame.render_widget(self.map_canvas(), map);
frame.render_widget(self.draw_canvas(draw), draw);

View File

@@ -21,10 +21,7 @@ use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::new().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::new().run(terminal))
}
struct App {
@@ -78,7 +75,7 @@ impl App {
}
}
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
let tick_rate = Duration::from_millis(250);
let mut last_tick = Instant::now();
loop {
@@ -111,10 +108,11 @@ impl App {
}
fn render(&self, frame: &mut Frame) {
let [top, bottom] = Layout::vertical([Constraint::Fill(1); 2]).areas(frame.area());
let [animated_chart, bar_chart] =
Layout::horizontal([Constraint::Fill(1), Constraint::Length(29)]).areas(top);
let [line_chart, scatter] = Layout::horizontal([Constraint::Fill(1); 2]).areas(bottom);
let vertical = Layout::vertical([Constraint::Fill(1); 2]);
let [top, bottom] = frame.area().layout(&vertical);
let horizontal = Layout::horizontal([Constraint::Fill(1), Constraint::Length(29)]);
let [animated_chart, bar_chart] = top.layout(&horizontal);
let [line_chart, scatter] = bottom.layout(&Layout::horizontal([Constraint::Fill(1); 2]));
self.render_animated_chart(frame, animated_chart);
render_barchart(frame, bar_chart);

View File

@@ -11,40 +11,35 @@
use color_eyre::Result;
use crossterm::event;
use itertools::Itertools;
use ratatui::Frame;
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::{Color, Style, Stylize};
use ratatui::text::Line;
use ratatui::widgets::{Block, Borders, Paragraph};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = run(terminal);
ratatui::restore();
app_result
}
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
ratatui::run(|terminal| {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
}
}
}
})
}
fn render(frame: &mut Frame) {
let layout = Layout::vertical([
let [named, indexed_colors, indexed_greys] = Layout::vertical([
Constraint::Length(30),
Constraint::Length(17),
Constraint::Length(2),
])
.split(frame.area());
.areas(frame.area());
render_named_colors(frame, layout[0]);
render_indexed_colors(frame, layout[1]);
render_indexed_grayscale(frame, layout[2]);
render_named_colors(frame, named);
render_indexed_colors(frame, indexed_colors);
render_indexed_grayscale(frame, indexed_greys);
}
const NAMED_COLORS: [Color; 16] = [
@@ -87,12 +82,12 @@ fn render_fg_named_colors(frame: &mut Frame, bg: Color, area: Rect) {
let inner = block.inner(area);
frame.render_widget(block, area);
let vertical = Layout::vertical([Constraint::Length(1); 2]).split(inner);
let areas = vertical.iter().flat_map(|area| {
Layout::horizontal([Constraint::Ratio(1, 8); 8])
.split(*area)
.to_vec()
});
let vertical = Layout::vertical([Constraint::Length(1); 2]);
let horizontal = Layout::horizontal([Constraint::Ratio(1, 8); 8]);
let areas = inner
.layout_vec(&vertical)
.into_iter()
.flat_map(|area| area.layout_vec(&horizontal));
for (fg, area) in NAMED_COLORS.into_iter().zip(areas) {
let color_name = fg.to_string();
let paragraph = Paragraph::new(color_name).fg(fg).bg(bg);
@@ -105,12 +100,12 @@ fn render_bg_named_colors(frame: &mut Frame, fg: Color, area: Rect) {
let inner = block.inner(area);
frame.render_widget(block, area);
let vertical = Layout::vertical([Constraint::Length(1); 2]).split(inner);
let areas = vertical.iter().flat_map(|area| {
Layout::horizontal([Constraint::Ratio(1, 8); 8])
.split(*area)
.to_vec()
});
let vertical = Layout::vertical([Constraint::Length(1); 2]);
let horizontal = Layout::horizontal([Constraint::Ratio(1, 8); 8]);
let areas = inner
.layout_vec(&vertical)
.into_iter()
.flat_map(|area| area.layout_vec(&horizontal));
for (bg, area) in NAMED_COLORS.into_iter().zip(areas) {
let color_name = bg.to_string();
let paragraph = Paragraph::new(color_name).fg(fg).bg(bg);

View File

@@ -31,10 +31,7 @@ use ratatui::widgets::Widget;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::default().run(terminal))
}
#[derive(Debug, Default)]
@@ -91,7 +88,7 @@ impl App {
/// Run the app
///
/// This is the main event loop for the app.
pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
pub fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
while self.is_running() {
terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?;
self.handle_events()?;
@@ -127,8 +124,8 @@ impl App {
impl Widget for &mut App {
fn render(self, area: Rect, buf: &mut Buffer) {
use Constraint::{Length, Min};
let [top, colors] = Layout::vertical([Length(1), Min(0)]).areas(area);
let [title, fps] = Layout::horizontal([Min(0), Length(8)]).areas(top);
let [top, colors] = area.layout(&Layout::vertical([Length(1), Min(0)]));
let [title, fps] = top.layout(&Layout::horizontal([Min(0), Length(8)]));
Text::from("colors_rgb example. Press q to quit")
.centered()
.render(title, buf);

View File

@@ -24,10 +24,7 @@ use strum::{Display, EnumIter, FromRepr};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::default().run(terminal))
}
#[derive(Default)]
@@ -82,7 +79,7 @@ struct SpacerBlock;
// App behaviour
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
self.insert_test_defaults();
while self.is_running() {
@@ -245,14 +242,13 @@ impl Widget for &App {
swap_legend_area,
_,
blocks_area,
] = Layout::vertical([
] = area.layout(&Layout::vertical([
Length(2), // header
Length(2), // instructions
Length(1), // swap key legend
Length(1), // gap
Fill(1), // blocks
])
.areas(area);
]));
App::header().render(header_area, buf);
App::instructions().render(instructions_area, buf);
@@ -322,20 +318,26 @@ impl App {
}
fn render_layout_blocks(&self, area: Rect, buf: &mut Buffer) {
let [user_constraints, area] = Layout::vertical([Length(3), Fill(1)])
.spacing(1)
.areas(area);
let main_layout = Layout::vertical([Length(3), Fill(1)]).spacing(1);
let [user_constraints, area] = area.layout(&main_layout);
self.render_user_constraints_legend(user_constraints, buf);
let [start, center, end, space_around, space_between] =
Layout::vertical([Length(7); 5]).areas(area);
let [
start,
center,
end,
space_between,
space_around,
space_evenly,
] = area.layout(&Layout::vertical([Length(7); 6]));
self.render_layout_block(Flex::Start, start, buf);
self.render_layout_block(Flex::Center, center, buf);
self.render_layout_block(Flex::End, end, buf);
self.render_layout_block(Flex::SpaceAround, space_around, buf);
self.render_layout_block(Flex::SpaceBetween, space_between, buf);
self.render_layout_block(Flex::SpaceAround, space_around, buf);
self.render_layout_block(Flex::SpaceEvenly, space_evenly, buf);
}
fn render_user_constraints_legend(&self, area: Rect, buf: &mut Buffer) {
@@ -349,8 +351,8 @@ impl App {
}
fn render_layout_block(&self, flex: Flex, area: Rect, buf: &mut Buffer) {
let [label_area, axis_area, blocks_area] =
Layout::vertical([Length(1), Max(1), Length(4)]).areas(area);
let layout = Layout::vertical([Length(1), Max(1), Length(4)]);
let [label_area, axis_area, blocks_area] = area.layout(&layout);
if label_area.height > 0 {
format!("Flex::{flex:?}").bold().render(label_area, buf);

View File

@@ -36,10 +36,7 @@ const FILL_COLOR: Color = tailwind::SLATE.c950;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::default().run(terminal))
}
#[derive(Default, Clone, Copy)]
@@ -72,7 +69,7 @@ enum AppState {
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
self.update_max_scroll_offset();
while self.is_running() {
terminal.draw(|frame| frame.render_widget(self, frame.area()))?;
@@ -143,7 +140,7 @@ impl App {
impl Widget for App {
fn render(self, area: Rect, buf: &mut Buffer) {
let [tabs, axis, demo] = Layout::vertical([Length(3), Length(3), Fill(0)]).areas(area);
let [tabs, axis, demo] = area.layout(&Layout::vertical([Length(3), Length(3), Fill(0)]));
self.render_tabs(tabs, buf);
Self::render_axis(axis, buf);
@@ -284,8 +281,8 @@ impl Widget for SelectedTab {
impl SelectedTab {
fn render_length_example(area: Rect, buf: &mut Buffer) {
let [example1, example2, example3, _] =
Layout::vertical([Length(EXAMPLE_HEIGHT); 4]).areas(area);
let layout = Layout::vertical([Length(EXAMPLE_HEIGHT); 4]);
let [example1, example2, example3, _] = area.layout(&layout);
Example::new(&[Length(20), Length(20)]).render(example1, buf);
Example::new(&[Length(20), Min(20)]).render(example2, buf);
@@ -293,8 +290,8 @@ impl SelectedTab {
}
fn render_percentage_example(area: Rect, buf: &mut Buffer) {
let [example1, example2, example3, example4, example5, _] =
Layout::vertical([Length(EXAMPLE_HEIGHT); 6]).areas(area);
let layout = Layout::vertical([Length(EXAMPLE_HEIGHT); 6]);
let [example1, example2, example3, example4, example5, _] = area.layout(&layout);
Example::new(&[Percentage(75), Fill(0)]).render(example1, buf);
Example::new(&[Percentage(25), Fill(0)]).render(example2, buf);
@@ -304,8 +301,8 @@ impl SelectedTab {
}
fn render_ratio_example(area: Rect, buf: &mut Buffer) {
let [example1, example2, example3, example4, _] =
Layout::vertical([Length(EXAMPLE_HEIGHT); 5]).areas(area);
let layout = Layout::vertical([Length(EXAMPLE_HEIGHT); 5]);
let [example1, example2, example3, example4, _] = area.layout(&layout);
Example::new(&[Ratio(1, 2); 2]).render(example1, buf);
Example::new(&[Ratio(1, 4); 4]).render(example2, buf);
@@ -314,15 +311,15 @@ impl SelectedTab {
}
fn render_fill_example(area: Rect, buf: &mut Buffer) {
let [example1, example2, _] = Layout::vertical([Length(EXAMPLE_HEIGHT); 3]).areas(area);
let [example1, example2, _] = area.layout(&Layout::vertical([Length(EXAMPLE_HEIGHT); 3]));
Example::new(&[Fill(1), Fill(2), Fill(3)]).render(example1, buf);
Example::new(&[Fill(1), Percentage(50), Fill(1)]).render(example2, buf);
}
fn render_min_example(area: Rect, buf: &mut Buffer) {
let [example1, example2, example3, example4, example5, _] =
Layout::vertical([Length(EXAMPLE_HEIGHT); 6]).areas(area);
let layout = Layout::vertical([Length(EXAMPLE_HEIGHT); 6]);
let [example1, example2, example3, example4, example5, _] = area.layout(&layout);
Example::new(&[Percentage(100), Min(0)]).render(example1, buf);
Example::new(&[Percentage(100), Min(20)]).render(example2, buf);
@@ -332,8 +329,8 @@ impl SelectedTab {
}
fn render_max_example(area: Rect, buf: &mut Buffer) {
let [example1, example2, example3, example4, example5, _] =
Layout::vertical([Length(EXAMPLE_HEIGHT); 6]).areas(area);
let layout = Layout::vertical([Length(EXAMPLE_HEIGHT); 6]);
let [example1, example2, example3, example4, example5, _] = area.layout(&layout);
Example::new(&[Percentage(0), Max(0)]).render(example1, buf);
Example::new(&[Percentage(0), Max(20)]).render(example2, buf);
@@ -357,9 +354,10 @@ impl Example {
impl Widget for Example {
fn render(self, area: Rect, buf: &mut Buffer) {
let [area, _] =
Layout::vertical([Length(ILLUSTRATION_HEIGHT), Length(SPACER_HEIGHT)]).areas(area);
let blocks = Layout::horizontal(&self.constraints).split(area);
let vertical = Layout::vertical([Length(ILLUSTRATION_HEIGHT), Length(SPACER_HEIGHT)]);
let horizontal = Layout::horizontal(&self.constraints);
let [area, _] = area.layout(&vertical);
let blocks = area.layout_vec(&horizontal);
for (block, constraint) in blocks.iter().zip(&self.constraints) {
Self::illustration(*constraint, block.width).render(*block, buf);

View File

@@ -15,7 +15,7 @@ use crossterm::event::{
};
use crossterm::execute;
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::layout::{Constraint, Flex, Layout, Rect};
use ratatui::style::{Color, Style};
use ratatui::text::Line;
use ratatui::widgets::{Paragraph, Widget};
@@ -167,13 +167,13 @@ fn run(mut terminal: DefaultTerminal) -> Result<()> {
}
fn render(frame: &mut Frame, states: [State; 3]) {
let vertical = Layout::vertical([
let layout = Layout::vertical([
Constraint::Length(1),
Constraint::Max(3),
Constraint::Length(1),
Constraint::Min(0), // ignore remaining space
]);
let [title, buttons, help, _] = vertical.areas(frame.area());
let [title, buttons, help, _] = frame.area().layout(&layout);
frame.render_widget(
Paragraph::new("Custom Widget Example (mouse enabled)"),
@@ -184,13 +184,8 @@ fn render(frame: &mut Frame, states: [State; 3]) {
}
fn render_buttons(frame: &mut Frame<'_>, area: Rect, states: [State; 3]) {
let horizontal = Layout::horizontal([
Constraint::Length(15),
Constraint::Length(15),
Constraint::Length(15),
Constraint::Min(0), // ignore remaining space
]);
let [red, green, blue, _] = horizontal.areas(area);
let layout = Layout::horizontal([Constraint::Length(15); 3]).flex(Flex::Start);
let [red, green, blue] = area.layout(&layout);
frame.render_widget(Button::new("Red").theme(RED).state(states[0]), red);
frame.render_widget(Button::new("Green").theme(GREEN).state(states[1]), green);

View File

@@ -12,9 +12,9 @@ termion = ["ratatui/termion", "dep:termion"]
termwiz = ["ratatui/termwiz", "dep:termwiz"]
[dependencies]
clap = { version = "4.5.37", features = ["derive"] }
clap = { version = "4.5.41", features = ["derive"] }
crossterm = { workspace = true, optional = true }
rand = "0.9.1"
rand = "0.9.2"
ratatui.workspace = true
termwiz = { workspace = true, optional = true }

View File

@@ -51,7 +51,7 @@ where
{
let mut last_tick = Instant::now();
loop {
terminal.draw(|frame| ui::draw(frame, &mut app))?;
terminal.draw(|frame| ui::render(frame, &mut app))?;
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
if !event::poll(timeout)? {

View File

@@ -42,7 +42,7 @@ where
{
let events = events(tick_rate);
loop {
terminal.draw(|frame| ui::draw(frame, &mut app))?;
terminal.draw(|frame| ui::render(frame, &mut app))?;
match events.recv()? {
Event::Input(key) => match key {

View File

@@ -36,7 +36,7 @@ fn run_app(
) -> Result<(), Box<dyn Error>> {
let mut last_tick = Instant::now();
loop {
terminal.draw(|frame| ui::draw(frame, &mut app))?;
terminal.draw(|frame| ui::render(frame, &mut app))?;
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
if let Some(input) = terminal

View File

@@ -10,7 +10,7 @@ use ratatui::{Frame, symbols};
use crate::app::App;
pub fn draw(frame: &mut Frame, app: &mut App) {
pub fn render(frame: &mut Frame, app: &mut App) {
let chunks = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]).split(frame.area());
let tabs = app
.tabs

View File

@@ -6,12 +6,12 @@ edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre = "0.6.3"
color-eyre = "0.6.5"
crossterm.workspace = true
indoc.workspace = true
itertools.workspace = true
palette = "0.7.6"
rand = "0.9.1"
rand = "0.9.2"
rand_chacha = "0.9.0"
ratatui = { workspace = true, features = ["all-widgets"] }
strum.workspace = true

View File

@@ -129,12 +129,12 @@ impl App {
/// matter, but for larger apps this can be a significant performance improvement.
impl Widget for &App {
fn render(self, area: Rect, buf: &mut Buffer) {
let vertical = Layout::vertical([
let layout = Layout::vertical([
Constraint::Length(1),
Constraint::Min(0),
Constraint::Length(1),
]);
let [title_bar, tab, bottom_bar] = vertical.areas(area);
let [title_bar, tab, bottom_bar] = area.layout(&layout);
Block::new().style(THEME.root).render(area, buf);
self.render_title_bar(title_bar, buf);
@@ -146,7 +146,7 @@ impl Widget for &App {
impl App {
fn render_title_bar(&self, area: Rect, buf: &mut Buffer) {
let layout = Layout::horizontal([Constraint::Min(0), Constraint::Length(43)]);
let [title, tabs] = layout.areas(area);
let [title, tabs] = area.layout(&layout);
Span::styled("Ratatui", THEME.app_title).render(title, buf);
let titles = Tab::iter().map(Tab::title);

View File

@@ -134,7 +134,7 @@ fn blend(mask_color: Color, cell_color: Color, percentage: f64) -> Color {
fn centered_rect(area: Rect, width: u16, height: u16) -> Rect {
let horizontal = Layout::horizontal([width]).flex(Flex::Center);
let vertical = Layout::vertical([height]).flex(Flex::Center);
let [area] = vertical.areas(area);
let [area] = horizontal.areas(area);
let [area] = area.layout(&vertical);
let [area] = area.layout(&horizontal);
area
}

View File

@@ -24,8 +24,8 @@ impl AboutTab {
impl Widget for AboutTab {
fn render(self, area: Rect, buf: &mut Buffer) {
RgbSwatch.render(area, buf);
let horizontal = Layout::horizontal([Constraint::Length(34), Constraint::Min(0)]);
let [logo_area, description] = horizontal.areas(area);
let layout = Layout::horizontal([Constraint::Length(34), Constraint::Min(0)]);
let [logo_area, description] = area.layout(&layout);
render_crate_description(description, buf);
let eye_state = if self.row_index % 2 == 0 {
MascotEyeColor::Default

View File

@@ -71,15 +71,15 @@ impl Widget for EmailTab {
horizontal: 2,
});
Clear.render(area, buf);
let vertical = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
let [inbox, email] = vertical.areas(area);
let layout = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
let [inbox, email] = area.layout(&layout);
render_inbox(self.row_index, inbox, buf);
render_email(self.row_index, email, buf);
}
}
fn render_inbox(selected_index: usize, area: Rect, buf: &mut Buffer) {
let vertical = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
let [tabs, inbox] = vertical.areas(area);
let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
let [tabs, inbox] = area.layout(&layout);
let theme = THEME.email;
Tabs::new(vec![" Inbox ", " Sent ", " Drafts "])
.style(theme.tabs)
@@ -130,8 +130,8 @@ fn render_email(selected_index: usize, area: Rect, buf: &mut Buffer) {
let inner = block.inner(area);
block.render(area, buf);
if let Some(email) = email {
let vertical = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]);
let [headers_area, body_area] = vertical.areas(inner);
let layout = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]);
let [headers_area, body_area] = inner.layout(&layout);
let headers = vec![
Line::from(vec![
"From: ".set_style(theme.header),

View File

@@ -135,8 +135,8 @@ impl Widget for RecipeTab {
horizontal: 2,
vertical: 1,
});
let [recipe, ingredients] =
Layout::horizontal([Constraint::Length(44), Constraint::Min(0)]).areas(area);
let layout = Layout::horizontal([Constraint::Length(44), Constraint::Min(0)]);
let [recipe, ingredients] = area.layout(&layout);
render_recipe(recipe, buf);
render_ingredients(self.row_index, ingredients, buf);

View File

@@ -39,8 +39,8 @@ impl Widget for TracerouteTab {
Block::new().style(THEME.content).render(area, buf);
let horizontal = Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
let vertical = Layout::vertical([Constraint::Min(0), Constraint::Length(3)]);
let [left, map] = horizontal.areas(area);
let [hops, pings] = vertical.areas(left);
let [left, map] = area.layout(&horizontal);
let [hops, pings] = left.layout(&vertical);
render_hops(self.row_index, hops, buf);
render_ping(self.row_index, pings, buf);

View File

@@ -41,16 +41,16 @@ impl Widget for WeatherTab {
horizontal: 2,
vertical: 1,
});
let [main, _, gauges] = Layout::vertical([
let tab_layout = Layout::vertical([
Constraint::Min(0),
Constraint::Length(1),
Constraint::Length(1),
])
.areas(area);
let [calendar, charts] =
Layout::horizontal([Constraint::Length(23), Constraint::Min(0)]).areas(main);
let [simple, horizontal] =
Layout::vertical([Constraint::Length(29), Constraint::Min(0)]).areas(charts);
]);
let [main, _, gauges] = area.layout(&tab_layout);
let main_layout = Layout::horizontal([Constraint::Length(23), Constraint::Min(0)]);
let [calendar, charts] = main.layout(&main_layout);
let charts_layout = Layout::vertical([Constraint::Length(29), Constraint::Min(0)]);
let [simple, horizontal] = charts.layout(&charts_layout);
render_calendar(calendar, buf);
render_simple_barchart(simple, buf);

View File

@@ -27,10 +27,7 @@ use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::default().run(terminal))
}
const EXAMPLE_DATA: &[(&str, &[Constraint])] = &[
@@ -156,11 +153,12 @@ enum SelectedTab {
Center,
End,
SpaceAround,
SpaceEvenly,
SpaceBetween,
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
// increase the layout cache to account for the number of layout events. This ensures that
// layout is not generally reprocessed on every frame (which would lead to possible janky
// results when there are more than one possible solution to the requested layout). This
@@ -258,7 +256,7 @@ fn example_height() -> u16 {
impl Widget for App {
fn render(self, area: Rect, buf: &mut Buffer) {
let layout = Layout::vertical([Length(3), Length(1), Fill(0)]);
let [tabs, axis, demo] = layout.areas(area);
let [tabs, axis, demo] = area.layout(&layout);
self.tabs().render(tabs, buf);
let scroll_needed = self.render_demo(demo, buf);
let axis_width = if scroll_needed {
@@ -372,8 +370,9 @@ impl SelectedTab {
Self::Start => SKY.c400,
Self::Center => SKY.c300,
Self::End => SKY.c200,
Self::SpaceAround => INDIGO.c400,
Self::SpaceEvenly => INDIGO.c400,
Self::SpaceBetween => INDIGO.c300,
Self::SpaceAround => INDIGO.c500,
};
format!(" {text} ").fg(color).bg(Color::Black).into()
}
@@ -388,8 +387,9 @@ impl StatefulWidget for SelectedTab {
Self::Start => Self::render_examples(area, buf, Flex::Start, spacing),
Self::Center => Self::render_examples(area, buf, Flex::Center, spacing),
Self::End => Self::render_examples(area, buf, Flex::End, spacing),
Self::SpaceAround => Self::render_examples(area, buf, Flex::SpaceAround, spacing),
Self::SpaceEvenly => Self::render_examples(area, buf, Flex::SpaceEvenly, spacing),
Self::SpaceBetween => Self::render_examples(area, buf, Flex::SpaceBetween, spacing),
Self::SpaceAround => Self::render_examples(area, buf, Flex::SpaceAround, spacing),
}
}
}
@@ -421,7 +421,7 @@ impl Widget for Example {
fn render(self, area: Rect, buf: &mut Buffer) {
let title_height = get_description_height(&self.description);
let layout = Layout::vertical([Length(title_height), Fill(0)]);
let [title, illustrations] = layout.areas(area);
let [title, illustrations] = area.layout(&layout);
let (blocks, spacers) = Layout::horizontal(&self.constraints)
.flex(self.flex)

View File

@@ -43,14 +43,11 @@ enum AppState {
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::default().run(terminal))
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
while self.state != AppState::Quitting {
terminal.draw(|frame| frame.render_widget(&self, frame.area()))?;
self.handle_events()?;
@@ -106,10 +103,10 @@ impl Widget for &App {
fn render(self, area: Rect, buf: &mut Buffer) {
use Constraint::{Length, Min, Ratio};
let layout = Layout::vertical([Length(2), Min(0), Length(1)]);
let [header_area, gauge_area, footer_area] = layout.areas(area);
let [header_area, gauge_area, footer_area] = area.layout(&layout);
let layout = Layout::vertical([Ratio(1, 4); 4]);
let [gauge1_area, gauge2_area, gauge3_area, gauge4_area] = layout.areas(gauge_area);
let [gauge1_area, gauge2_area, gauge3_area, gauge4_area] = gauge_area.layout(&layout);
render_header(header_area, buf);
render_footer(footer_area, buf);
@@ -185,7 +182,7 @@ impl App {
}
}
fn title_block(title: &str) -> Block {
fn title_block(title: &str) -> Block<'_> {
let title = Line::from(title).centered();
Block::new()
.borders(Borders::NONE)

View File

@@ -21,17 +21,14 @@ use ratatui::{DefaultTerminal, Frame};
/// and exits when the user presses 'q'.
fn main() -> Result<()> {
color_eyre::install()?; // augment errors / panics with easy to read messages
let terminal = ratatui::init();
let app_result = run(terminal).context("app loop failed");
ratatui::restore();
app_result
ratatui::run(run).context("failed to run app")
}
/// Run the application loop. This is where you would handle events and update the application
/// state. This example exits when the user presses 'q'. Other styles of application loops are
/// possible, for example, you could have multiple application states and switch between them based
/// on events, or you could have a single application state and update it based on events.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
fn run(terminal: &mut DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if should_quit()? {

View File

@@ -7,9 +7,8 @@
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
/// [OSC 8]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
use color_eyre::Result;
use crossterm::event::{self, KeyCode};
use crossterm::event;
use itertools::Itertools;
use ratatui::DefaultTerminal;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::Stylize;
@@ -18,35 +17,18 @@ use ratatui::widgets::Widget;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::new().run(terminal);
ratatui::restore();
app_result
}
struct App {
hyperlink: Hyperlink<'static>,
}
let text = Line::from(vec!["Example ".into(), "hyperlink".blue()]);
let hyperlink = Hyperlink::new(text, "https://example.com");
impl App {
fn new() -> Self {
let text = Line::from(vec!["Example ".into(), "hyperlink".blue()]);
let hyperlink = Hyperlink::new(text, "https://example.com");
Self { hyperlink }
}
fn run(self, mut terminal: DefaultTerminal) -> Result<()> {
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| frame.render_widget(&self.hyperlink, frame.area()))?;
if event::read()?
.as_key_press_event()
.is_some_and(|key| matches!(key.code, KeyCode::Char('q') | KeyCode::Esc))
{
break;
terminal.draw(|frame| frame.render_widget(&hyperlink, frame.area()))?;
if event::read()?.is_key_press() {
break Ok(());
}
}
Ok(())
}
})
}
/// A hyperlink widget that renders a hyperlink in the terminal using [OSC 8].

View File

@@ -8,7 +8,7 @@ rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
rand = "0.9.1"
rand = "0.9.2"
ratatui.workspace = true
[lints]

View File

@@ -230,8 +230,8 @@ fn render(frame: &mut Frame, downloads: &Downloads) {
let vertical = Layout::vertical([Constraint::Length(2), Constraint::Length(4)]).margin(1);
let horizontal = Layout::horizontal([Constraint::Percentage(20), Constraint::Percentage(80)]);
let [progress_area, main] = vertical.areas(area);
let [list_area, gauge_area] = horizontal.areas(main);
let [progress_area, main] = area.layout(&vertical);
let [list_area, gauge_area] = main.layout(&horizontal);
// total progress
let done = NUM_DOWNLOADS - downloads.pending.len() - downloads.in_progress.len();

View File

@@ -23,5 +23,5 @@ Some more ideas for handling focus can be found in:
- [`focusable`](https://crates.io/crates/focusable) (see also [Ratatui forum
post](https://forum.ratatui.rs/t/focusable-crate-manage-focus-state-for-your-widgets/73))
- [`rat-focus`](https://crates.io/crates/rat-focus)
- A useful [`Bevy` discssion](https://github.com/bevyengine/bevy/discussions/15374) about focus
- A useful [`Bevy` discussion](https://github.com/bevyengine/bevy/discussions/15374) about focus
more generally.

View File

@@ -26,12 +26,8 @@ use serde::Serialize;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = App::default().run(terminal);
ratatui::restore();
// serialize the form to JSON if the user submitted it, otherwise print "Canceled"
match result {
match ratatui::run(|terminal| App::default().run(terminal)) {
Ok(Some(form)) => println!("{}", serde_json::to_string_pretty(&form)?),
Ok(None) => println!("Canceled"),
Err(err) => eprintln!("{err}"),
@@ -54,7 +50,7 @@ enum AppState {
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<Option<InputForm>> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<Option<InputForm>> {
while self.state == AppState::Running {
terminal.draw(|frame| self.render(frame))?;
self.handle_events()?;
@@ -119,8 +115,8 @@ impl InputForm {
///
/// The cursor is placed at the end of the focused field.
fn render(&self, frame: &mut Frame) {
let [first_name_area, last_name_area, age_area] =
Layout::vertical(Constraint::from_lengths([1, 1, 1])).areas(frame.area());
let layout = Layout::vertical(Constraint::from_lengths([1, 1, 1]));
let [first_name_area, last_name_area, age_area] = frame.area().layout(&layout);
frame.render_widget(&self.first_name, first_name_area);
frame.render_widget(&self.last_name, last_name_area);
@@ -189,11 +185,11 @@ impl StringField {
impl Widget for &StringField {
fn render(self, area: Rect, buf: &mut Buffer) {
let constraints = [
let layout = Layout::horizontal([
Constraint::Length(self.label.len() as u16 + 2),
Constraint::Fill(1),
];
let [label_area, value_area] = Layout::horizontal(constraints).areas(area);
]);
let [label_area, value_area] = area.layout(&layout);
let label = Line::from_iter([self.label, ": "]).bold();
label.render(label_area, buf);
self.value.clone().render(value_area, buf);
@@ -254,11 +250,11 @@ impl AgeField {
impl Widget for &AgeField {
fn render(self, area: Rect, buf: &mut Buffer) {
let constraints = [
let layout = Layout::horizontal([
Constraint::Length(self.label.len() as u16 + 2),
Constraint::Fill(1),
];
let [label_area, value_area] = Layout::horizontal(constraints).areas(area);
]);
let [label_area, value_area] = area.layout(&layout);
let label = Line::from_iter([self.label, ": "]).bold();
let value = self.value.to_string();
label.render(label_area, buf);

View File

@@ -1,28 +1,23 @@
/// A minimal example of a Ratatui application.
///
/// This is a bare minimum example. There are many approaches to running an application loop,
/// so this is not meant to be prescriptive. See the [examples] folder for more complete
/// examples. In particular, the [hello-world] example is a good starting point.
///
/// This example runs with the Ratatui library code in the branch that you are currently
/// reading. See the [`latest`] branch for the code which works with the most recent Ratatui
/// release.
///
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
/// [examples]: https://github.com/ratatui/ratatui/blob/main/examples
/// [hello-world]: https://github.com/ratatui/ratatui/blob/main/examples/apps/hello-world
use crossterm::event;
use ratatui::text::Text;
fn main() {
let mut terminal = ratatui::init();
loop {
terminal
.draw(|frame| frame.render_widget(Text::raw("Hello World!"), frame.area()))
.expect("failed to draw frame");
if event::read().expect("failed to read event").is_key_press() {
break;
//! A minimal example of a Ratatui application.
//!
//! This is a bare minimum example. There are many approaches to running an application loop,
//! so this is not meant to be prescriptive. See the [examples] folder for more complete
//! examples. In particular, the [hello-world] example is a good starting point.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
//! [hello-world]: https://github.com/ratatui/ratatui/blob/main/examples/apps/hello-world
fn main() -> Result<(), Box<dyn std::error::Error>> {
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| frame.render_widget("Hello World!", frame.area()))?;
if crossterm::event::read()?.is_key_press() {
break Ok(());
}
}
}
ratatui::restore();
})
}

View File

@@ -12,34 +12,29 @@ use std::{error::Error, iter::once, result};
use crossterm::event;
use itertools::Itertools;
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout};
use ratatui::style::{Color, Modifier, Style, Stylize};
use ratatui::text::Line;
use ratatui::widgets::Paragraph;
use ratatui::{DefaultTerminal, Frame};
type Result<T> = result::Result<T, Box<dyn Error>>;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = run(terminal);
ratatui::restore();
app_result
}
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
ratatui::run(|terminal| {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
break Ok(());
}
}
}
})
}
fn render(frame: &mut Frame) {
let vertical = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
let [text_area, main_area] = vertical.areas(frame.area());
let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
let [text_area, main_area] = frame.area().layout(&layout);
frame.render_widget(
Paragraph::new("Note: not all terminals support all modifiers")
.style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)),

View File

@@ -10,7 +10,7 @@ color-eyre.workspace = true
crossterm.workspace = true
## a collection of line drawing algorithms (e.g. Bresenham's line algorithm)
line_drawing = "1.0.1"
rand = "0.9.1"
rand = "0.9.2"
ratatui.workspace = true
[lints]

View File

@@ -21,10 +21,7 @@ use ratatui::{DefaultTerminal, Frame, symbols};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = MouseDrawingApp::default().run(terminal);
ratatui::restore();
result
ratatui::run(|terminal| MouseDrawingApp::default().run(terminal))
}
#[derive(Default)]
@@ -40,7 +37,7 @@ struct MouseDrawingApp {
}
impl MouseDrawingApp {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
execute!(std::io::stdout(), EnableMouseCapture)?;
while !self.should_exit {
terminal.draw(|frame| self.render(frame))?;

View File

@@ -31,69 +31,58 @@
/// [Color Eyre recipe]: https://ratatui.rs/recipes/apps/color-eyre
use color_eyre::{Result, eyre::bail};
use crossterm::event::{self, KeyCode};
use ratatui::Frame;
use ratatui::text::Line;
use ratatui::widgets::{Block, Paragraph};
use ratatui::{DefaultTerminal, Frame};
#[derive(Debug)]
enum PanicHandlerState {
Enabled,
Disabled,
}
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::new().run(terminal);
ratatui::restore();
app_result
}
struct App {
hook_enabled: bool,
}
impl App {
const fn new() -> Self {
Self { hook_enabled: true }
}
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
let mut panic_hook_state = PanicHandlerState::Enabled;
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| self.render(frame))?;
terminal.draw(|frame| render(frame, &panic_hook_state))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('p') => panic!("intentional demo panic"),
KeyCode::Char('e') => bail!("intentional demo error"),
KeyCode::Char('h') => {
let _ = std::panic::take_hook();
self.hook_enabled = false;
panic_hook_state = PanicHandlerState::Disabled;
}
KeyCode::Char('q') => return Ok(()),
_ => {}
}
}
}
}
fn render(&self, frame: &mut Frame) {
let text = vec![
if self.hook_enabled {
Line::from("HOOK IS CURRENTLY **ENABLED**")
} else {
Line::from("HOOK IS CURRENTLY **DISABLED**")
},
Line::from(""),
Line::from("Press `p` to cause a panic"),
Line::from("Press `e` to cause an error"),
Line::from("Press `h` to disable the panic hook"),
Line::from("Press `q` to quit"),
Line::from(""),
Line::from("When your app panics without a panic hook, you will likely have to"),
Line::from("reset your terminal afterwards with the `reset` command"),
Line::from(""),
Line::from("Try first with the panic handler enabled, and then with it disabled"),
Line::from("to see the difference"),
];
let paragraph = Paragraph::new(text)
.block(Block::bordered().title("Panic Handler Demo"))
.centered();
frame.render_widget(paragraph, frame.area());
}
})
}
fn render(frame: &mut Frame, state: &PanicHandlerState) {
let text = vec![
Line::from(format!("Panic hook is currently: {state:?}")),
Line::from(""),
Line::from("Press `p` to cause a panic"),
Line::from("Press `e` to cause an error"),
Line::from("Press `h` to disable the panic hook"),
Line::from("Press `q` to quit"),
Line::from(""),
Line::from("When your app panics without a panic hook, you will likely have to"),
Line::from("reset your terminal afterwards with the `reset` command"),
Line::from(""),
Line::from("Try first with the panic handler enabled, and then with it disabled"),
Line::from("to see the difference"),
];
let paragraph = Paragraph::new(text)
.block(Block::bordered().title("Panic Handler Demo"))
.centered();
frame.render_widget(paragraph, frame.area());
}

View File

@@ -1,80 +1,70 @@
/// A Ratatui example that demonstrates how to handle popups.
// See also https://github.com/joshka/tui-popup and
// https://github.com/sephiroth74/tui-confirm-dialog
///
/// This example runs with the Ratatui library code in the branch that you are currently
/// reading. See the [`latest`] branch for the code which works with the most recent Ratatui
/// release.
///
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//! A Ratatui example that demonstrates how to handle popups.
//! See also:
//! - <https://github.com/joshka/tui-popup> and
//! - <https://github.com/sephiroth74/tui-confirm-dialog>
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
use color_eyre::Result;
use crossterm::event::{self, KeyCode};
use ratatui::Frame;
use ratatui::layout::{Constraint, Flex, Layout, Rect};
use ratatui::style::Stylize;
use ratatui::widgets::{Block, Clear, Paragraph, Wrap};
use ratatui::{DefaultTerminal, Frame};
use ratatui::text::Line;
use ratatui::widgets::{Block, Clear};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
}
#[derive(Default)]
struct App {
show_popup: bool,
}
// This flag will be toggled when the user presses 'p'. This could be stored in an app struct
// if you have more state to manage than just this flag.
let mut show_popup = false;
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| self.render(frame))?;
terminal.draw(|frame| render(frame, show_popup))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('q') => return Ok(()),
KeyCode::Char('p') => self.show_popup = !self.show_popup,
KeyCode::Char('p') => show_popup = !show_popup,
_ => {}
}
}
}
}
})
}
fn render(&self, frame: &mut Frame) {
let area = frame.area();
fn render(frame: &mut Frame, show_popup: bool) {
let area = frame.area();
let vertical = Layout::vertical([Constraint::Percentage(20), Constraint::Percentage(80)]);
let [instructions, content] = vertical.areas(area);
let layout = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]);
let [instructions, content] = area.layout(&layout);
let text = if self.show_popup {
"Press p to close the popup"
} else {
"Press p to show the popup"
};
let paragraph = Paragraph::new(text.slow_blink())
.centered()
.wrap(Wrap { trim: true });
frame.render_widget(paragraph, instructions);
frame.render_widget(
Line::from("Press 'p' to toggle popup, 'q' to quit").centered(),
instructions,
);
let block = Block::bordered().title("Content").on_blue();
frame.render_widget(block, content);
frame.render_widget(Block::bordered().title("Content").on_blue(), content);
if self.show_popup {
let block = Block::bordered().title("Popup");
let area = popup_area(area, 60, 20);
frame.render_widget(Clear, area); //this clears out the background
frame.render_widget(block, area);
}
if show_popup {
let popup = Block::bordered().title("Popup");
let popup_area = centered_area(area, 60, 20);
// clears out any background in the area before rendering the popup
frame.render_widget(Clear, popup_area);
frame.render_widget(popup, popup_area);
}
}
/// helper function to create a centered rect using up certain percentage of the available rect `r`
fn popup_area(area: Rect, percent_x: u16, percent_y: u16) -> Rect {
/// Create a centered rect using up certain percentage of the available rect
fn centered_area(area: Rect, percent_x: u16, percent_y: u16) -> Rect {
let vertical = Layout::vertical([Constraint::Percentage(percent_y)]).flex(Flex::Center);
let horizontal = Layout::horizontal([Constraint::Percentage(percent_x)]).flex(Flex::Center);
let [area] = vertical.areas(area);
let [area] = horizontal.areas(area);
let [area] = area.layout(&vertical);
let [area] = area.layout(&horizontal);
area
}

View File

@@ -29,14 +29,11 @@ struct App {
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::default().run(terminal))
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
let tick_rate = Duration::from_millis(250);
let mut last_tick = Instant::now();
loop {

View File

@@ -34,10 +34,7 @@ const ITEM_HEIGHT: usize = 4;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::new().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::new().run(terminal))
}
struct TableColors {
buffer_bg: Color,
@@ -80,20 +77,14 @@ impl Data {
[&self.name, &self.address, &self.email]
}
// https://github.com/rust-lang/rust/issues/139338
#[allow(clippy::missing_const_for_fn)]
fn name(&self) -> &str {
&self.name
}
// https://github.com/rust-lang/rust/issues/139338
#[allow(clippy::missing_const_for_fn)]
fn address(&self) -> &str {
&self.address
}
// https://github.com/rust-lang/rust/issues/139338
#[allow(clippy::missing_const_for_fn)]
fn email(&self) -> &str {
&self.email
}
@@ -120,6 +111,7 @@ impl App {
items: data_vec,
}
}
pub fn next_row(&mut self) {
let i = match self.state.selected() {
Some(i) => {
@@ -171,7 +163,7 @@ impl App {
self.colors = TableColors::new(&PALETTES[self.color_index]);
}
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
loop {
terminal.draw(|frame| self.render(frame))?;
@@ -194,8 +186,8 @@ impl App {
}
fn render(&mut self, frame: &mut Frame) {
let vertical = &Layout::vertical([Constraint::Min(5), Constraint::Length(4)]);
let rects = vertical.split(frame.area());
let layout = Layout::vertical([Constraint::Min(5), Constraint::Length(4)]);
let rects = frame.area().layout_vec(&layout);
self.set_colors();

View File

@@ -27,10 +27,7 @@ const COMPLETED_TEXT_FG_COLOR: Color = GREEN.c500;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::default().run(terminal))
}
/// This struct holds the current state of the app. In particular, it has the `todo_list` field
@@ -124,7 +121,7 @@ impl TodoItem {
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
while !self.should_exit {
terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?;
if let Some(key) = event::read()?.as_key_press_event() {
@@ -181,15 +178,15 @@ impl App {
impl Widget for &mut App {
fn render(self, area: Rect, buf: &mut Buffer) {
let [header_area, main_area, footer_area] = Layout::vertical([
let main_layout = Layout::vertical([
Constraint::Length(2),
Constraint::Fill(1),
Constraint::Length(1),
])
.areas(area);
]);
let [header_area, content_area, footer_area] = area.layout(&main_layout);
let [list_area, item_area] =
Layout::vertical([Constraint::Fill(1), Constraint::Fill(1)]).areas(main_area);
let content_layout = Layout::vertical([Constraint::Fill(1), Constraint::Fill(1)]);
let [list_area, item_area] = content_area.layout(&content_layout);
App::render_header(header_area, buf);
App::render_footer(footer_area, buf);

View File

@@ -30,10 +30,7 @@ use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::new().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::new().run(terminal))
}
/// App holds the state of the application
@@ -127,7 +124,7 @@ impl App {
self.reset_cursor();
}
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
loop {
terminal.draw(|frame| self.render(frame))?;
@@ -158,12 +155,12 @@ impl App {
}
fn render(&self, frame: &mut Frame) {
let vertical = Layout::vertical([
let layout = Layout::vertical([
Constraint::Length(1),
Constraint::Length(3),
Constraint::Min(1),
]);
let [help_area, input_area, messages_area] = vertical.areas(frame.area());
let [help_area, input_area, messages_area] = frame.area().layout(&layout);
let (msg, style) = match self.input_mode {
InputMode::Normal => (

View File

@@ -8,7 +8,7 @@ rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
rand = "0.9.1"
rand = "0.9.2"
ratatui.workspace = true
[lints]

View File

@@ -9,67 +9,38 @@
//! [`BarChart`]: https://docs.rs/ratatui/latest/ratatui/widgets/struct.BarChart.html
use color_eyre::Result;
use crossterm::event::{self, KeyCode};
use crossterm::event;
use rand::{Rng, rng};
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout};
use ratatui::style::{Color, Style, Stylize};
use ratatui::text::Line;
use ratatui::widgets::{Bar, BarChart, BarGroup};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::new().run(terminal);
ratatui::restore();
app_result
let temperatures: Vec<u8> = (0..24).map(|_| rng().random_range(50..90)).collect();
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| render(frame, &temperatures))?;
if event::read()?.is_key_press() {
break Ok(());
}
}
})
}
struct App {
should_exit: bool,
temperatures: Vec<u8>,
}
fn render(frame: &mut Frame, temperatures: &[u8]) {
let layout = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]).spacing(1);
let [title, main] = frame.area().layout(&layout);
impl App {
fn new() -> Self {
let mut rng = rng();
let temperatures = (0..24).map(|_| rng.random_range(50..90)).collect();
Self {
should_exit: false,
temperatures,
}
}
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
while !self.should_exit {
terminal.draw(|frame| self.render(frame))?;
self.handle_events()?;
}
Ok(())
}
fn handle_events(&mut self) -> Result<()> {
if event::read()?
.as_key_press_event()
.is_some_and(|key| key.code == KeyCode::Char('q'))
{
self.should_exit = true;
}
Ok(())
}
fn render(&self, frame: &mut Frame) {
let [title, main] = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)])
.spacing(1)
.areas(frame.area());
frame.render_widget("Weather demo".bold().into_centered_line(), title);
frame.render_widget(vertical_barchart(&self.temperatures), main);
}
frame.render_widget("Weather demo".bold().into_centered_line(), title);
frame.render_widget(vertical_barchart(temperatures), main);
}
/// Create a vertical bar chart from the temperatures data.
fn vertical_barchart(temperatures: &[u8]) -> BarChart {
fn vertical_barchart(temperatures: &[u8]) -> BarChart<'_> {
let bars: Vec<Bar> = temperatures
.iter()
.enumerate()
@@ -80,7 +51,7 @@ fn vertical_barchart(temperatures: &[u8]) -> BarChart {
.bar_width(5)
}
fn vertical_bar(hour: usize, temperature: &u8) -> Bar {
fn vertical_bar(hour: usize, temperature: &u8) -> Bar<'_> {
Bar::default()
.value(u64::from(*temperature))
.label(Line::from(format!("{hour:>02}:00")))

View File

@@ -1,39 +1,34 @@
/// An example of how to use [`WidgetRef`] to store heterogeneous widgets in a container.
///
/// This example creates a `StackContainer` widget that can hold any number of widgets of
/// different types. It creates two widgets, `Greeting` and `Farewell`, and stores them in a
/// `StackContainer` with a vertical layout. The `StackContainer` widget renders each of its
/// child widgets in the order they were added.
///
/// This example runs with the Ratatui library code in the branch that you are currently
/// reading. See the [`latest`] branch for the code which works with the most recent Ratatui
/// release.
///
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//! An example of how to use [`WidgetRef`] to store heterogeneous widgets in a container.
//!
//! This example creates a `StackContainer` widget that can hold any number of widgets of
//! different types. It creates two widgets, `Greeting` and `Farewell`, and stores them in a
//! `StackContainer` with a vertical layout. The `StackContainer` widget renders each of its
//! child widgets in the order they were added.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
use std::iter::zip;
use color_eyre::Result;
use crossterm::event;
use ratatui::Frame;
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::widgets::{Block, Paragraph, Widget, WidgetRef};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
ratatui::run(|terminal| {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
}
}
}
})
}
fn render(frame: &mut Frame) {

View File

@@ -0,0 +1,11 @@
[package]
name = "ratatui-state-examples"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre = "0.6"
crossterm = "0.29"
ratatui = { workspace = true, features = ["unstable-widget-ref"] }

View File

@@ -0,0 +1,179 @@
# Ratatui State Management Examples
This collection demonstrates various patterns for handling both mutable and immutable state in
Ratatui applications. Each example solves a counter problem - incrementing a counter value - but
uses different architectural approaches. These patterns represent common solutions to state
management challenges you'll encounter when building TUI applications.
For more information about widgets in Ratatui, see the [widgets module documentation](https://docs.rs/ratatui/latest/ratatui/widgets/index.html).
## When to Use Each Pattern
Choose the pattern that best fits your application's architecture and complexity:
- **Simple applications**: Use `render-function` or `mutable-widget` patterns
- **Clean separation**: Consider `stateful-widget` or `component-trait` patterns
- **Complex hierarchies**: Use `nested-*` patterns for parent-child relationships
- **Shared state**: Use `refcell` when multiple widgets need access to the same state
- **Advanced scenarios**: Use `widget-with-mutable-ref` when you understand Rust lifetimes well
## Running the Examples
To run any example, use:
```bash
cargo run --bin example-name
```
Press any key (or resize the terminal) to increment the counter. Press `<Esc>` or `q` to exit.
## Examples
### Immutable State Patterns
These patterns keep widget state immutable during rendering, with state updates happening outside
the render cycle. They're generally easier to reason about and less prone to borrowing issues.
#### [`immutable-function.rs`] - Function-Based Immutable State
**Best for**: Simple applications with pure rendering functions
**Pros**: Pure functions, easy to test, clear separation of concerns
**Cons**: Verbose parameter passing, limited integration with Ratatui ecosystem
Uses standalone functions that take immutable references to state. State updates happen in the
application loop outside of rendering.
#### [`immutable-shared-ref.rs`] - Shared Reference Pattern (Recommended)
**Best for**: Most modern Ratatui applications
**Pros**: Reusable widgets, efficient, integrates with Ratatui ecosystem, modern best practice
**Cons**: Requires external state management for dynamic behavior
Implements [`Widget`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.Widget.html) for
`&T`, allowing widgets to be rendered multiple times by reference without being consumed.
#### [`immutable-consuming.rs`] - Consuming Widget Pattern
**Best for**: Compatibility with older code, simple widgets created fresh each frame
**Pros**: Simple implementation, widely compatible, familiar pattern
**Cons**: Widget consumed on each render, requires reconstruction for reuse
Implements [`Widget`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.Widget.html) directly
on the owned type, consuming the widget when rendered.
### Mutable State Patterns
These patterns allow widgets to modify their state during rendering, useful for widgets that need
to update state as part of their rendering behavior.
#### [`mutable-function.rs`] - Function-Based Mutable State
**Best for**: Simple applications with minimal mutable state
**Pros**: Easy to understand, no traits to implement, direct control
**Cons**: State gets passed around as function parameters, harder to organize as complexity grows
Uses simple functions that accept mutable state references. State is managed at the application
level and passed down to render functions.
#### [`mutable-widget.rs`] - Mutable Widget Pattern
**Best for**: Self-contained widgets with their own mutable state
**Pros**: Encapsulates state within the widget, familiar OOP-style approach
**Cons**: Requires `&mut` references, can be challenging with complex borrowing scenarios
Implements [`Widget`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.Widget.html) for
`&mut T`, allowing the widget to mutate its own state during rendering.
### Intermediate Patterns
#### [`stateful-widget.rs`] - Stateful Widget Pattern
**Best for**: Clean separation of widget logic from state
**Pros**: Separates widget logic from state, reusable, idiomatic Ratatui pattern
**Cons**: State must be managed externally
Uses [`StatefulWidget`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.StatefulWidget.html)
to keep rendering logic separate from state management.
#### [`component-trait.rs`] - Custom Component Trait
**Best for**: Implementing consistent behavior across multiple widget types
**Pros**: Flexible, allows custom render signatures, good for widget frameworks
**Cons**: Non-standard, requires users to learn your custom API
Creates a custom trait similar to [`Widget`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.Widget.html)
but with a `&mut self` render method for direct mutation.
### Advanced Patterns
#### [`nested-mutable-widget.rs`] - Nested Mutable Widgets
**Best for**: Parent-child widget relationships with mutable state
**Pros**: Hierarchical organization, each widget manages its own state
**Cons**: Complex borrowing, requires careful lifetime management
Demonstrates how to nest widgets that both need mutable access to their state.
#### [`nested-stateful-widget.rs`] - Nested Stateful Widgets
**Best for**: Complex applications with hierarchical state management
**Pros**: Clean separation, composable, scales well with application complexity
**Cons**: More boilerplate, requires understanding of nested state patterns
Shows how to compose multiple [`StatefulWidget`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.StatefulWidget.html)s
in a parent-child hierarchy.
#### [`refcell.rs`] - Interior Mutability Pattern
**Best for**: Shared state across multiple widgets, complex state sharing scenarios
**Pros**: Allows shared mutable access, works with immutable widget references
**Cons**: Runtime borrow checking, potential panics, harder to debug
Uses [`Rc<RefCell<T>>`](https://doc.rust-lang.org/std/rc/struct.Rc.html) for interior mutability
when multiple widgets need access to the same state.
#### [`widget-with-mutable-ref.rs`] - Lifetime-Based Mutable References
**Best for**: Advanced users who need precise control over state lifetime
**Pros**: Zero-cost abstraction, explicit lifetime management
**Cons**: Complex lifetimes, requires deep Rust knowledge, easy to get wrong
Stores mutable references directly in widget structs using explicit lifetimes.
## Choosing the Right Pattern
**For most applications, start with immutable patterns:**
1. **Simple apps**: Use `immutable-function` for basic rendering with external state management
2. **Modern Ratatui**: Use `immutable-shared-ref` for reusable, efficient widgets (recommended)
3. **Legacy compatibility**: Use `immutable-consuming` when working with older code patterns
**Use mutable patterns when widgets need to update state during rendering:**
1. **Simple mutable state**: Begin with `mutable-function` or `mutable-widget` for prototypes
2. **Clean separation**: Use `stateful-widget` when you want to separate widget logic from state
3. **Hierarchical widgets**: Use `nested-*` patterns for complex widget relationships
4. **Shared state**: Use `refcell` when multiple widgets need the same state
5. **Performance critical**: Consider `widget-with-mutable-ref` for advanced lifetime management
## Common Pitfalls
- **Borrowing issues**: The borrow checker can be challenging with mutable state.
[`StatefulWidget`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.StatefulWidget.html)
often provides the cleanest solution.
- **Overengineering**: Don't use complex patterns like `refcell` or `widget-with-mutable-ref`
unless you actually need them.
- **State organization**: Keep state close to where it's used. Don't pass state through many
layers unnecessarily.
[`component-trait.rs`]: ./src/bin/component-trait.rs
[`immutable-consuming.rs`]: ./src/bin/immutable-consuming.rs
[`immutable-function.rs`]: ./src/bin/immutable-function.rs
[`immutable-shared-ref.rs`]: ./src/bin/immutable-shared-ref.rs
[`mutable-widget.rs`]: ./src/bin/mutable-widget.rs
[`nested-mutable-widget.rs`]: ./src/bin/nested-mutable-widget.rs
[`nested-stateful-widget.rs`]: ./src/bin/nested-stateful-widget.rs
[`refcell.rs`]: ./src/bin/refcell.rs
[`mutable-function.rs`]: ./src/bin/mutable-function.rs
[`stateful-widget.rs`]: ./src/bin/stateful-widget.rs
[`widget-with-mutable-ref.rs`]: ./src/bin/widget-with-mutable-ref.rs

View File

@@ -0,0 +1,83 @@
//! # Custom Component Trait Pattern
//!
//! This example demonstrates using a custom trait instead of the standard `Widget` trait for
//! handling mutable state during rendering. This pattern is useful when you want to implement
//! consistent behavior across multiple widget types without implementing the `Widget` trait for
//! each one.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - You're building a widget framework or library with custom behavior
//! - You want a consistent API across multiple widget types
//! - You need more control over the render method signature
//! - You're prototyping widget behavior before standardizing on `Widget` or `StatefulWidget`
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Flexible - you can define custom method signatures
//! - Consistent - enforces the same behavior across widget types
//! - Simple - no need to understand `StatefulWidget` complexity
//!
//! **Cons:**
//! - Non-standard - users must learn your custom API instead of Ratatui's standard traits
//! - Less discoverable - doesn't integrate with Ratatui's widget ecosystem
//! - Limited reuse - can't be used with existing Ratatui functions expecting `Widget`
//!
//! ## Example Usage
//!
//! The custom `Component` trait allows widgets to mutate their state directly during rendering
//! by taking `&mut self` instead of `self`. This is similar to the mutable widget pattern but
//! with a custom trait interface.
use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the custom component trait pattern for mutable state management.
///
/// Creates a counter widget using a custom `Component` trait and runs the application loop,
/// updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut counter = Counter::default();
loop {
terminal.draw(|frame| counter.render(frame, frame.area()))?;
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// A custom trait for components that can render themselves while mutating their state.
///
/// This trait provides an alternative to the standard `Widget` trait by allowing components to
/// take `&mut self`, enabling direct state mutation during rendering.
trait Component {
/// Render the component to the given area of the frame.
fn render(&mut self, frame: &mut Frame, area: Rect);
}
/// A simple counter component that increments its value each time it's rendered.
///
/// Demonstrates how the custom `Component` trait allows widgets to maintain and mutate
/// their own state during the rendering process.
#[derive(Default)]
struct Counter {
count: usize,
}
impl Component for Counter {
fn render(&mut self, frame: &mut Frame, area: Rect) {
self.count += 1;
frame.render_widget(format!("Counter: {count}", count = self.count), area);
}
}

View File

@@ -0,0 +1,86 @@
//! # Consuming Widget Pattern with Immutable State
//!
//! This example demonstrates implementing the `Widget` trait directly on the widget type,
//! causing it to be consumed when rendered. This was the original pattern in Ratatui and
//! is still commonly used, especially for simple widgets that are created fresh each frame.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - You're working with existing code that uses this pattern
//! - Your widgets are simple and created fresh each frame
//! - You want maximum compatibility with older Ratatui code
//! - You don't need to reuse widget instances
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Simple - straightforward implementation
//! - Compatible - works with all Ratatui versions
//! - Familiar - widely used pattern in existing code
//! - No borrowing - no need to manage references
//!
//! **Cons:**
//! - Consuming - widget is destroyed after each render
//! - Inefficient - requires reconstruction for repeated use
//! - Limited reuse - cannot store and reuse widget instances
//!
//! ## Example Usage
//!
//! The widget implements `Widget` directly on the owned type, meaning it's consumed
//! when rendered and must be recreated for subsequent renders.
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::Widget;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the consuming widget pattern for immutable state rendering.
///
/// Creates a new counter widget instance each frame, showing how the consuming
/// pattern works with immutable state that's managed externally.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut count = 0;
loop {
terminal.draw(|frame| {
// Widget is created fresh each time and consumed when rendered
let counter = Counter::new(count);
frame.render_widget(counter, frame.area());
})?;
// State updates happen outside of widget lifecycle
count += 1;
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// A simple counter widget that displays a count value.
///
/// Implements `Widget` directly on the owned type, meaning the widget is consumed
/// when rendered. The count state is managed externally and passed in during construction.
struct Counter {
count: usize,
}
impl Counter {
/// Create a new counter widget with the given count.
fn new(count: usize) -> Self {
Self { count }
}
}
impl Widget for Counter {
fn render(self, area: Rect, buf: &mut Buffer) {
// Widget is consumed here - self is moved, not borrowed
format!("Counter: {}", self.count).render(area, buf);
}
}

View File

@@ -0,0 +1,82 @@
//! # Function-Based Pattern with Immutable State
//!
//! This example demonstrates using standalone functions for rendering widgets with immutable
//! state. This pattern keeps state management completely separate from widget rendering logic,
//! making it easy to test and reason about.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - You prefer functional programming approaches
//! - Your rendering logic is simple and doesn't need complex widget hierarchies
//! - You want clear separation between state and rendering
//! - You're building simple UIs or prototyping quickly
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Simple - easy to understand and test
//! - Pure functions - no side effects in rendering
//! - Flexible - can easily compose multiple render functions
//! - Clear separation - state management is completely separate from rendering
//!
//! **Cons:**
//! - Limited - doesn't integrate with Ratatui's widget ecosystem
//! - Verbose - requires passing state explicitly to every function
//! - No reuse - can't be used with existing Ratatui widget infrastructure
//!
//! ## Example Usage
//!
//! The function takes immutable references to both the frame and state, ensuring that
//! rendering is a pure operation that doesn't modify state.
use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the function-based pattern for immutable state rendering.
///
/// Creates a counter state and renders it using a pure function, incrementing the counter
/// in the application loop rather than during rendering.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut counter_state = Counter::default();
loop {
terminal.draw(|frame| render_counter(frame, frame.area(), &counter_state))?;
// State updates happen outside of rendering
counter_state.increment();
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// State for the counter.
///
/// This state is managed externally and passed to render functions as an immutable reference.
#[derive(Default)]
struct Counter {
count: usize,
}
impl Counter {
/// Increment the counter value.
fn increment(&mut self) {
self.count += 1;
}
}
/// Pure render function that displays the counter state.
///
/// Takes immutable references to ensure rendering has no side effects on state.
/// This function can be easily tested and composed with other render functions.
fn render_counter(frame: &mut Frame, area: Rect, state: &Counter) {
frame.render_widget(format!("Counter: {}", state.count), area);
}

View File

@@ -0,0 +1,92 @@
//! # Shared Reference Pattern with Immutable State
//!
//! This example demonstrates implementing the `Widget` trait on a shared reference (`&Widget`)
//! with immutable state. This is the recommended pattern for most widgets in modern Ratatui
//! applications, as it allows widgets to be reused without being consumed.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - You want to reuse widgets across multiple renders
//! - Your widget doesn't need to modify its state during rendering
//! - You want the benefits of Ratatui's widget ecosystem
//! - You're building modern, efficient Ratatui applications
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Reusable - widget can be rendered multiple times without reconstruction
//! - Efficient - no cloning or reconstruction needed
//! - Standard - integrates with Ratatui's widget ecosystem
//! - Modern - follows current Ratatui best practices
//!
//! **Cons:**
//! - Immutable - cannot modify widget state during rendering
//! - External state - requires external state management for dynamic behavior
//!
//! ## Example Usage
//!
//! The widget implements `Widget for &Counter`, allowing it to be rendered by reference
//! while keeping its internal data immutable during rendering.
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::Widget;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the shared reference pattern for immutable widget rendering.
///
/// Creates a counter widget that can be rendered multiple times by reference,
/// with state updates happening outside the widget's render method.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut counter = Counter::new();
loop {
terminal.draw(|frame| {
// Widget is rendered by reference, can be reused
frame.render_widget(&counter, frame.area());
})?;
// State updates happen outside of rendering
counter.increment();
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// A counter widget with immutable rendering behavior.
///
/// Implements `Widget` on a shared reference, allowing the widget to be rendered
/// multiple times without being consumed while keeping its data immutable during rendering.
struct Counter {
count: usize,
}
impl Counter {
/// Create a new counter.
fn new() -> Self {
Self { count: 0 }
}
/// Increment the counter value.
///
/// This method modifies the counter's state outside of the rendering process,
/// maintaining the separation between state updates and rendering.
fn increment(&mut self) {
self.count += 1;
}
}
impl Widget for &Counter {
fn render(self, area: Rect, buf: &mut Buffer) {
// Rendering is immutable - no state changes occur here
format!("Counter: {}", self.count).render(area, buf);
}
}

View File

@@ -0,0 +1,67 @@
//! # Render Function Pattern
//!
//! This example demonstrates the simplest approach to handling mutable state - using regular
//! functions that accept mutable state references. This pattern works well for simple applications
//! and prototypes where you don't need the complexity of widget traits.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - Simple applications with minimal state management needs
//! - Prototypes and quick experiments
//! - When you prefer functional programming over object-oriented approaches
//! - Applications where state is naturally managed at the top level
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Extremely simple - no traits to implement or understand
//! - Direct and explicit - state flow is obvious
//! - Flexible - easy to modify without interface constraints
//! - Beginner-friendly - uses basic Rust concepts
//!
//! **Cons:**
//! - State must be passed through function parameters
//! - Harder to organize as application complexity grows
//! - No encapsulation - state management is scattered
//! - Less reusable than widget-based approaches
//! - Can lead to parameter passing through many layers
//!
//! ## Example Usage
//!
//! State is managed at the application level and passed to render functions as needed.
//! This approach works well when state is simple and doesn't need complex organization.
use ratatui::Frame;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the render function pattern for mutable state management.
///
/// Creates a counter using simple functions and runs the application loop,
/// updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut counter = 0;
loop {
terminal.draw(|frame| render(frame, &mut counter))?;
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// Renders a counter using a simple function-based approach.
///
/// Demonstrates the functional approach to state management where state is managed externally
/// and passed in as a parameter.
fn render(frame: &mut Frame, counter: &mut usize) {
*counter += 1;
frame.render_widget(format!("Counter: {counter}"), frame.area());
}

View File

@@ -0,0 +1,74 @@
//! # Mutable Widget Pattern
//!
//! This example demonstrates implementing the `Widget` trait on a mutable reference (`&mut T`)
//! to allow direct state mutation during rendering. This is one of the simplest approaches for
//! widgets that need to maintain their own state.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - You have self-contained widgets with their own state
//! - You prefer an object-oriented approach to widget design
//! - Your widget's state is simple and doesn't need complex sharing
//! - You want to encapsulate state within the widget itself
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Simple and intuitive - state is encapsulated within the widget
//! - Familiar pattern for developers coming from OOP backgrounds
//! - Direct state access without external state management
//! - Works well with Rust's ownership system for simple cases
//!
//! **Cons:**
//! - Can lead to borrowing challenges in complex scenarios
//! - Requires mutable access to the widget, which may not always be available
//! - Less flexible than `StatefulWidget` for shared or complex state patterns
//! - May require careful lifetime management in nested scenarios
//!
//! ## Example Usage
//!
//! The widget implements `Widget` for `&mut Self`, allowing it to mutate its internal state
//! during the render call. Each render increments a counter, demonstrating state mutation.
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::Widget;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the mutable widget pattern for mutable state management.
///
/// Creates a counter widget using `Widget` for `&mut Self` and runs the application loop,
/// updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut counter = Counter::default();
loop {
terminal.draw(|frame| frame.render_widget(&mut counter, frame.area()))?;
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// A counter widget that maintains its own state and increments on each render.
///
/// Demonstrates the mutable widget pattern by implementing `Widget` for `&mut Self`.
#[derive(Default)]
struct Counter {
counter: usize,
}
impl Widget for &mut Counter {
fn render(self, area: Rect, buf: &mut Buffer) {
self.counter += 1;
format!("Counter: {counter}", counter = self.counter).render(area, buf);
}
}

View File

@@ -0,0 +1,96 @@
//! # Nested Mutable Widget Pattern
//!
//! This example demonstrates nesting widgets that both need mutable access to their state.
//! This pattern is useful when you have a parent-child widget relationship where both widgets
//! need to maintain and mutate their own state during rendering.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - You have hierarchical widget relationships (parent-child)
//! - Each widget needs to maintain its own distinct state
//! - You prefer the mutable widget pattern over StatefulWidget
//! - Widgets have clear ownership of their state
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Clear hierarchical organization
//! - Each widget encapsulates its own state
//! - Intuitive parent-child relationships
//! - State ownership is explicit
//!
//! **Cons:**
//! - Complex borrowing scenarios can arise
//! - Requires careful lifetime management
//! - May lead to borrow checker issues in complex hierarchies
//! - Less flexible than StatefulWidget for state sharing
//!
//! ## Example Usage
//!
//! The parent `App` widget contains a child `Counter` widget. Both implement `Widget` for
//! `&mut Self`, allowing them to mutate their respective states during rendering. The parent
//! delegates rendering to the child while maintaining its own state structure.
use ratatui::DefaultTerminal;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::Widget;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the nested mutable widget pattern for mutable state management.
///
/// Creates a parent-child widget hierarchy using mutable widgets and runs the application loop,
/// updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
let app = App::default();
ratatui::run(|terminal| app.run(terminal))
}
/// The main application widget that contains and manages child widgets.
///
/// Demonstrates the parent widget in a nested mutable widget hierarchy.
#[derive(Default)]
struct App {
counter: Counter,
}
impl App {
/// Run the application with the given terminal.
fn run(mut self, terminal: &mut DefaultTerminal) -> color_eyre::Result<()> {
loop {
terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?;
if is_exit_key_pressed()? {
break Ok(());
}
}
}
}
impl Widget for &mut App {
fn render(self, area: Rect, buf: &mut Buffer) {
self.counter.render(area, buf);
}
}
/// A counter widget that maintains its own state within a nested hierarchy.
///
/// Can be used standalone or as a child within other widgets, demonstrating
/// how mutable widgets can be composed together.
#[derive(Default)]
struct Counter {
count: usize,
}
impl Widget for &mut Counter {
fn render(self, area: Rect, buf: &mut Buffer) {
self.count += 1;
format!("Counter: {count}", count = self.count).render(area, buf);
}
}

View File

@@ -0,0 +1,103 @@
//! # Nested StatefulWidget Pattern
//!
//! This example demonstrates composing multiple `StatefulWidget`s in a parent-child hierarchy.
//! This pattern is ideal for complex applications where you need clean separation of concerns
//! and want to leverage the benefits of the StatefulWidget pattern at multiple levels.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - Complex applications with hierarchical state management needs
//! - When you want clean separation between widgets and their state
//! - Building composable widget systems
//! - Applications that need testable, reusable widget components
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Excellent separation of concerns
//! - Highly composable and reusable widgets
//! - Easy to test individual widgets and their state
//! - Scales well with application complexity
//! - Follows idiomatic Ratatui patterns
//!
//! **Cons:**
//! - More boilerplate code than simpler patterns
//! - Requires understanding of nested state management
//! - State structures can become complex
//! - May be overkill for simple applications
//!
//! ## Example Usage
//!
//! The parent `App` widget manages application-level state while delegating specific
//! functionality to child widgets like `Counter`. Each widget is responsible for its own
//! state type and rendering logic, making the system highly modular.
use ratatui::DefaultTerminal;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::{StatefulWidget, Widget};
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the nested StatefulWidget pattern for mutable state management.
///
/// Creates a parent-child widget hierarchy using StatefulWidgets and runs the application loop,
/// updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(App::run)
}
/// The main application widget using the StatefulWidget pattern.
///
/// Demonstrates how to compose multiple StatefulWidgets together while coordinating
/// between different child widgets.
struct App;
impl App {
/// Run the application with the given terminal.
fn run(terminal: &mut DefaultTerminal) -> color_eyre::Result<()> {
let mut state = AppState { counter: 0 };
loop {
terminal.draw(|frame| frame.render_stateful_widget(App, frame.area(), &mut state))?;
if is_exit_key_pressed()? {
break Ok(());
}
}
}
}
/// Application state that contains all the state needed by the app and its child widgets.
///
/// Demonstrates how to organize hierarchical state in the StatefulWidget pattern.
struct AppState {
counter: usize,
}
impl StatefulWidget for App {
type State = AppState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
Counter.render(area, buf, &mut state.counter);
}
}
/// A counter widget that uses StatefulWidget for clean state separation.
///
/// Focuses purely on rendering logic and can be reused with different state instances.
struct Counter;
impl StatefulWidget for Counter {
type State = usize;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
*state += 1;
format!("Counter: {state}").render(area, buf);
}
}

View File

@@ -0,0 +1,86 @@
//! # Interior Mutability Pattern (RefCell)
//!
//! This example demonstrates using `Rc<RefCell<T>>` for interior mutability, allowing multiple
//! widgets to share and mutate the same state. This pattern is useful when you need shared
//! mutable state but can't use mutable references due to borrowing constraints.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - Multiple widgets need to access and modify the same state
//! - You can't use mutable references due to borrowing constraints
//! - You need shared ownership of mutable data
//! - Complex widget hierarchies where state needs to be accessed from multiple locations
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Allows shared mutable access to state
//! - Works with immutable widget references
//! - Enables complex state sharing patterns
//! - Can be cloned cheaply (reference counting)
//!
//! **Cons:**
//! - Runtime borrow checking - potential for panics if you violate borrowing rules
//! - Less efficient than compile-time borrow checking
//! - Harder to debug when borrow violations occur
//! - More complex than simpler state management patterns
//! - Can lead to subtle bugs if not used carefully
//!
//! ## Important Safety Notes
//!
//! - Only one mutable borrow can exist at a time
//! - Violating this rule will cause a panic at runtime
//! - Always minimize the scope of borrows to avoid conflicts
//!
//! ## Example Usage
//!
//! The widget wraps its state in `Rc<RefCell<T>>`, allowing the state to be shared and mutated
//! even when the widget itself is used by value (as required by the `Widget` trait).
use std::cell::RefCell;
use std::ops::AddAssign;
use std::rc::Rc;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::Widget;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the interior mutability pattern for mutable state management.
///
/// Creates a counter widget using `Rc<RefCell<T>>` and runs the application loop,
/// updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let counter = Counter::default();
loop {
terminal.draw(|frame| frame.render_widget(counter.clone(), frame.area()))?;
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// A counter widget that uses interior mutability for shared state management.
///
/// Demonstrates how `Rc<RefCell<T>>` enables mutable state access even when the
/// widget itself is used by value.
#[derive(Default, Clone)]
struct Counter {
count: Rc<RefCell<usize>>,
}
impl Widget for Counter {
fn render(self, area: Rect, buf: &mut Buffer) {
self.count.borrow_mut().add_assign(1);
format!("Counter: {count}", count = self.count.borrow()).render(area, buf);
}
}

View File

@@ -0,0 +1,78 @@
//! # StatefulWidget Pattern (Recommended)
//!
//! This example demonstrates the `StatefulWidget` trait, which is the recommended approach for
//! handling mutable state in Ratatui applications. This pattern separates the widget's rendering
//! logic from its state, making it more flexible and reusable.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - Most Ratatui applications (this is the recommended default)
//! - When building reusable widget libraries
//! - When you need clean separation between rendering logic and state
//! - When multiple widgets might share similar state structures
//! - When you want to follow idiomatic Ratatui patterns
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Clean separation of concerns between widget and state
//! - Reusable - the same widget can work with different state instances
//! - Testable - state and rendering logic can be tested independently
//! - Composable - works well with complex application architectures
//! - Idiomatic - follows Ratatui's recommended patterns
//!
//! **Cons:**
//! - Slightly more verbose than direct mutation patterns
//! - Requires understanding of the `StatefulWidget` trait
//! - State must be managed externally
//!
//! ## Example Usage
//!
//! The widget defines its rendering behavior through `StatefulWidget`, while the state is
//! managed separately. This allows the same widget to be used with different state instances
//! and makes testing easier.
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::{StatefulWidget, Widget};
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the StatefulWidget pattern for mutable state management.
///
/// Creates a counter widget using `StatefulWidget` and runs the application loop,
/// updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut counter = 0;
loop {
terminal.draw(|frame| {
frame.render_stateful_widget(CounterWidget, frame.area(), &mut counter)
})?;
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// A counter widget that uses the StatefulWidget pattern for state management.
///
/// Demonstrates the separation of rendering logic from state, making the widget reusable
/// with different state instances and easier to test.
struct CounterWidget;
impl StatefulWidget for CounterWidget {
type State = usize;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
*state += 1;
format!("Counter: {state}").render(area, buf);
}
}

View File

@@ -0,0 +1,84 @@
//! # Lifetime-Based Mutable References Pattern
//!
//! This example demonstrates storing mutable references directly in widget structs using explicit
//! lifetimes. This is an advanced pattern that provides zero-cost state access but requires
//! careful lifetime management.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - You need maximum performance with zero runtime overhead
//! - You have a good understanding of Rust lifetimes and borrowing
//! - State lifetime is clearly defined and relatively simple
//! - You're building performance-critical applications
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Zero runtime cost - no reference counting or runtime borrow checking
//! - Compile-time safety - borrow checker ensures memory safety
//! - Direct access to state without indirection
//! - Maximum performance for state access
//!
//! **Cons:**
//! - Complex lifetime management - requires deep Rust knowledge
//! - Easy to create compilation errors that are hard to understand
//! - Inflexible - lifetime constraints can make code harder to refactor
//! - Not suitable for beginners - requires advanced Rust skills
//! - Widget structs become less reusable due to lifetime constraints
//!
//! ## Important Considerations
//!
//! - The widget's lifetime is tied to the state's lifetime
//! - You must ensure the state outlives the widget
//! - Lifetime annotations can become complex in larger applications
//! - Consider simpler patterns unless performance is critical
//!
//! ## Example Usage
//!
//! The widget stores a mutable reference to external state, allowing direct access without
//! runtime overhead. The widget must be recreated for each render call due to the lifetime
//! constraints.
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::Widget;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the lifetime-based mutable references pattern for mutable state management.
///
/// Creates a counter widget using mutable references with explicit lifetimes and runs the
/// application loop, updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut count = 0;
loop {
let counter = CounterWidget { count: &mut count };
terminal.draw(|frame| frame.render_widget(counter, frame.area()))?;
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// A counter widget that holds a mutable reference to external state.
///
/// Demonstrates the lifetime-based pattern where the widget directly stores a
/// mutable reference to external state.
struct CounterWidget<'a> {
count: &'a mut usize,
}
impl Widget for CounterWidget<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
*self.count += 1;
format!("Counter: {count}", count = self.count).render(area, buf);
}
}

View File

@@ -0,0 +1,8 @@
//! Helper functions for checking if exit keys are pressed
use crossterm::event::{self, KeyCode};
pub fn is_exit_key_pressed() -> std::io::Result<bool> {
Ok(event::read()?
.as_key_press_event()
.is_some_and(|key| matches!(key.code, KeyCode::Esc | KeyCode::Char('q'))))
}

View File

@@ -4,7 +4,7 @@ description = """
Core types and traits for the Ratatui Terminal UI library.
Widget libraries should use this crate. Applications should use the main Ratatui crate.
"""
version = "0.1.0-alpha.5"
version = "0.1.0-alpha.6"
readme = "README.md"
authors.workspace = true
documentation.workspace = true
@@ -41,7 +41,7 @@ layout-cache = ["std"]
anstyle = ["dep:anstyle"]
## enables conversions from colors in the [`palette`] crate to [`Color`](crate::style::Color).
palette = ["dep:palette"]
palette = ["std", "dep:palette"]
## enables the backend code that sets the underline color. Underline color is only supported by
## the Crossterm backend, and is not supported on Windows 7.
@@ -53,7 +53,7 @@ scrolling-regions = []
## 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"]
serde = ["std", "dep:serde", "bitflags/serde", "compact_str/serde"]
[dependencies]
anstyle = { workspace = true, optional = true }

View File

@@ -29,6 +29,32 @@ Add `ratatui-core` to your `Cargo.toml`:
cargo add ratatui-core
```
## Crate Organization
`ratatui-core` is part of the Ratatui workspace that was modularized in version 0.30.0 to
improve compilation times, API stability, and dependency management. This crate provides the
foundational types and traits that other crates in the workspace depend on.
**When to use `ratatui-core`:**
- Building widget libraries that implement [`Widget`] or [`StatefulWidget`]
- Creating lightweight applications that don't need built-in widgets
- You want minimal dependencies and faster compilation times
- You need maximum API stability (core types change less frequently)
**When to use the main [`ratatui`] crate:**
- Building applications that use built-in widgets
- You want convenience and don't mind slightly longer compilation times
- You need backend implementations and terminal management utilities
For detailed information about the workspace organization, see [ARCHITECTURE.md].
[`ratatui`]: https://crates.io/crates/ratatui
[`Widget`]: widgets::Widget
[`StatefulWidget`]: widgets::StatefulWidget
[ARCHITECTURE.md]: https://github.com/ratatui/ratatui/blob/main/ARCHITECTURE.md
## Contributing
We welcome contributions from the community! Please see our [CONTRIBUTING](../CONTRIBUTING.md)

View File

@@ -104,8 +104,6 @@ impl Buffer {
}
/// Returns the content of the buffer as a slice
// https://github.com/rust-lang/rust/issues/139338
#[allow(clippy::missing_const_for_fn)]
pub fn content(&self) -> &[Cell] {
&self.content
}
@@ -1237,7 +1235,7 @@ mod tests {
}
/// Emojis normally contain various characters which should stay part of the Emoji.
/// This should work fine by utilizing unicode_segmentation but a testcase is probably helpful
/// This should work fine by utilizing `unicode_segmentation` but a testcase is probably helpful
/// due to the nature of never perfect Unicode implementations and all of its quirks.
#[rstest]
// Shrug without gender or skintone. Has a width of 2 like all emojis have.

View File

@@ -1,9 +1,10 @@
use compact_str::CompactString;
use crate::style::{Color, Modifier, Style};
use crate::symbols::merge::MergeStrategy;
/// A buffer cell
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Cell {
/// The string to be drawn in the cell.
@@ -14,7 +15,7 @@ pub struct Cell {
/// buffer for short strings.
///
/// See <https://github.com/ratatui/ratatui/pull/601> for more information.
symbol: CompactString,
symbol: Option<CompactString>,
/// The foreground color of the cell.
pub fg: Color,
@@ -35,35 +36,86 @@ pub struct Cell {
impl Cell {
/// An empty `Cell`
pub const EMPTY: Self = Self::new(" ");
pub const EMPTY: Self = Self {
symbol: None,
fg: Color::Reset,
bg: Color::Reset,
#[cfg(feature = "underline-color")]
underline_color: Color::Reset,
modifier: Modifier::empty(),
skip: false,
};
/// Creates a new `Cell` with the given symbol.
///
/// This works at compile time and puts the symbol onto the stack. Fails to build when the
/// symbol doesnt fit onto the stack and requires to be placed on the heap. Use
/// symbol doesn't fit onto the stack and requires to be placed on the heap. Use
/// `Self::default().set_symbol()` in that case. See [`CompactString::const_new`] for more
/// details on this.
pub const fn new(symbol: &'static str) -> Self {
Self {
symbol: CompactString::const_new(symbol),
fg: Color::Reset,
bg: Color::Reset,
#[cfg(feature = "underline-color")]
underline_color: Color::Reset,
modifier: Modifier::empty(),
skip: false,
symbol: Some(CompactString::const_new(symbol)),
..Self::EMPTY
}
}
/// Gets the symbol of the cell.
///
/// If the cell has no symbol, returns a single space character.
#[must_use]
pub fn symbol(&self) -> &str {
self.symbol.as_str()
self.symbol.as_ref().map_or(" ", |s| s.as_str())
}
/// Merges the symbol of the cell with the one already on the cell, using the provided
/// [`MergeStrategy`].
///
/// Merges [Box Drawing Unicode block] characters to create a single character representing
/// their combination, useful for [border collapsing]. Currently limited to box drawing
/// characters, with potential future support for others.
///
/// Merging may not be perfect due to Unicode limitations; some symbol combinations might not
/// produce a valid character. [`MergeStrategy`] defines how to handle such cases, e.g.,
/// `Exact` for valid merges only, or `Fuzzy` for close matches.
///
/// If the cell has no symbol set, it will set the symbol to the provided one rather than
/// merging.
///
/// # Example
///
/// ```
/// # use ratatui_core::buffer::Cell;
/// use ratatui_core::symbols::merge::MergeStrategy;
///
/// assert_eq!(
/// Cell::new("┘")
/// .merge_symbol("┏", MergeStrategy::Exact)
/// .symbol(),
/// "╆",
/// );
///
/// assert_eq!(
/// Cell::new("╭")
/// .merge_symbol("┘", MergeStrategy::Fuzzy)
/// .symbol(),
/// "┼",
/// );
/// ```
///
/// [border collapsing]: https://ratatui.rs/recipes/layout/collapse-borders/
/// [Box Drawing Unicode block]: https://en.wikipedia.org/wiki/Box_Drawing
pub fn merge_symbol(&mut self, symbol: &str, strategy: MergeStrategy) -> &mut Self {
let merged_symbol = self
.symbol
.as_ref()
.map_or(symbol, |s| strategy.merge(s, symbol));
self.symbol = Some(CompactString::new(merged_symbol));
self
}
/// Sets the symbol of the cell.
pub fn set_symbol(&mut self, symbol: &str) -> &mut Self {
self.symbol = CompactString::new(symbol);
self.symbol = Some(CompactString::new(symbol));
self
}
@@ -71,14 +123,14 @@ impl Cell {
///
/// This is particularly useful for adding zero-width characters to the cell.
pub(crate) fn append_symbol(&mut self, symbol: &str) -> &mut Self {
self.symbol.push_str(symbol);
self.symbol.get_or_insert_default().push_str(symbol);
self
}
/// Sets the symbol of the cell to a single character.
pub fn set_char(&mut self, ch: char) -> &mut Self {
let mut buf = [0; 4];
self.symbol = CompactString::new(ch.encode_utf8(&mut buf));
self.symbol = Some(CompactString::new(ch.encode_utf8(&mut buf)));
self
}
@@ -139,21 +191,49 @@ impl Cell {
/// Resets the cell to the empty state.
pub fn reset(&mut self) {
self.symbol = CompactString::const_new(" ");
self.fg = Color::Reset;
self.bg = Color::Reset;
#[cfg(feature = "underline-color")]
{
self.underline_color = Color::Reset;
}
self.modifier = Modifier::empty();
self.skip = false;
*self = Self::EMPTY;
}
}
impl Default for Cell {
fn default() -> Self {
Self::EMPTY
impl PartialEq for Cell {
/// Compares two `Cell`s for equality.
///
/// Note that cells with no symbol (i.e., `Cell::EMPTY`) are considered equal to cells with a
/// single space symbol. This is to ensure that empty cells are treated uniformly,
/// regardless of how they were created
fn eq(&self, other: &Self) -> bool {
// Treat None and Some(" ") as equal
let symbols_eq = self.symbol() == other.symbol();
#[cfg(feature = "underline-color")]
let underline_color_eq = self.underline_color == other.underline_color;
#[cfg(not(feature = "underline-color"))]
let underline_color_eq = true;
symbols_eq
&& underline_color_eq
&& self.fg == other.fg
&& self.bg == other.bg
&& self.modifier == other.modifier
&& self.skip == other.skip
}
}
impl Eq for Cell {}
impl core::hash::Hash for Cell {
/// Hashes the cell.
///
/// This treats symbols with Some(" ") as equal to None, so that empty cells are
/// treated uniformly, regardless of how they were created.
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.symbol().hash(state);
self.fg.hash(state);
self.bg.hash(state);
#[cfg(feature = "underline-color")]
self.underline_color.hash(state);
self.modifier.hash(state);
self.skip.hash(state);
}
}
@@ -175,7 +255,7 @@ mod tests {
assert_eq!(
cell,
Cell {
symbol: CompactString::const_new(""),
symbol: Some(CompactString::const_new("")),
fg: Color::Reset,
bg: Color::Reset,
#[cfg(feature = "underline-color")]

View File

@@ -1,5 +1,314 @@
#![warn(clippy::missing_const_for_fn)]
//! Provides types and traits for working with layout and positioning in the terminal.
//! Layout and positioning in terminal user interfaces.
//!
//! This module provides a comprehensive set of types and traits for working with layout and
//! positioning in terminal applications. It implements a flexible layout system that allows you to
//! divide the terminal screen into different areas using constraints, manage positioning and
//! sizing, and handle complex UI arrangements.
//!
//! The layout system in Ratatui is based on the Cassowary constraint solver algorithm, implemented
//! through the [`kasuari`] crate. This allows for sophisticated constraint-based layouts where
//! multiple requirements can be satisfied simultaneously, with priorities determining which
//! constraints take precedence when conflicts arise.
//!
//! [`kasuari`]: https://crates.io/crates/kasuari
//!
//! # Core Concepts
//!
//! ## Coordinate System
//!
//! The coordinate system runs left to right, top to bottom, with the origin `(0, 0)` in the top
//! left corner of the terminal. The x and y coordinates are represented by `u16` values.
//!
//! ```text
//! x (columns)
//! ┌─────────────→
//! y │ (0,0)
//! │
//! (rows)
//! ↓
//! ```
//!
//! ## Layout Fundamentals
//!
//! Layouts form the structural foundation of your terminal UI. The [`Layout`] struct divides
//! available screen space into rectangular areas using a constraint-based approach. You define
//! multiple constraints for how space should be allocated, and the Cassowary solver determines
//! the optimal layout that satisfies as many constraints as possible. These areas can then be
//! used to render widgets or nested layouts.
//!
//! Note that the [`Layout`] struct is not required to create layouts - you can also manually
//! calculate and create [`Rect`] areas using simple mathematics to divide up the terminal space
//! if you prefer direct control over positioning and sizing.
//!
//! ## Rectangular Areas
//!
//! All layout operations work with rectangular areas represented by the [`Rect`] type. A [`Rect`]
//! defines a position and size in the terminal, specified by its top-left corner coordinates and
//! dimensions.
//!
//! # Available Types
//!
//! ## Core Layout Types
//!
//! - [`Layout`] - The primary layout engine that divides space using constraints and direction
//! - [`Rect`] - Represents a rectangular area with position and dimensions
//! - [`Constraint`] - Defines how space should be allocated (length, percentage, ratio, etc.)
//! - [`Direction`] - Specifies layout orientation (horizontal or vertical)
//! - [`Flex`] - Controls space distribution when constraints are satisfied
//!
//! ## Positioning and Sizing
//!
//! - [`Position`] - Represents a point in the terminal coordinate system
//! - [`Size`] - Represents dimensions (width and height)
//! - [`Margin`] - Defines spacing around rectangular areas
//! - [`Offset`] - Represents relative movement in the coordinate system
//! - [`Spacing`] - Controls spacing or overlap between layout segments
//!
//! ## Alignment
//!
//! - [`Alignment`] (alias for [`HorizontalAlignment`]) - Horizontal text/content alignment
//! - [`HorizontalAlignment`] - Horizontal alignment options (left, center, right)
//! - [`VerticalAlignment`] - Vertical alignment options (top, center, bottom)
//!
//! ## Iteration Support
//!
//! - [`Rows`] - Iterator over horizontal rows within a rectangular area
//! - [`Columns`] - Iterator over vertical columns within a rectangular area
//! - [`Positions`] - Iterator over all positions within a rectangular area
//!
//! # Quick Start
//!
//! Here's a simple example of creating a basic layout using the [`Layout`] struct:
//!
//! ```rust
//! use ratatui_core::layout::{Constraint, Direction, Layout, Rect};
//!
//! // Create a terminal area
//! let area = Rect::new(0, 0, 80, 24);
//!
//! // Divide it vertically into two equal parts using Layout
//! let layout = Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]);
//! let [top, bottom] = layout.areas(area);
//!
//! // Now you have two areas: top and bottom
//! ```
//!
//! **Note**: When the number of layout areas is known at compile time, use destructuring
//! assignment with descriptive variable names for better readability:
//!
//! ```rust
//! use ratatui_core::layout::{Constraint, Layout, Rect};
//!
//! let area = Rect::new(0, 0, 80, 24);
//! let [header, content, footer] = Layout::vertical([
//! Constraint::Length(3),
//! Constraint::Fill(1),
//! Constraint::Length(1),
//! ])
//! .areas(area);
//! ```
//!
//! Use [`Layout::split`] when the number of areas is only known at runtime.
//!
//! Alternatively, you can create layouts manually using mathematics:
//!
//! ```rust
//! use ratatui_core::layout::Rect;
//!
//! // Create a terminal area
//! let area = Rect::new(0, 0, 80, 24);
//!
//! // Manually divide into two equal parts
//! let top_half = Rect::new(area.x, area.y, area.width, area.height / 2);
//! let bottom_half = Rect::new(
//! area.x,
//! area.y + area.height / 2,
//! area.width,
//! area.height / 2,
//! );
//! ```
//!
//! # Layout Examples
//!
//! ## Basic Vertical Split
//!
//! ```rust
//! use ratatui_core::layout::{Constraint, Layout, Rect};
//!
//! let area = Rect::new(0, 0, 80, 24);
//! let [header, content, footer] = Layout::vertical([
//! Constraint::Length(3), // Header: fixed height
//! Constraint::Fill(1), // Content: flexible
//! Constraint::Length(1), // Footer: fixed height
//! ])
//! .areas(area);
//! ```
//!
//! ## Horizontal Sidebar Layout
//!
//! ```rust
//! use ratatui_core::layout::{Constraint, Layout, Rect};
//!
//! let area = Rect::new(0, 0, 80, 24);
//! let [sidebar, main] = Layout::horizontal([
//! Constraint::Length(20), // Sidebar: fixed width
//! Constraint::Fill(1), // Main content: flexible
//! ])
//! .areas(area);
//! ```
//!
//! ## Complex Nested Layout
//!
//! ```rust
//! use ratatui_core::layout::{Constraint, Layout, Rect};
//!
//! fn create_complex_layout(area: Rect) -> [Rect; 4] {
//! // First, split vertically
//! let [header, body, footer] = Layout::vertical([
//! Constraint::Length(3), // Header
//! Constraint::Fill(1), // Body
//! Constraint::Length(1), // Footer
//! ])
//! .areas(area);
//!
//! // Then split the body horizontally
//! let [sidebar, main] = Layout::horizontal([
//! Constraint::Length(20), // Sidebar
//! Constraint::Fill(1), // Main
//! ])
//! .areas(body);
//!
//! [header, sidebar, main, footer]
//! }
//! ```
//!
//! # Working with Constraints
//!
//! [`Constraint`]s define how space is allocated within a layout using the Cassowary constraint
//! solver algorithm. The constraint solver attempts to satisfy all constraints simultaneously,
//! with priorities determining which constraints take precedence when conflicts arise. Different
//! constraint types serve different purposes:
//!
//! - [`Constraint::Min`] - Minimum size constraint
//! - [`Constraint::Max`] - Maximum size constraint
//! - [`Constraint::Length`] - Fixed size in character cells
//! - [`Constraint::Percentage`] - Relative size as a percentage of available space
//! - [`Constraint::Ratio`] - Proportional size using ratios
//! - [`Constraint::Fill`] - Proportional fill of remaining space
//!
//! Constraints are resolved in priority order, with [`Constraint::Min`] having the highest
//! priority and [`Constraint::Fill`] having the lowest. The constraint solver will satisfy as
//! many constraints as possible while respecting these priorities.
//!
//! # Flexible Space Distribution
//!
//! The [`Flex`] enum controls how extra space is distributed when constraints are satisfied:
//!
//! - [`Flex::Start`] - Align content to the start, leaving excess space at the end
//! - [`Flex::End`] - Align content to the end, leaving excess space at the start
//! - [`Flex::Center`] - Center content, distributing excess space equally on both sides
//! - [`Flex::SpaceBetween`] - Distribute excess space evenly *between* elements, none at the ends
//! - [`Flex::SpaceAround`] - Distribute space *around* elements: equal padding on both sides of
//! each element; gaps between elements are twice the edge spacing
//! - [`Flex::SpaceEvenly`] - Distribute space *evenly*: equal spacing between all elements,
//! including before the first and after the last.
//! - [`Flex::Legacy`] - Legacy behavior (puts excess space in the last element)
//!
//! # Positioning and Alignment
//!
//! Use [`Position`] to represent specific points in the terminal, [`Size`] for dimensions, and the
//! alignment types for controlling content positioning within areas:
//!
//! ```rust
//! use ratatui_core::layout::{Alignment, Position, Rect, Size};
//!
//! let pos = Position::new(10, 5);
//! let size = Size::new(80, 24);
//! let rect = Rect::new(pos.x, pos.y, size.width, size.height);
//!
//! // Alignment for content within areas
//! let center = Alignment::Center;
//! ```
//!
//! # Advanced Features
//!
//! ## Margins and Spacing
//!
//! Add spacing around areas using uniform margins or between layout segments using [`Spacing`]:
//!
//! ```rust
//! use ratatui_core::layout::{Constraint, Layout, Margin, Rect, Spacing};
//!
//! let layout = Layout::vertical([Constraint::Fill(1), Constraint::Fill(1)])
//! .margin(2) // 2-cell margin on all sides
//! .spacing(Spacing::Space(1)); // 1-cell spacing between segments
//!
//! // For asymmetric margins, use the Rect inner method directly
//! let area = Rect::new(0, 0, 80, 24).inner(Margin::new(2, 1));
//! ```
//!
//! ## Area Iteration
//!
//! Iterate over rows, columns, or all positions within a rectangular area. The `rows()` and
//! `columns()` iterators return full [`Rect`] regions that can be used to render widgets or
//! passed to other layout methods for more complex nested layouts. The `positions()` iterator
//! returns [`Position`] values representing individual cell coordinates:
//!
//! ```rust
//! use ratatui_core::buffer::Buffer;
//! use ratatui_core::layout::{Constraint, Layout, Rect};
//! use ratatui_core::widgets::Widget;
//!
//! let area = Rect::new(0, 0, 20, 10);
//! let mut buffer = Buffer::empty(area);
//!
//! // Renders "Row 0", "Row 1", etc. in each horizontal row
//! for (i, row) in area.rows().enumerate() {
//! format!("Row {i}").render(row, &mut buffer);
//! }
//!
//! // Renders column indices (0-9 repeating) in each vertical column
//! for (i, col) in area.columns().enumerate() {
//! format!("{}", i % 10).render(col, &mut buffer);
//! }
//!
//! // Renders position indices (0-9 repeating) at each cell position
//! for (i, pos) in area.positions().enumerate() {
//! buffer[pos].set_symbol(&format!("{}", i % 10));
//! }
//! ```
//!
//! # Performance Considerations
//!
//! The layout system includes optional caching to improve performance for repeated layout
//! calculations. Layout caching is enabled by default in the main `ratatui` crate, but requires
//! explicitly enabling the `layout-cache` feature when using `ratatui-core` directly. When
//! enabled, layout results are cached based on the area and layout configuration.
//!
//! # Related Documentation
//!
//! For more detailed information and practical examples:
//!
//! - [Layout Concepts](https://ratatui.rs/concepts/layout/) - Comprehensive guide to layout
//! concepts
//! - [Layout Recipes](https://ratatui.rs/recipes/layout/) - Practical layout examples and patterns
//! - [Grid Layout Recipe](https://ratatui.rs/recipes/layout/grid/) - Creating grid-based layouts
//! - [Center a Widget Recipe](https://ratatui.rs/recipes/layout/center-a-widget/) - Centering
//! content
//! - [Dynamic Layouts Recipe](https://ratatui.rs/recipes/layout/dynamic/) - Creating responsive
//! layouts
//!
//! # Examples
//!
//! See the Ratatui repository for complete examples:
//!
//! - [`constraints`](https://github.com/ratatui/ratatui/blob/main/examples/apps/constraints/) -
//! Demonstrates different constraint types
//! - [`flex`](https://github.com/ratatui/ratatui/blob/main/examples/apps/flex/) - Shows flex space
//! distribution
//! - [`layout`](https://github.com/ratatui/ratatui/blob/main/examples/apps/layout/) - Basic layout
//! examples
mod alignment;
mod constraint;

View File

@@ -7,12 +7,19 @@ use strum::{Display, EnumString};
/// and libraries, it's unlikely that this alias will be removed in the future.
pub type Alignment = HorizontalAlignment;
/// A type representing horizontal alignment.
/// Horizontal content alignment within a layout area.
///
/// Prior to Ratatui 0.30.0, this type was named `Alignment`. In Ratatui 0.30.0, the name was
/// changed to `HorizontalAlignment` to make it more descriptive. The old name is still available as
/// an alias for backwards compatibility.
///
/// This type is used throughout Ratatui to control how content is positioned horizontally within
/// available space. It's commonly used with widgets to control text alignment, but can also be
/// used in layout calculations.
///
/// For comprehensive layout documentation and examples, see the [`layout`](crate::layout) module.
#[derive(Debug, Default, Display, EnumString, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum HorizontalAlignment {
#[default]
Left,
@@ -20,8 +27,14 @@ pub enum HorizontalAlignment {
Right,
}
/// A type representing vertical alignment.
/// Vertical content alignment within a layout area.
///
/// This type is used to control how content is positioned vertically within available space.
/// It complements [`HorizontalAlignment`] to provide full 2D positioning control.
///
/// For comprehensive layout documentation and examples, see the [`layout`](crate::layout) module.
#[derive(Debug, Default, Display, EnumString, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum VerticalAlignment {
#[default]
Top,

View File

@@ -5,10 +5,9 @@ use strum::EnumIs;
/// A constraint that defines the size of a layout element.
///
/// Constraints can be used to specify a fixed size, a percentage of the available space, a ratio of
/// the available space, a minimum or maximum size or a fill proportional value for a layout
/// element.
///
/// Constraints are the core mechanism for defining how space should be allocated within a
/// [`Layout`](crate::layout::Layout). They can specify fixed sizes (length), proportional sizes
/// (percentage, ratio), size limits (min, max), or proportional fill values for layout elements.
/// Relative constraints (percentage, ratio) are calculated relative to the entire space being
/// divided, rather than the space available after applying more fixed constraints (min, max,
/// length).
@@ -22,6 +21,27 @@ use strum::EnumIs;
/// 5. [`Constraint::Ratio`]
/// 6. [`Constraint::Fill`]
///
/// # Size Calculation
///
/// - [`apply`](Self::apply) - Apply the constraint to a length and return the resulting size
///
/// # Collection Creation
///
/// - [`from_lengths`](Self::from_lengths) - Create a collection of length constraints
/// - [`from_ratios`](Self::from_ratios) - Create a collection of ratio constraints
/// - [`from_percentages`](Self::from_percentages) - Create a collection of percentage constraints
/// - [`from_maxes`](Self::from_maxes) - Create a collection of maximum constraints
/// - [`from_mins`](Self::from_mins) - Create a collection of minimum constraints
/// - [`from_fills`](Self::from_fills) - Create a collection of fill constraints
///
/// # Conversion and Construction
///
/// - [`from(u16)`](Self::from) - Create a [`Length`](Self::Length) constraint from `u16`
/// - [`from(&Constraint)`](Self::from) - Create from `&Constraint` (copy)
/// - [`as_ref()`](Self::as_ref) - Get a reference to self
/// - [`default()`](Self::default) - Create default constraint
/// ([`Percentage(100)`](Self::Percentage))
///
/// # Examples
///
/// `Constraint` provides helper methods to create lists of constraints from various input formats.
@@ -45,7 +65,10 @@ use strum::EnumIs;
/// // Create a layout with fill proportional sizes for each element
/// let constraints = Constraint::from_fills([1, 2, 1]);
/// ```
///
/// For comprehensive layout documentation and examples, see the [`layout`](crate::layout) module.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumIs)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Constraint {
/// Applies a minimum size constraint to the element
///

View File

@@ -1,6 +1,16 @@
use strum::{Display, EnumString};
/// Defines the direction of a layout.
///
/// This enumeration is used with [`Layout`](crate::layout::Layout) to specify whether layout
/// segments should be arranged horizontally or vertically.
///
/// - `Horizontal`: Layout segments are arranged side by side (left to right)
/// - `Vertical`: Layout segments are arranged top to bottom (default)
///
/// For comprehensive layout documentation and examples, see the [`layout`](crate::layout) module.
#[derive(Debug, Default, Display, EnumString, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Direction {
Horizontal,
#[default]

View File

@@ -5,7 +5,12 @@ use crate::layout::Constraint;
/// Defines the options for layout flex justify content in a container.
///
/// This enumeration controls the distribution of space when layout constraints are met.
/// This enumeration controls the distribution of space when layout constraints are met and there
/// is excess space available. The `Flex` option is used with [`Layout`](crate::layout::Layout) to
/// control how extra space is distributed among layout segments, which is particularly useful for
/// creating responsive layouts that adapt to different terminal sizes.
///
/// Available options:
///
/// - `Legacy`: Fills the available space within the container, putting excess space into the last
/// element.
@@ -14,7 +19,10 @@ use crate::layout::Constraint;
/// - `Center`: Centers items within the container.
/// - `SpaceBetween`: Adds excess space between each element.
/// - `SpaceAround`: Adds excess space around each element.
///
/// For comprehensive layout documentation and examples, see the [`layout`](crate::layout) module.
#[derive(Copy, Debug, Default, Display, EnumString, Clone, Eq, PartialEq, Hash, EnumIs)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Flex {
/// Fills the available space within the container, putting excess space into the last
/// constraint of the lowest priority. This matches the default behavior of ratatui and tui
@@ -155,7 +163,8 @@ pub enum Flex {
/// ```
SpaceBetween,
/// Adds excess space around each element.
/// Evenly distributes excess space between all elements, including before the first and after
/// the last.
///
/// # Examples
///
@@ -175,6 +184,28 @@ pub enum Flex {
/// │ Max(20) │
/// └──────────────────┘
/// ```
SpaceEvenly,
/// Adds excess space around each element.
///
/// # Examples
///
/// ```plain
/// <------------------------------------80 px------------------------------------->
/// ┌────16 px─────┐ ┌──────20 px───────┐ ┌──────20 px───────┐
/// │Percentage(20)│ │ Length(20) │ │ Length(20) │
/// └──────────────┘ └──────────────────┘ └──────────────────┘
///
/// <------------------------------------80 px------------------------------------->
/// ┌──────20 px───────┐ ┌──────20 px───────┐
/// │ Max(20) │ │ Max(20) │
/// └──────────────────┘ └──────────────────┘
///
/// <------------------------------------80 px------------------------------------->
/// ┌──────20 px───────┐
/// │ Max(20) │
/// └──────────────────┘
/// ```
SpaceAround,
}
#[cfg(test)]

View File

@@ -1,5 +1,6 @@
use alloc::rc::Rc;
use alloc::vec::Vec;
use core::array::TryFromSliceError;
use core::iter;
#[cfg(feature = "layout-cache")]
use core::num::NonZeroUsize;
@@ -75,6 +76,7 @@ std::thread_local! {
///
/// See the [`Layout::spacing`] method for details on how to use this enum.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Spacing {
Space(u16),
Overlap(u16),
@@ -108,47 +110,57 @@ impl From<i16> for Spacing {
}
}
/// The primary layout engine for dividing terminal space using constraints and direction.
///
/// A layout is a set of constraints that can be applied to a given area to split it into smaller
/// ones.
/// rectangular areas. This is the core building block for creating structured user interfaces in
/// terminal applications.
///
/// A layout is composed of:
/// - a direction (horizontal or vertical)
/// - a set of constraints (length, ratio, percentage, fill, min, max)
/// - a margin (horizontal and vertical), the space between the edge of the main area and the split
/// areas
/// - a flex option
/// - a spacing option
/// - a flex option that controls space distribution
/// - a spacing option that controls gaps between segments
///
/// The algorithm used to compute the layout is based on the [`kasuari`] solver. It is a simple
/// linear solver that can be used to solve linear equations and inequalities. In our case, we
/// define a set of constraints that are applied to split the provided area into Rects aligned in a
/// single direction, and the solver computes the values of the position and sizes that satisfy as
/// many of the constraints in order of their priorities.
/// The algorithm used to compute the layout is based on the [`kasuari`] solver, a linear constraint
/// solver that computes positions and sizes to satisfy as many constraints as possible in order of
/// their priorities.
///
/// When the layout is computed, the result is cached in a thread-local cache, so that subsequent
/// calls with the same parameters are faster. The cache is a `LruCache`, and the size of the cache
/// can be configured using [`Layout::init_cache()`].
///
/// # Constructors
/// # Construction
///
/// There are four ways to create a new layout:
/// - [`default`](Default::default) - Create a layout with default values (vertical direction, no
/// constraints, no margin)
/// - [`new`](Self::new) - Create a new layout with a given direction and constraints
/// - [`vertical`](Self::vertical) - Create a new vertical layout with the given constraints
/// - [`horizontal`](Self::horizontal) - Create a new horizontal layout with the given constraints
///
/// - [`Layout::default`]: create a new layout with default values
/// - [`Layout::new`]: create a new layout with a given direction and constraints
/// - [`Layout::vertical`]: create a new vertical layout with the given constraints
/// - [`Layout::horizontal`]: create a new horizontal layout with the given constraints
/// # Configuration
///
/// # Setters
/// - [`direction`](Self::direction) - Set the direction of the layout
/// - [`constraints`](Self::constraints) - Set the constraints of the layout
/// - [`margin`](Self::margin) - Set uniform margin on all sides
/// - [`horizontal_margin`](Self::horizontal_margin) - Set the horizontal margin of the layout
/// - [`vertical_margin`](Self::vertical_margin) - Set the vertical margin of the layout
/// - [`flex`](Self::flex) - Set the way space is distributed when constraints are satisfied
/// - [`spacing`](Self::spacing) - Set the gap between the constraints of the layout
///
/// There are several setters to modify the layout:
/// # Layout Operations
///
/// - [`Layout::direction`]: set the direction of the layout
/// - [`Layout::constraints`]: set the constraints of the layout
/// - [`Layout::margin`]: set the margin of the layout
/// - [`Layout::horizontal_margin`]: set the horizontal margin of the layout
/// - [`Layout::vertical_margin`]: set the vertical margin of the layout
/// - [`Layout::flex`]: set the way the space is distributed when the constraints are satisfied
/// - [`Layout::spacing`]: sets the gap between the constraints of the layout
/// - [`areas`](Self::areas) - Split area into fixed number of rectangles (compile-time known)
/// - [`spacers`](Self::spacers) - Get spacer rectangles between layout areas
/// - [`split`](Self::split) - Split area into rectangles (runtime determined count)
/// - [`split_with_spacers`](Self::split_with_spacers) - Split area and return both areas and
/// spacers
///
/// # Cache Management
///
/// - [`init_cache`](Self::init_cache) - Initialize layout cache with custom size
///
/// # Example
///
@@ -159,22 +171,25 @@ impl From<i16> for Spacing {
/// use ratatui_core::widgets::Widget;
///
/// fn render(area: Rect, buf: &mut Buffer) {
/// let layout = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
/// let [left, right] = layout.areas(area);
/// Text::from("foo").render(left, buf);
/// Text::from("bar").render(right, buf);
/// let layout = Layout::vertical([Constraint::Length(5), Constraint::Fill(1)]);
/// let [top, bottom] = layout.areas(area);
/// Text::from("foo").render(top, buf);
/// Text::from("bar").render(bottom, buf);
/// }
/// ```
///
/// See the `layout`, `flex`, and `constraints` examples in the [Examples] folder for more details
/// about how to use layouts.
///
/// For comprehensive layout documentation and examples, see the [`layout`](crate::layout) module.
///
/// ![layout
/// example](https://camo.githubusercontent.com/77d22f3313b782a81e5e033ef82814bb48d786d2598699c27f8e757ccee62021/68747470733a2f2f7668732e636861726d2e73682f7668732d315a4e6f4e4c4e6c4c746b4a58706767396e435635652e676966)
///
/// [`kasuari`]: https://crates.io/crates/kasuari
/// [Examples]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Layout {
direction: Direction,
constraints: Vec<Constraint>,
@@ -213,7 +228,7 @@ impl Layout {
///
/// Layout::new(
/// Direction::Horizontal,
/// [Constraint::Length(5), Constraint::Min(0)],
/// [Constraint::Length(5), Constraint::Fill(1)],
/// );
///
/// Layout::new(
@@ -245,7 +260,7 @@ impl Layout {
/// ```rust
/// use ratatui_core::layout::{Constraint, Layout};
///
/// let layout = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
/// let layout = Layout::vertical([Constraint::Length(5), Constraint::Fill(1)]);
/// ```
pub fn vertical<I>(constraints: I) -> Self
where
@@ -265,7 +280,7 @@ impl Layout {
/// ```rust
/// use ratatui_core::layout::{Constraint, Layout};
///
/// let layout = Layout::horizontal([Constraint::Length(5), Constraint::Min(0)]);
/// let layout = Layout::horizontal([Constraint::Length(5), Constraint::Fill(1)]);
/// ```
pub fn horizontal<I>(constraints: I) -> Self
where
@@ -297,13 +312,13 @@ impl Layout {
///
/// let layout = Layout::default()
/// .direction(Direction::Horizontal)
/// .constraints([Constraint::Length(5), Constraint::Min(0)])
/// .constraints([Constraint::Length(5), Constraint::Fill(1)])
/// .split(Rect::new(0, 0, 10, 10));
/// assert_eq!(layout[..], [Rect::new(0, 0, 5, 10), Rect::new(5, 0, 5, 10)]);
///
/// let layout = Layout::default()
/// .direction(Direction::Vertical)
/// .constraints([Constraint::Length(5), Constraint::Min(0)])
/// .constraints([Constraint::Length(5), Constraint::Fill(1)])
/// .split(Rect::new(0, 0, 10, 10));
/// assert_eq!(layout[..], [Rect::new(0, 0, 10, 5), Rect::new(0, 5, 10, 5)]);
/// ```
@@ -350,10 +365,10 @@ impl Layout {
/// ]
/// );
///
/// Layout::default().constraints([Constraint::Min(0)]);
/// Layout::default().constraints(&[Constraint::Min(0)]);
/// Layout::default().constraints(vec![Constraint::Min(0)]);
/// Layout::default().constraints([Constraint::Min(0)].iter().filter(|_| true));
/// Layout::default().constraints([Constraint::Fill(1)]);
/// Layout::default().constraints(&[Constraint::Fill(1)]);
/// Layout::default().constraints(vec![Constraint::Fill(1)]);
/// Layout::default().constraints([Constraint::Fill(1)].iter().filter(|_| true));
/// Layout::default().constraints([1, 2, 3].iter().map(|&c| Constraint::Length(c)));
/// Layout::default().constraints([1, 2, 3]);
/// Layout::default().constraints(vec![1, 2, 3]);
@@ -376,7 +391,7 @@ impl Layout {
/// use ratatui_core::layout::{Constraint, Layout, Rect};
///
/// let layout = Layout::default()
/// .constraints([Constraint::Min(0)])
/// .constraints([Constraint::Fill(1)])
/// .margin(2)
/// .split(Rect::new(0, 0, 10, 10));
/// assert_eq!(layout[..], [Rect::new(2, 2, 6, 6)]);
@@ -398,7 +413,7 @@ impl Layout {
/// use ratatui_core::layout::{Constraint, Layout, Rect};
///
/// let layout = Layout::default()
/// .constraints([Constraint::Min(0)])
/// .constraints([Constraint::Fill(1)])
/// .horizontal_margin(2)
/// .split(Rect::new(0, 0, 10, 10));
/// assert_eq!(layout[..], [Rect::new(2, 0, 6, 10)]);
@@ -417,7 +432,7 @@ impl Layout {
/// use ratatui_core::layout::{Constraint, Layout, Rect};
///
/// let layout = Layout::default()
/// .constraints([Constraint::Min(0)])
/// .constraints([Constraint::Fill(1)])
/// .vertical_margin(2)
/// .split(Rect::new(0, 0, 10, 10));
/// assert_eq!(layout[..], [Rect::new(0, 2, 10, 6)]);
@@ -438,8 +453,10 @@ impl Layout {
/// - [`Flex::Start`]: The items are aligned to the start of the layout.
/// - [`Flex::Center`]: The items are aligned to the center of the layout.
/// - [`Flex::End`]: The items are aligned to the end of the layout.
/// - [`Flex::SpaceAround`]: The items are evenly distributed with equal space around them.
/// - [`Flex::SpaceBetween`]: The items are evenly distributed with equal space between them.
/// - [`Flex::SpaceAround`]: The items are evenly distributed with equal space around them,
/// except the first and last items, which have half the space on their sides.
/// - [`Flex::SpaceEvenly`]: The items are evenly distributed with equal space around them.
///
/// # Examples
///
@@ -478,7 +495,8 @@ impl Layout {
/// passed to this function. See the documentation of the [`Spacing`] enum for more information.
///
/// Note that if the layout has only one segment, the spacing will not be applied.
/// Also, spacing will not be applied for [`Flex::SpaceAround`] and [`Flex::SpaceBetween`]
/// Also, spacing will not be applied for [`Flex::SpaceAround`], [`Flex::SpaceEvenly`] and
/// [`Flex::SpaceBetween`]
///
/// # Examples
///
@@ -523,17 +541,53 @@ impl Layout {
/// # Examples
///
/// ```rust
/// use ratatui_core::layout::{Layout, Constraint, Rect};
/// use ratatui_core::layout::{Constraint, Layout, Rect};
///
/// let area = Rect::new(0, 0, 10, 10);
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]);
/// let [top, main] = layout.areas(area);
///
/// // or explicitly specify the number of constraints:
/// let areas = layout.areas::<2>(area);
/// ```
pub fn areas<const N: usize>(&self, area: Rect) -> [Rect; N] {
let (areas, _) = self.split_with_spacers(area);
areas.as_ref().try_into().expect("invalid number of rects")
let areas = self.split(area);
areas.as_ref().try_into().unwrap_or_else(|_| {
panic!(
"invalid number of rects: expected {N}, found {}",
areas.len()
)
})
}
/// Split the rect into a number of sub-rects according to the given [`Layout`].
///
/// An ergonomic wrapper around [`Layout::split`] that returns an array of `Rect`s instead of
/// `Rc<[Rect]>`.
///
/// This method requires the number of constraints to be known at compile time. If you don't
/// know the number of constraints at compile time, use [`Layout::split`] instead.
///
/// # Errors
///
/// Returns an error if the number of constraints is not equal to the length of the returned
/// array.
///
/// # Examples
///
/// ```rust
/// use ratatui_core::layout::{Constraint, Layout, Rect};
///
/// let area = Rect::new(0, 0, 10, 10);
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
/// let [top, main] = layout.try_areas(area)?;
///
/// // or explicitly specify the number of constraints:
/// let areas = layout.try_areas::<2>(area)?;
/// # Ok::<(), core::array::TryFromSliceError>(())
/// ```
pub fn try_areas<const N: usize>(&self, area: Rect) -> Result<[Rect; N], TryFromSliceError> {
self.split(area).as_ref().try_into()
}
/// Split the rect into a number of sub-rects according to the given [`Layout`] and return just
@@ -557,7 +611,7 @@ impl Layout {
/// use ratatui_core::layout::{Constraint, Layout, Rect};
///
/// let area = Rect::new(0, 0, 10, 10);
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]);
/// let [top, main] = layout.areas(area);
/// let [before, inbetween, after] = layout.spacers(area);
///
@@ -572,7 +626,7 @@ impl Layout {
.expect("invalid number of rects")
}
/// Wrapper function around the cassowary solver to be able to split a given area into
/// Wrapper function around the [`kasuari`] solver to be able to split a given area into
/// smaller ones based on the preferred widths or heights and the direction.
///
/// Note that the constraints are applied to the whole area that is to be split, so using
@@ -596,7 +650,7 @@ impl Layout {
/// use ratatui_core::layout::{Constraint, Direction, Layout, Rect};
/// let layout = Layout::default()
/// .direction(Direction::Vertical)
/// .constraints([Constraint::Length(5), Constraint::Min(0)])
/// .constraints([Constraint::Length(5), Constraint::Fill(1)])
/// .split(Rect::new(2, 2, 10, 10));
/// assert_eq!(layout[..], [Rect::new(2, 2, 10, 5), Rect::new(2, 7, 10, 5)]);
///
@@ -610,7 +664,7 @@ impl Layout {
self.split_with_spacers(area).0
}
/// Wrapper function around the cassowary solver that splits the given area into smaller ones
/// Wrapper function around the [`kasuari`] solver that splits the given area into smaller ones
/// based on the preferred widths or heights and the direction, with the ability to include
/// spacers between the areas.
///
@@ -629,7 +683,7 @@ impl Layout {
///
/// let (areas, spacers) = Layout::default()
/// .direction(Direction::Vertical)
/// .constraints([Constraint::Length(5), Constraint::Min(0)])
/// .constraints([Constraint::Length(5), Constraint::Fill(1)])
/// .split_with_spacers(Rect::new(2, 2, 10, 10));
/// assert_eq!(areas[..], [Rect::new(2, 2, 10, 5), Rect::new(2, 7, 10, 5)]);
/// assert_eq!(
@@ -672,7 +726,7 @@ impl Layout {
}
fn try_split(&self, area: Rect) -> Result<(Segments, Spacers), AddConstraintError> {
// To take advantage of all of cassowary features, we would want to store the `Solver` in
// To take advantage of all of [`kasuari`] features, we would want to store the `Solver` in
// one of the fields of the Layout struct. And we would want to set it up such that we could
// add or remove constraints as and when needed.
// The advantage of doing it as described above is that it would allow users to
@@ -881,9 +935,48 @@ fn configure_flex_constraints(
solver.add_constraint(last.is_empty())?;
}
}
// all spacers are the same size and will grow to fill any remaining space after the
// constraints are satisfied
// All spacers excluding first and last are the same size and will grow to fill
// any remaining space after the constraints are satisfied.
// All spacers excluding first and last are also twice the size of the first and last
// spacers
Flex::SpaceAround => {
if spacers.len() <= 2 {
// If there are two or less spacers, fallback to Flex::SpaceEvenly
for (left, right) in spacers.iter().tuple_combinations() {
solver.add_constraint(left.has_size(right, SPACER_SIZE_EQ))?;
}
for spacer in spacers {
solver.add_constraint(spacer.has_min_size(spacing, SPACER_SIZE_EQ))?;
solver.add_constraint(spacer.has_size(area, SPACE_GROW))?;
}
} else {
// Separate the first and last spacer from the middle ones
let (first, rest) = spacers.split_first().unwrap();
let (last, middle) = rest.split_last().unwrap();
// All middle spacers should be equal in size
for (left, right) in middle.iter().tuple_combinations() {
solver.add_constraint(left.has_size(right, SPACER_SIZE_EQ))?;
}
// First and last spacers should be half the size of any middle spacer
if let Some(first_middle) = middle.first() {
solver.add_constraint(first_middle.has_double_size(first, SPACER_SIZE_EQ))?;
solver.add_constraint(first_middle.has_double_size(last, SPACER_SIZE_EQ))?;
}
// Apply minimum size and growth constraints
for spacer in spacers {
solver.add_constraint(spacer.has_min_size(spacing, SPACER_SIZE_EQ))?;
solver.add_constraint(spacer.has_size(area, SPACE_GROW))?;
}
}
}
// All spacers are the same size and will grow to fill any remaining space after the
// constraints are satisfied
Flex::SpaceEvenly => {
for (left, right) in spacers.iter().tuple_combinations() {
solver.add_constraint(left.has_size(right, SPACER_SIZE_EQ))?;
}
@@ -893,8 +986,9 @@ fn configure_flex_constraints(
}
}
// all spacers are the same size and will grow to fill any remaining space after the
// constraints are satisfied, but the first and last spacers are zero size
// All spacers excluding first and last are the same size and will grow to fill
// any remaining space after the constraints are satisfied.
// The first and last spacers are zero size.
Flex::SpaceBetween => {
for (left, right) in spacers_except_first_and_last.iter().tuple_combinations() {
solver.add_constraint(left.has_size(right.size(), SPACER_SIZE_EQ))?;
@@ -908,6 +1002,7 @@ fn configure_flex_constraints(
solver.add_constraint(last.is_empty())?;
}
}
Flex::Start => {
for spacer in spacers_except_first_and_last {
solver.add_constraint(spacer.has_size(spacing_f64, SPACER_SIZE_EQ))?;
@@ -948,7 +1043,7 @@ fn configure_flex_constraints(
/// │abcdef││abcdef│
/// └──────┘└──────┘
///
/// [Min(0), Fill(2)]
/// [Fill(1), Fill(2)]
/// ┌──────┐┌────────────┐
/// │abcdef││abcdefabcdef│
/// └──────┘└────────────┘
@@ -1092,6 +1187,14 @@ impl Element {
self.size() | EQ(strength) | size.into()
}
fn has_double_size<E: Into<Expression>>(
&self,
size: E,
strength: Strength,
) -> kasuari::Constraint {
self.size() | EQ(strength) | (size.into() * 2.0)
}
fn is_empty(&self) -> kasuari::Constraint {
self.size() | EQ(Strength::REQUIRED - Strength::WEAK) | 0.0
}
@@ -2169,6 +2272,7 @@ mod tests {
Flex::End,
Flex::Center,
Flex::SpaceAround,
Flex::SpaceEvenly,
Flex::SpaceBetween,
] {
let widths = Layout::horizontal(&constraints)
@@ -2306,26 +2410,37 @@ mod tests {
#[case::length_center2(vec![Length(25), Length(25)], vec![25..50, 50..75], Flex::Center)]
#[case::length_end2(vec![Length(25), Length(25)], vec![50..75, 75..100], Flex::End)]
#[case::length_spacebetween(vec![Length(25), Length(25)], vec![0..25, 75..100], Flex::SpaceBetween)]
#[case::length_spacearound(vec![Length(25), Length(25)], vec![17..42, 58..83], Flex::SpaceAround)]
#[case::length_spaceevenly(vec![Length(25), Length(25)], vec![17..42, 58..83], Flex::SpaceEvenly)]
#[case::length_spacearound(vec![Length(25), Length(25)], vec![13..38, 63..88], Flex::SpaceAround)]
#[case::percentage_legacy(vec![Percentage(25), Percentage(25)], vec![0..25, 25..100], Flex::Legacy)]
#[case::percentage_start(vec![Percentage(25), Percentage(25)], vec![0..25, 25..50], Flex::Start)]
#[case::percentage_center(vec![Percentage(25), Percentage(25)], vec![25..50, 50..75], Flex::Center)]
#[case::percentage_end(vec![Percentage(25), Percentage(25)], vec![50..75, 75..100], Flex::End)]
#[case::percentage_spacebetween(vec![Percentage(25), Percentage(25)], vec![0..25, 75..100], Flex::SpaceBetween)]
#[case::percentage_spacearound(vec![Percentage(25), Percentage(25)], vec![17..42, 58..83], Flex::SpaceAround)]
#[case::percentage_spaceevenly(vec![Percentage(25), Percentage(25)], vec![17..42, 58..83], Flex::SpaceEvenly)]
#[case::percentage_spacearound(vec![Percentage(25), Percentage(25)], vec![13..38, 63..88], Flex::SpaceAround)]
#[case::min_legacy2(vec![Min(25), Min(25)], vec![0..25, 25..100], Flex::Legacy)]
#[case::min_start2(vec![Min(25), Min(25)], vec![0..50, 50..100], Flex::Start)]
#[case::min_center2(vec![Min(25), Min(25)], vec![0..50, 50..100], Flex::Center)]
#[case::min_end2(vec![Min(25), Min(25)], vec![0..50, 50..100], Flex::End)]
#[case::min_spacebetween(vec![Min(25), Min(25)], vec![0..50, 50..100], Flex::SpaceBetween)]
#[case::min_spaceevenly(vec![Min(25), Min(25)], vec![0..50, 50..100], Flex::SpaceEvenly)]
#[case::min_spacearound(vec![Min(25), Min(25)], vec![0..50, 50..100], Flex::SpaceAround)]
#[case::max_legacy2(vec![Max(25), Max(25)], vec![0..25, 25..100], Flex::Legacy)]
#[case::max_start2(vec![Max(25), Max(25)], vec![0..25, 25..50], Flex::Start)]
#[case::max_center2(vec![Max(25), Max(25)], vec![25..50, 50..75], Flex::Center)]
#[case::max_end2(vec![Max(25), Max(25)], vec![50..75, 75..100], Flex::End)]
#[case::max_spacebetween(vec![Max(25), Max(25)], vec![0..25, 75..100], Flex::SpaceBetween)]
#[case::max_spacearound(vec![Max(25), Max(25)], vec![17..42, 58..83], Flex::SpaceAround)]
#[case::max_spaceevenly(vec![Max(25), Max(25)], vec![17..42, 58..83], Flex::SpaceEvenly)]
#[case::max_spacearound(vec![Max(25), Max(25)], vec![13..38, 63..88], Flex::SpaceAround)]
#[case::length_spaced_around(vec![Length(25), Length(25), Length(25)], vec![0..25, 38..63, 75..100], Flex::SpaceBetween)]
#[case::one_segment_legacy(vec![Length(50)], vec![0..100], Flex::Legacy)]
#[case::one_segment_start(vec![Length(50)], vec![0..50], Flex::Start)]
#[case::one_segment_end(vec![Length(50)], vec![50..100], Flex::End)]
#[case::one_segment_center(vec![Length(50)], vec![25..75], Flex::Center)]
#[case::one_segment_spacebetween(vec![Length(50)], vec![0..100], Flex::SpaceBetween)]
#[case::one_segment_spaceevenly(vec![Length(50)], vec![25..75], Flex::SpaceEvenly)]
#[case::one_segment_spacearound(vec![Length(50)], vec![25..75], Flex::SpaceAround)]
fn flex_constraint(
#[case] constraints: Vec<Constraint>,
#[case] expected: Vec<Range<u16>>,
@@ -2348,7 +2463,8 @@ mod tests {
#[case::length_overlap4(vec![(42 , 20) , (61 , 20) , (80 , 20)] , vec![Length(20) , Length(20) , Length(20)] , Flex::End , -1)]
#[case::length_overlap5(vec![(0 , 20) , (19 , 20) , (38 , 62)] , vec![Length(20) , Length(20) , Length(20)] , Flex::Legacy , -1)]
#[case::length_overlap6(vec![(0 , 20) , (40 , 20) , (80 , 20)] , vec![Length(20) , Length(20) , Length(20)] , Flex::SpaceBetween , -1)]
#[case::length_overlap7(vec![(10 , 20) , (40 , 20) , (70 , 20)] , vec![Length(20) , Length(20) , Length(20)] , Flex::SpaceAround , -1)]
#[case::length_overlap7(vec![(10 , 20) , (40 , 20) , (70 , 20)] , vec![Length(20) , Length(20) , Length(20)] , Flex::SpaceEvenly , -1)]
#[case::length_overlap7(vec![(7 , 20) , (40 , 20) , (73 , 20)] , vec![Length(20) , Length(20) , Length(20)] , Flex::SpaceAround , -1)]
fn flex_overlap(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
@@ -2375,7 +2491,8 @@ mod tests {
#[case::length_spacing(vec![(36, 20), (58, 20) , (80, 20)], vec![Length(20), Length(20), Length(20)], Flex::End , 2)]
#[case::length_spacing(vec![(0 , 20), (22, 20) , (44, 56)], vec![Length(20), Length(20), Length(20)], Flex::Legacy , 2)]
#[case::length_spacing(vec![(0 , 20), (40, 20) , (80, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceBetween, 2)]
#[case::length_spacing(vec![(10, 20), (40, 20) , (70, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceAround, 2)]
#[case::length_spacing(vec![(10, 20), (40, 20) , (70, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceEvenly, 2)]
#[case::length_spacing(vec![(7, 20), (40, 20) , (73, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceAround, 2)]
fn flex_spacing(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
@@ -2432,7 +2549,8 @@ mod tests {
#[case::c(vec![(36, 20), (58, 20), (80, 20)], vec![Length(20), Length(20), Length(20)], Flex::End, 2)]
#[case::d(vec![(0, 20), (22, 20), (44, 56)], vec![Length(20), Length(20), Length(20)], Flex::Legacy, 2)]
#[case::e(vec![(0, 20), (22, 20), (44, 56)], vec![Length(20), Length(20), Length(20)], Flex::Legacy, 2)]
#[case::f(vec![(10, 20), (40, 20), (70, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceAround, 2)]
#[case::f(vec![(10, 20), (40, 20), (70, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceEvenly, 2)]
#[case::f(vec![(7, 20), (40, 20), (73, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceAround, 2)]
fn constraint_specification_tests_for_priority_with_spacing(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
@@ -2454,13 +2572,14 @@ mod tests {
#[case::prop(vec![(0 , 10), (10, 80), (90 , 10)] , vec![Length(10), Fill(1), Length(10)], Flex::Legacy)]
#[case::flex(vec![(0 , 10), (90 , 10)] , vec![Length(10), Length(10)], Flex::SpaceBetween)]
#[case::prop(vec![(0 , 27), (27, 10), (37, 26), (63, 10), (73, 27)] , vec![Fill(1), Length(10), Fill(1), Length(10), Fill(1)], Flex::Legacy)]
#[case::flex(vec![(27 , 10), (63, 10)] , vec![Length(10), Length(10)], Flex::SpaceAround)]
#[case::flex(vec![(27 , 10), (63, 10)] , vec![Length(10), Length(10)], Flex::SpaceEvenly)]
#[case::prop(vec![(0 , 10), (10, 10), (20 , 80)] , vec![Length(10), Length(10), Fill(1)], Flex::Legacy)]
#[case::flex(vec![(0 , 10), (10, 10)] , vec![Length(10), Length(10)], Flex::Start)]
#[case::prop(vec![(0 , 80), (80 , 10), (90, 10)] , vec![Fill(1), Length(10), Length(10)], Flex::Legacy)]
#[case::flex(vec![(80 , 10), (90, 10)] , vec![Length(10), Length(10)], Flex::End)]
#[case::prop(vec![(0 , 40), (40, 10), (50, 10), (60, 40)] , vec![Fill(1), Length(10), Length(10), Fill(1)], Flex::Legacy)]
#[case::flex(vec![(40 , 10), (50, 10)] , vec![Length(10), Length(10)], Flex::Center)]
#[case::flex(vec![(20 , 10), (70, 10)] , vec![Length(10), Length(10)], Flex::SpaceAround)]
fn fill_vs_flex(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
@@ -2477,8 +2596,9 @@ mod tests {
#[rstest]
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Legacy , 0)]
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceAround , 0)]
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceEvenly , 0)]
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceBetween , 0)]
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceAround , 0)]
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Start , 0)]
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Center , 0)]
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::End , 0)]
@@ -2486,11 +2606,13 @@ mod tests {
#[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::Start , 10)]
#[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::Center , 10)]
#[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::End , 10)]
#[case::flex10(vec![(10 , 35), (55 , 35)] , vec![Fill(1), Fill(1)], Flex::SpaceAround , 10)]
#[case::flex10(vec![(10 , 35), (55 , 35)] , vec![Fill(1), Fill(1)], Flex::SpaceEvenly , 10)]
#[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::SpaceBetween , 10)]
#[case::flex10(vec![(10 , 30), (60 , 30)] , vec![Fill(1), Fill(1)], Flex::SpaceAround , 10)]
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::Legacy , 0)]
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceAround , 0)]
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceEvenly , 0)]
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceBetween , 0)]
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceAround , 0)]
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::Start , 0)]
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::Center , 0)]
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::End , 0)]
@@ -2498,8 +2620,9 @@ mod tests {
#[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::Start , 10)]
#[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::Center , 10)]
#[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::End , 10)]
#[case::flex_length10(vec![(10 , 25), (45, 10), (65 , 25)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceAround , 10)]
#[case::flex_length10(vec![(10 , 25), (45, 10), (65 , 25)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceEvenly , 10)]
#[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceBetween , 10)]
#[case::flex_length10(vec![(10 , 15), (45, 10), (75 , 15)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceAround , 10)]
fn fill_spacing(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
@@ -2525,24 +2648,28 @@ mod tests {
#[case::flex0_4(vec![(0 , 55), (45 , 55)] , vec![Fill(1), Fill(1)], Flex::Start , -10)]
#[case::flex0_5(vec![(0 , 55), (45 , 55)] , vec![Fill(1), Fill(1)], Flex::Center , -10)]
#[case::flex0_6(vec![(0 , 55), (45 , 55)] , vec![Fill(1), Fill(1)], Flex::End , -10)]
#[case::flex0_7(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceEvenly , -10)]
#[case::flex10_1(vec![(0 , 51), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Legacy , -1)]
#[case::flex10_2(vec![(0 , 51), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Start , -1)]
#[case::flex10_3(vec![(0 , 51), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Center , -1)]
#[case::flex10_4(vec![(0 , 51), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::End , -1)]
#[case::flex10_5(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceAround , -1)]
#[case::flex10_6(vec![(0 , 51), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceBetween , -1)]
#[case::flex10_7(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceEvenly , -1)]
#[case::flex_length0_1(vec![(0 , 55), (45, 10), (45 , 55)] , vec![Fill(1), Length(10), Fill(1)], Flex::Legacy , -10)]
#[case::flex_length0_2(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceAround , -10)]
#[case::flex_length0_3(vec![(0 , 55), (45, 10), (45 , 55)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceBetween , -10)]
#[case::flex_length0_4(vec![(0 , 55), (45, 10), (45 , 55)] , vec![Fill(1), Length(10), Fill(1)], Flex::Start , -10)]
#[case::flex_length0_5(vec![(0 , 55), (45, 10), (45 , 55)] , vec![Fill(1), Length(10), Fill(1)], Flex::Center , -10)]
#[case::flex_length0_6(vec![(0 , 55), (45, 10), (45 , 55)] , vec![Fill(1), Length(10), Fill(1)], Flex::End , -10)]
#[case::flex_length0_7(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceEvenly , -10)]
#[case::flex_length10_1(vec![(0 , 46), (45, 10), (54 , 46)] , vec![Fill(1), Length(10), Fill(1)], Flex::Legacy , -1)]
#[case::flex_length10_2(vec![(0 , 46), (45, 10), (54 , 46)] , vec![Fill(1), Length(10), Fill(1)], Flex::Start , -1)]
#[case::flex_length10_3(vec![(0 , 46), (45, 10), (54 , 46)] , vec![Fill(1), Length(10), Fill(1)], Flex::Center , -1)]
#[case::flex_length10_4(vec![(0 , 46), (45, 10), (54 , 46)] , vec![Fill(1), Length(10), Fill(1)], Flex::End , -1)]
#[case::flex_length10_5(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceAround , -1)]
#[case::flex_length10_6(vec![(0 , 46), (45, 10), (54 , 46)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceBetween , -1)]
#[case::flex_length10_7(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceEvenly , -1)]
fn fill_overlap(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
@@ -2584,7 +2711,8 @@ mod tests {
#[rstest]
#[case::spacers(vec![(0, 0), (10, 0), (100, 0)], vec![Length(10), Length(10)], Flex::Legacy)]
#[case::spacers(vec![(0, 0), (10, 80), (100, 0)], vec![Length(10), Length(10)], Flex::SpaceBetween)]
#[case::spacers(vec![(0, 27), (37, 26), (73, 27)], vec![Length(10), Length(10)], Flex::SpaceAround)]
#[case::spacers(vec![(0, 27), (37, 26), (73, 27)], vec![Length(10), Length(10)], Flex::SpaceEvenly)]
#[case::spacers(vec![(0, 20), (30, 40), (80, 20)], vec![Length(10), Length(10)], Flex::SpaceAround)]
#[case::spacers(vec![(0, 0), (10, 0), (20, 80)], vec![Length(10), Length(10)], Flex::Start)]
#[case::spacers(vec![(0, 40), (50, 0), (60, 40)], vec![Length(10), Length(10)], Flex::Center)]
#[case::spacers(vec![(0, 80), (90, 0), (100, 0)], vec![Length(10), Length(10)], Flex::End)]
@@ -2608,7 +2736,8 @@ mod tests {
#[rstest]
#[case::spacers(vec![(0, 0), (10, 5), (100, 0)], vec![Length(10), Length(10)], Flex::Legacy, 5)]
#[case::spacers(vec![(0, 0), (10, 80), (100, 0)], vec![Length(10), Length(10)], Flex::SpaceBetween, 5)]
#[case::spacers(vec![(0, 27), (37, 26), (73, 27)], vec![Length(10), Length(10)], Flex::SpaceAround, 5)]
#[case::spacers(vec![(0, 27), (37, 26), (73, 27)], vec![Length(10), Length(10)], Flex::SpaceEvenly, 5)]
#[case::spacers(vec![(0, 20), (30, 40), (80, 20)], vec![Length(10), Length(10)], Flex::SpaceAround, 5)]
#[case::spacers(vec![(0, 0), (10, 5), (25, 75)], vec![Length(10), Length(10)], Flex::Start, 5)]
#[case::spacers(vec![(0, 38), (48, 5), (63, 37)], vec![Length(10), Length(10)], Flex::Center, 5)]
#[case::spacers(vec![(0, 75), (85, 5), (100, 0)], vec![Length(10), Length(10)], Flex::End, 5)]
@@ -2634,7 +2763,8 @@ mod tests {
#[rstest]
#[case::spacers_1(vec![(0, 0), (10, 0), (100, 0)], vec![Length(10), Length(10)], Flex::Legacy, -1)]
#[case::spacers_2(vec![(0, 0), (10, 80), (100, 0)], vec![Length(10), Length(10)], Flex::SpaceBetween, -1)]
#[case::spacers_3(vec![(0, 27), (37, 26), (73, 27)], vec![Length(10), Length(10)], Flex::SpaceAround, -1)]
#[case::spacers_3(vec![(0, 27), (37, 26), (73, 27)], vec![Length(10), Length(10)], Flex::SpaceEvenly, -1)]
#[case::spacers_3(vec![(0, 20), (30, 40), (80, 20)], vec![Length(10), Length(10)], Flex::SpaceAround, -1)]
#[case::spacers_4(vec![(0, 0), (10, 0), (19, 81)], vec![Length(10), Length(10)], Flex::Start, -1)]
#[case::spacers_5(vec![(0, 41), (51, 0), (60, 40)], vec![Length(10), Length(10)], Flex::Center, -1)]
#[case::spacers_6(vec![(0, 81), (91, 0), (100, 0)], vec![Length(10), Length(10)], Flex::End, -1)]
@@ -2660,7 +2790,8 @@ mod tests {
#[rstest]
#[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::Legacy, 200)]
#[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::SpaceBetween, 200)]
#[case::spacers(vec![(0, 33), (33, 34), (67, 33)], vec![Length(10), Length(10)], Flex::SpaceAround, 200)]
#[case::spacers(vec![(0, 33), (33, 34), (67, 33)], vec![Length(10), Length(10)], Flex::SpaceEvenly, 200)]
#[case::spacers(vec![(0, 25), (25, 50), (75, 25)], vec![Length(10), Length(10)], Flex::SpaceAround, 200)]
#[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::Start, 200)]
#[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::Center, 200)]
#[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::End, 200)]
@@ -2703,5 +2834,11 @@ mod tests {
.collect::<Vec<(u16, u16)>>();
assert_eq!(result, expected);
}
#[rstest]
#[timeout(core::time::Duration::from_secs(10))]
fn regression_1855() {
Layout::horizontal(vec![Min(0); 40]).split(Rect::new(0, 0, 40, 1));
}
}
}

View File

@@ -1,5 +1,38 @@
use core::fmt;
/// Represents spacing around rectangular areas.
///
/// `Margin` defines the horizontal and vertical spacing that should be applied around a rectangular
/// area. It's commonly used with [`Layout`](crate::layout::Layout) to add space between the
/// layout's boundaries and its contents, or with [`Rect::inner`](crate::layout::Rect::inner) and
/// [`Rect::outer`](crate::layout::Rect::outer) to create padded areas.
///
/// The margin values represent the number of character cells to add on each side. For horizontal
/// margin, the space is applied to both the left and right sides. For vertical margin, the space
/// is applied to both the top and bottom sides.
///
/// # Construction
///
/// - [`new`](Self::new) - Create a new margin with horizontal and vertical spacing
/// - [`default`](Default::default) - Create with zero margin
///
/// # Examples
///
/// ```rust
/// use ratatui_core::layout::{Constraint, Layout, Margin, Rect};
///
/// // Create a margin of 2 cells horizontally and 1 cell vertically
/// let margin = Margin::new(2, 1);
///
/// // Apply directly to a rectangle
/// let area = Rect::new(0, 0, 80, 24);
/// let inner_area = area.inner(margin);
///
/// // Or use with a layout (which only accepts uniform margins)
/// let layout = Layout::vertical([Constraint::Fill(1)]).margin(2);
/// ```
///
/// For comprehensive layout documentation and examples, see the [`layout`](crate::layout) module.
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Margin {

View File

@@ -3,12 +3,26 @@ use core::fmt;
use crate::layout::Rect;
/// Position in the terminal
/// Position in the terminal coordinate system.
///
/// The position is relative to the top left corner of the terminal window, with the top left corner
/// being (0, 0). The x axis is horizontal increasing to the right, and the y axis is vertical
/// increasing downwards.
///
/// `Position` is used throughout the layout system to represent specific points in the terminal.
/// It can be created from coordinates, tuples, or extracted from rectangular areas.
///
/// # Construction
///
/// - [`new`](Self::new) - Create a new position from x and y coordinates
/// - [`default`](Default::default) - Create at origin (0, 0)
///
/// # Conversion
///
/// - [`from((u16, u16))`](Self::from) - Create from `(u16, u16)` tuple
/// - [`from(Rect)`](Self::from) - Create from [`Rect`] (uses top-left corner)
/// - [`into((u16, u16))`] - Convert to `(u16, u16)` tuple
///
/// # Examples
///
/// ```
@@ -23,6 +37,8 @@ use crate::layout::Rect;
/// // position can be converted back into the components when needed
/// let (x, y) = position.into();
/// ```
///
/// For comprehensive layout documentation and examples, see the [`layout`](crate::layout) module.
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Position {

View File

@@ -1,4 +1,5 @@
#![warn(missing_docs)]
use core::array::TryFromSliceError;
use core::cmp::{max, min};
use core::fmt;
@@ -9,10 +10,77 @@ pub use iter::*;
use super::{Constraint, Flex, Layout};
/// A Rectangular area.
/// A rectangular area in the terminal.
///
/// A simple rectangle used in the computation of the layout and to give widgets a hint about the
/// area they are supposed to render to.
/// A `Rect` represents a rectangular region in the terminal coordinate system, defined by its
/// top-left corner position and dimensions. This is the fundamental building block for all layout
/// operations and widget rendering in Ratatui.
///
/// Rectangles are used throughout the layout system to define areas where widgets can be rendered.
/// They are typically created by [`Layout`] operations that divide terminal space, but can also be
/// manually constructed for specific positioning needs.
///
/// The coordinate system uses the top-left corner as the origin (0, 0), with x increasing to the
/// right and y increasing downward. All measurements are in character cells.
///
/// # Construction and Conversion
///
/// - [`new`](Self::new) - Create a new rectangle from coordinates and dimensions
/// - [`as_position`](Self::as_position) - Convert to a position at the top-left corner
/// - [`as_size`](Self::as_size) - Convert to a size representing the dimensions
/// - [`from((Position, Size))`](Self::from) - Create from `(Position, Size)` tuple
/// - [`from(((u16, u16), (u16, u16)))`](Self::from) - Create from `((u16, u16), (u16, u16))`
/// coordinate and dimension tuples
/// - [`into((Position, Size))`] - Convert to `(Position, Size)` tuple
/// - [`default`](Self::default) - Create a zero-sized rectangle at origin
///
/// # Geometry and Properties
///
/// - [`area`](Self::area) - Calculate the total area in character cells
/// - [`is_empty`](Self::is_empty) - Check if the rectangle has zero area
/// - [`left`](Self::left), [`right`](Self::right), [`top`](Self::top), [`bottom`](Self::bottom) -
/// Get edge coordinates
///
/// # Spatial Operations
///
/// - [`inner`](Self::inner), [`outer`](Self::outer) - Apply margins to shrink or expand
/// - [`offset`](Self::offset) - Move the rectangle by a relative amount
/// - [`union`](Self::union) - Combine with another rectangle to create a bounding box
/// - [`intersection`](Self::intersection) - Find the overlapping area with another rectangle
/// - [`clamp`](Self::clamp) - Constrain the rectangle to fit within another
///
/// # Positioning and Centering
///
/// - [`centered_horizontally`](Self::centered_horizontally) - Center horizontally within a
/// constraint
/// - [`centered_vertically`](Self::centered_vertically) - Center vertically within a constraint
/// - [`centered`](Self::centered) - Center both horizontally and vertically
///
/// # Testing and Iteration
///
/// - [`contains`](Self::contains) - Check if a position is within the rectangle
/// - [`intersects`](Self::intersects) - Check if it overlaps with another rectangle
/// - [`rows`](Self::rows) - Iterate over horizontal rows within the rectangle
/// - [`columns`](Self::columns) - Iterate over vertical columns within the rectangle
/// - [`positions`](Self::positions) - Iterate over all positions within the rectangle
///
/// # Examples
///
/// ```rust
/// use ratatui_core::layout::{Position, Rect, Size};
///
/// // Create a rectangle manually
/// let rect = Rect::new(10, 5, 80, 20);
/// assert_eq!(rect.x, 10);
/// assert_eq!(rect.y, 5);
/// assert_eq!(rect.width, 80);
/// assert_eq!(rect.height, 20);
///
/// // Create from position and size
/// let rect = Rect::from((Position::new(10, 5), Size::new(80, 20)));
/// ```
///
/// For comprehensive layout documentation and examples, see the [`layout`](crate::layout) module.
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Rect {
@@ -155,6 +223,34 @@ impl Rect {
}
}
/// Returns a new `Rect` outside the current one, with the given margin applied on each side.
///
/// If the margin causes the `Rect`'s bounds to outsdie the range of a `u16`, the `Rect` will
/// be truncated to keep the bounds within `u16`. This will cause the size of the `Rect` to
/// change.
///
/// The generated `Rect` may not fit inside the buffer or containing area, so it consider
/// constraining the resulting `Rect` with [`Rect::clamp`] before using it.
#[must_use = "method returns the modified value"]
pub const fn outer(self, margin: Margin) -> Self {
let x = self.x.saturating_sub(margin.horizontal);
let y = self.y.saturating_sub(margin.vertical);
let width = self
.right()
.saturating_add(margin.horizontal)
.saturating_sub(x);
let height = self
.bottom()
.saturating_add(margin.vertical)
.saturating_sub(y);
Self {
x,
y,
width,
height,
}
}
/// Moves the `Rect` without modifying its size.
///
/// Moves the `Rect` according to the given offset without modifying its [`width`](Rect::width)
@@ -270,17 +366,31 @@ impl Rect {
/// An iterator over rows within the `Rect`.
///
/// Each row is a full `Rect` region with height 1 that can be used for rendering widgets
/// or as input to further layout methods.
///
/// # Example
///
/// ```
/// use ratatui_core::buffer::Buffer;
/// use ratatui_core::layout::Rect;
/// use ratatui_core::text::Line;
/// use ratatui_core::layout::{Constraint, Layout, Rect};
/// use ratatui_core::widgets::Widget;
///
/// fn render(area: Rect, buf: &mut Buffer) {
/// for row in area.rows() {
/// Line::raw("Hello, world!").render(row, buf);
/// fn render_list(area: Rect, buf: &mut Buffer) {
/// // Renders "Item 0", "Item 1", etc. in each row
/// for (i, row) in area.rows().enumerate() {
/// format!("Item {i}").render(row, buf);
/// }
/// }
///
/// fn render_with_nested_layout(area: Rect, buf: &mut Buffer) {
/// // Splits each row into left/right areas and renders labels and content
/// for (i, row) in area.rows().take(3).enumerate() {
/// let [left, right] =
/// Layout::horizontal([Constraint::Percentage(30), Constraint::Fill(1)]).areas(row);
///
/// format!("{i}:").render(left, buf);
/// "Content".render(right, buf);
/// }
/// }
/// ```
@@ -290,17 +400,20 @@ impl Rect {
/// An iterator over columns within the `Rect`.
///
/// Each column is a full `Rect` region with width 1 that can be used for rendering widgets
/// or as input to further layout methods.
///
/// # Example
///
/// ```
/// use ratatui_core::buffer::Buffer;
/// use ratatui_core::layout::Rect;
/// use ratatui_core::text::Text;
/// use ratatui_core::widgets::Widget;
///
/// fn render(area: Rect, buf: &mut Buffer) {
/// fn render_columns(area: Rect, buf: &mut Buffer) {
/// // Renders column indices (0-9 repeating) in each column
/// for (i, column) in area.columns().enumerate() {
/// Text::from(format!("{}", i)).render(column, buf);
/// format!("{}", i % 10).render(column, buf);
/// }
/// }
/// ```
@@ -311,16 +424,19 @@ impl Rect {
/// An iterator over the positions within the `Rect`.
///
/// The positions are returned in a row-major order (left-to-right, top-to-bottom).
/// Each position is a `Position` that represents a single cell coordinate.
///
/// # Example
///
/// ```
/// use ratatui_core::buffer::Buffer;
/// use ratatui_core::layout::Rect;
/// use ratatui_core::layout::{Position, Rect};
/// use ratatui_core::widgets::Widget;
///
/// fn render(area: Rect, buf: &mut Buffer) {
/// for position in area.positions() {
/// buf[(position.x, position.y)].set_symbol("x");
/// fn render_positions(area: Rect, buf: &mut Buffer) {
/// // Renders position indices (0-9 repeating) at each cell position
/// for (i, position) in area.positions().enumerate() {
/// buf[position].set_symbol(&format!("{}", i % 10));
/// }
/// }
/// ```
@@ -367,9 +483,7 @@ impl Rect {
/// ```
#[must_use]
pub fn centered_horizontally(self, constraint: Constraint) -> Self {
let [area] = Layout::horizontal([constraint])
.flex(Flex::Center)
.areas(self);
let [area] = self.layout(&Layout::horizontal([constraint]).flex(Flex::Center));
area
}
@@ -387,9 +501,7 @@ impl Rect {
/// ```
#[must_use]
pub fn centered_vertically(self, constraint: Constraint) -> Self {
let [area] = Layout::vertical([constraint])
.flex(Flex::Center)
.areas(self);
let [area] = self.layout(&Layout::vertical([constraint]).flex(Flex::Center));
area
}
@@ -417,6 +529,99 @@ impl Rect {
.centered_vertically(vertical_constraint)
}
/// Split the rect into a number of sub-rects according to the given [`Layout`].
///
/// An ergonomic wrapper around [`Layout::split`] that returns an array of `Rect`s instead of
/// `Rc<[Rect]>`.
///
/// This method requires the number of constraints to be known at compile time. If you don't
/// know the number of constraints at compile time, use [`Layout::split`] instead.
///
/// # Panics
///
/// Panics if the number of constraints is not equal to the length of the returned array.
///
/// # Examples
///
/// ```
/// use ratatui_core::layout::{Constraint, Layout, Rect};
///
/// let area = Rect::new(0, 0, 10, 10);
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
/// let [top, main] = area.layout(&layout);
/// assert_eq!(top, Rect::new(0, 0, 10, 1));
/// assert_eq!(main, Rect::new(0, 1, 10, 9));
///
/// // or explicitly specify the number of constraints:
/// let areas = area.layout::<2>(&layout);
/// assert_eq!(areas, [Rect::new(0, 0, 10, 1), Rect::new(0, 1, 10, 9),]);
/// ```
#[must_use]
pub fn layout<const N: usize>(self, layout: &Layout) -> [Self; N] {
let areas = layout.split(self);
areas.as_ref().try_into().unwrap_or_else(|_| {
panic!(
"invalid number of rects: expected {N}, found {}",
areas.len()
)
})
}
/// Split the rect into a number of sub-rects according to the given [`Layout`].
///
/// An ergonomic wrapper around [`Layout::split`] that returns a [`Vec`] of `Rect`s instead of
/// `Rc<[Rect]>`.
///
/// # Examples
///
/// ```
/// use ratatui_core::layout::{Constraint, Layout, Rect};
///
/// let area = Rect::new(0, 0, 10, 10);
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
/// let areas = area.layout_vec(&layout);
/// assert_eq!(areas, vec![Rect::new(0, 0, 10, 1), Rect::new(0, 1, 10, 9),]);
/// ```
///
/// [`Vec`]: alloc::vec::Vec
#[must_use]
pub fn layout_vec(self, layout: &Layout) -> alloc::vec::Vec<Self> {
layout.split(self).as_ref().to_vec()
}
/// Try to split the rect into a number of sub-rects according to the given [`Layout`].
///
/// An ergonomic wrapper around [`Layout::split`] that returns an array of `Rect`s instead of
/// `Rc<[Rect]>`.
///
/// # Errors
///
/// Returns an error if the number of constraints is not equal to the length of the returned
/// array.
///
/// # Examples
///
/// ```
/// use ratatui_core::layout::{Constraint, Layout, Rect};
///
/// let area = Rect::new(0, 0, 10, 10);
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
/// let [top, main] = area.try_layout(&layout)?;
/// assert_eq!(top, Rect::new(0, 0, 10, 1));
/// assert_eq!(main, Rect::new(0, 1, 10, 9));
///
/// // or explicitly specify the number of constraints:
/// let areas = area.try_layout::<2>(&layout)?;
/// assert_eq!(areas, [Rect::new(0, 0, 10, 1), Rect::new(0, 1, 10, 9),]);
/// # Ok::<(), core::array::TryFromSliceError>(())
/// ``````
pub fn try_layout<const N: usize>(
self,
layout: &Layout,
) -> Result<[Self; N], TryFromSliceError> {
layout.split(self).as_ref().try_into()
}
/// indents the x value of the `Rect` by a given `offset`
///
/// This is pub(crate) for now as we need to stabilize the naming / design of this API.
@@ -511,6 +716,27 @@ mod tests {
);
}
#[test]
fn outer() {
// enough space to grow on all sides
assert_eq!(
Rect::new(100, 200, 10, 20).outer(Margin::new(20, 30)),
Rect::new(80, 170, 50, 80)
);
// left / top saturation should truncate the size (10 less on left / top)
assert_eq!(
Rect::new(10, 20, 10, 20).outer(Margin::new(20, 30)),
Rect::new(0, 0, 40, 70),
);
// right / bottom saturation should truncate the size (10 less on bottom / right)
assert_eq!(
Rect::new(u16::MAX - 20, u16::MAX - 40, 10, 20).outer(Margin::new(20, 30)),
Rect::new(u16::MAX - 40, u16::MAX - 70, 40, 70),
);
}
#[test]
fn offset() {
assert_eq!(
@@ -756,4 +982,54 @@ mod tests {
Rect::new(1, 2, 3, 1)
);
}
#[test]
fn layout() {
let layout = Layout::horizontal([Constraint::Length(3), Constraint::Min(0)]);
let [a, b] = Rect::new(0, 0, 10, 10).layout(&layout);
assert_eq!(a, Rect::new(0, 0, 3, 10));
assert_eq!(b, Rect::new(3, 0, 7, 10));
let areas = Rect::new(0, 0, 10, 10).layout::<2>(&layout);
assert_eq!(areas[0], Rect::new(0, 0, 3, 10));
assert_eq!(areas[1], Rect::new(3, 0, 7, 10));
}
#[test]
#[should_panic(expected = "invalid number of rects: expected 3, found 1")]
fn layout_invalid_number_of_rects() {
let layout = Layout::horizontal([Constraint::Length(1)]);
let [_, _, _] = Rect::new(0, 0, 10, 10).layout(&layout);
}
#[test]
fn layout_vec() {
let layout = Layout::horizontal([Constraint::Length(3), Constraint::Min(0)]);
let areas = Rect::new(0, 0, 10, 10).layout_vec(&layout);
assert_eq!(areas[0], Rect::new(0, 0, 3, 10));
assert_eq!(areas[1], Rect::new(3, 0, 7, 10));
}
#[test]
fn try_layout() {
let layout = Layout::horizontal([Constraint::Length(3), Constraint::Min(0)]);
let [a, b] = Rect::new(0, 0, 10, 10).try_layout(&layout).unwrap();
assert_eq!(a, Rect::new(0, 0, 3, 10));
assert_eq!(b, Rect::new(3, 0, 7, 10));
let areas = Rect::new(0, 0, 10, 10).try_layout::<2>(&layout).unwrap();
assert_eq!(areas[0], Rect::new(0, 0, 3, 10));
assert_eq!(areas[1], Rect::new(3, 0, 7, 10));
}
#[test]
fn try_layout_invalid_number_of_rects() {
let layout = Layout::horizontal([Constraint::Length(1)]);
Rect::new(0, 0, 10, 10)
.try_layout::<3>(&layout)
.unwrap_err();
}
}

View File

@@ -3,10 +3,38 @@ use core::fmt;
use crate::layout::Rect;
/// A simple size struct
/// A simple size struct for representing dimensions in the terminal.
///
/// The width and height are stored as `u16` values and represent the number of columns and rows
/// respectively.
/// respectively. This is used throughout the layout system to represent dimensions of rectangular
/// areas and other layout elements.
///
/// Size can be created from tuples, extracted from rectangular areas, or constructed directly.
/// It's commonly used in conjunction with [`Position`](crate::layout::Position) to define
/// rectangular areas.
///
/// # Construction
///
/// - [`new`](Self::new) - Create a new size from width and height
/// - [`default`](Default::default) - Create with zero dimensions
///
/// # Conversion
///
/// - [`from((u16, u16))`](Self::from) - Create from `(u16, u16)` tuple
/// - [`from(Rect)`](Self::from) - Create from [`Rect`] (uses width and height)
/// - [`into((u16, u16))`] - Convert to `(u16, u16)` tuple
///
/// # Examples
///
/// ```rust
/// use ratatui_core::layout::{Rect, Size};
///
/// let size = Size::new(80, 24);
/// let size = Size::from((80, 24));
/// let size = Size::from(Rect::new(0, 0, 80, 24));
/// ```
///
/// For comprehensive layout documentation and examples, see the [`layout`](crate::layout) module.
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Size {

View File

@@ -27,6 +27,32 @@
//! ```shell
//! cargo add ratatui-core
//! ```
//!
//! # Crate Organization
//!
//! `ratatui-core` is part of the Ratatui workspace that was modularized in version 0.30.0 to
//! improve compilation times, API stability, and dependency management. This crate provides the
//! foundational types and traits that other crates in the workspace depend on.
//!
//! **When to use `ratatui-core`:**
//!
//! - Building widget libraries that implement [`Widget`] or [`StatefulWidget`]
//! - Creating lightweight applications that don't need built-in widgets
//! - You want minimal dependencies and faster compilation times
//! - You need maximum API stability (core types change less frequently)
//!
//! **When to use the main [`ratatui`] crate:**
//!
//! - Building applications that use built-in widgets
//! - You want convenience and don't mind slightly longer compilation times
//! - You need backend implementations and terminal management utilities
//!
//! For detailed information about the workspace organization, see [ARCHITECTURE.md].
//!
//! [`ratatui`]: https://crates.io/crates/ratatui
//! [`Widget`]: widgets::Widget
//! [`StatefulWidget`]: widgets::StatefulWidget
//! [ARCHITECTURE.md]: https://github.com/ratatui/ratatui/blob/main/ARCHITECTURE.md
#![cfg_attr(feature = "document-features", doc = "\n## Features")]
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
//!

View File

@@ -190,6 +190,8 @@ impl From<anstyle::Style> for Style {
Self {
fg: style.get_fg_color().map(Color::from),
bg: style.get_bg_color().map(Color::from),
#[cfg(feature = "underline-color")]
underline_color: style.get_underline_color().map(Color::from),
add_modifier: style.get_effects().into(),
..Default::default()
}
@@ -207,6 +209,11 @@ impl From<Style> for anstyle::Style {
let bg = anstyle::Color::from(bg);
anstyle_style = anstyle_style.bg_color(Some(bg));
}
#[cfg(feature = "underline-color")]
if let Some(underline) = style.underline_color {
let underline = anstyle::Color::from(underline);
anstyle_style = anstyle_style.underline_color(Some(underline));
}
anstyle_style = anstyle_style.effects(style.add_modifier.into());
anstyle_style
}
@@ -300,10 +307,13 @@ mod tests {
let anstyle_style = anstyle::Style::new()
.fg_color(Some(anstyle::Color::Ansi(AnsiColor::Red)))
.bg_color(Some(anstyle::Color::Ansi(AnsiColor::Blue)))
.underline_color(Some(anstyle::Color::Ansi(AnsiColor::Green)))
.effects(Effects::BOLD | Effects::ITALIC);
let style = Style::from(anstyle_style);
assert_eq!(style.fg, Some(Color::Red));
assert_eq!(style.bg, Some(Color::Blue));
#[cfg(feature = "underline-color")]
assert_eq!(style.underline_color, Some(Color::Green));
assert!(style.add_modifier.contains(Modifier::BOLD));
assert!(style.add_modifier.contains(Modifier::ITALIC));
}
@@ -313,6 +323,8 @@ mod tests {
let style = Style {
fg: Some(Color::Red),
bg: Some(Color::Blue),
#[cfg(feature = "underline-color")]
underline_color: Some(Color::Green),
add_modifier: Modifier::BOLD | Modifier::ITALIC,
..Default::default()
};
@@ -325,6 +337,11 @@ mod tests {
anstyle_style.get_bg_color(),
Some(anstyle::Color::Ansi(AnsiColor::Blue))
);
#[cfg(feature = "underline-color")]
assert_eq!(
anstyle_style.get_underline_color(),
Some(anstyle::Color::Ansi(AnsiColor::Green))
);
assert!(anstyle_style.get_effects().contains(Effects::BOLD));
assert!(anstyle_style.get_effects().contains(Effects::ITALIC));
}

View File

@@ -419,6 +419,7 @@ use crate::style::Color;
/// This is a collection of colors that are used in Material design. They consist of a set of
/// colors from 50 to 900, and a set of accent colors from 100 to 700.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AccentedPalette {
pub c50: Color,
pub c100: Color,
@@ -441,6 +442,7 @@ pub struct AccentedPalette {
/// This is a collection of colors that are used in Material design. They consist of a set of
/// colors from 50 to 900.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NonAccentedPalette {
pub c50: Color,
pub c100: Color,

View File

@@ -277,6 +277,7 @@
use crate::style::Color;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Palette {
pub c50: Color,
pub c100: Color,

View File

@@ -9,5 +9,6 @@ pub mod braille;
pub mod half_block;
pub mod line;
pub mod marker;
pub mod merge;
pub mod scrollbar;
pub mod shade;

View File

@@ -8,19 +8,19 @@ pub const ONE_QUARTER: &str = "▂";
pub const ONE_EIGHTH: &str = "";
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Set {
pub full: &'static str,
pub seven_eighths: &'static str,
pub three_quarters: &'static str,
pub five_eighths: &'static str,
pub half: &'static str,
pub three_eighths: &'static str,
pub one_quarter: &'static str,
pub one_eighth: &'static str,
pub empty: &'static str,
pub struct Set<'a> {
pub full: &'a str,
pub seven_eighths: &'a str,
pub three_quarters: &'a str,
pub five_eighths: &'a str,
pub half: &'a str,
pub three_eighths: &'a str,
pub one_quarter: &'a str,
pub one_eighth: &'a str,
pub empty: &'a str,
}
impl Default for Set {
impl Default for Set<'_> {
fn default() -> Self {
NINE_LEVELS
}

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