Compare commits
61 Commits
rect-offse
...
kd/multi-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccf9d92f10 | ||
|
|
b712034644 | ||
|
|
38fca62fa9 | ||
|
|
3a51a027a6 | ||
|
|
5b30f2275c | ||
|
|
9bd89c218a | ||
|
|
2cfe82a47e | ||
|
|
b4c27c744c | ||
|
|
977a4899c8 | ||
|
|
cd27b4829a | ||
|
|
feee871519 | ||
|
|
1a4bb1cbb8 | ||
|
|
839cca20bf | ||
|
|
f945a0bcff | ||
|
|
eb281df974 | ||
|
|
28e81c0714 | ||
|
|
0051bb2037 | ||
|
|
477217c77a | ||
|
|
31de3586f7 | ||
|
|
f702025b75 | ||
|
|
76e5fe5a9a | ||
|
|
366cbae09f | ||
|
|
ec763af851 | ||
|
|
699c2d7c8d | ||
|
|
3cc29bdada | ||
|
|
8de3d52469 | ||
|
|
b30411d1c7 | ||
|
|
aa4260f92c | ||
|
|
5f1e119563 | ||
|
|
4d1784f2de | ||
|
|
baedc39494 | ||
|
|
366c2a0e6d | ||
|
|
bef2bc1e7c | ||
|
|
64eb3913a4 | ||
|
|
e95230beda | ||
|
|
f4637d40c3 | ||
|
|
da1ade7b2e | ||
|
|
1706b0a3e4 | ||
|
|
4392759501 | ||
|
|
20fc0ddfca | ||
|
|
3687f78f6a | ||
|
|
5fbb77ad20 | ||
|
|
97ee102f17 | ||
|
|
c442dfd1ad | ||
|
|
0a164965ea | ||
|
|
bf0923473c | ||
|
|
81b96338ea | ||
|
|
2e71c1874e | ||
|
|
c75aa1990f | ||
|
|
326a461f9a | ||
|
|
f3172c59d4 | ||
|
|
bef5bcf750 | ||
|
|
11264787d0 | ||
|
|
9b3c260b76 | ||
|
|
363c4c54e8 | ||
|
|
b7778e5cd1 | ||
|
|
b5061c5250 | ||
|
|
359204c929 | ||
|
|
14461c3a35 | ||
|
|
3b002fdcab | ||
|
|
0207160784 |
5
.github/CODEOWNERS
vendored
5
.github/CODEOWNERS
vendored
@@ -5,4 +5,7 @@
|
||||
# https://git-scm.com/docs/gitignore#_pattern_format
|
||||
|
||||
# Maintainers
|
||||
* @orhun @mindoodoo @sayanarijit @joshka @kdheepak @Valentin271 @EdJoPaTo
|
||||
* @orhun @joshka @kdheepak @Valentin271 @EdJoPaTo
|
||||
|
||||
# Past maintainers
|
||||
# @mindoodoo @sayanarijit
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This document contains a list of breaking changes in each version and some notes to help migrate
|
||||
between versions. It is compiled manually from the commit history and changelog. We also tag PRs on
|
||||
github with a [breaking change] label.
|
||||
GitHub with a [breaking change] label.
|
||||
|
||||
[breaking change]: (https://github.com/ratatui-org/ratatui/issues?q=label%3A%22breaking+change%22)
|
||||
|
||||
@@ -37,7 +37,7 @@ This is a quick summary of the sections below:
|
||||
- `Scrollbar`: symbols moved to `symbols` module
|
||||
- MSRV is now 1.67.0
|
||||
- [v0.22.0](#v0220)
|
||||
- serde representation of `Borders` and `Modifiers` has changed
|
||||
- `serde` representation of `Borders` and `Modifiers` has changed
|
||||
- [v0.21.0](#v0210)
|
||||
- MSRV is now 1.65.0
|
||||
- `terminal::ViewPort` is now an enum
|
||||
@@ -49,14 +49,14 @@ This is a quick summary of the sections below:
|
||||
|
||||
## [v0.26.0](https://github.com/ratatui-org/ratatui/releases/tag/v0.26.0)
|
||||
|
||||
### `Flex::Start` is the new default flex mode for `Layout`
|
||||
### `Flex::Start` is the new default flex mode for `Layout` ([#881])
|
||||
|
||||
[#881]: https://github.com/ratatui-org/ratatui/pull/881
|
||||
|
||||
Previously, constraints would stretch to fill all available space, violating constraints if
|
||||
necessary.
|
||||
|
||||
With v0.26.0, `Flex` modes are introduced and the default is `Flex::Start`, which will align
|
||||
With v0.26.0, `Flex` modes are introduced, and the default is `Flex::Start`, which will align
|
||||
areas associated with constraints to be beginning of the area. With v0.26.0, additionally,
|
||||
`Min` constraints grow to fill excess space. These changes will allow users to build layouts
|
||||
more easily.
|
||||
@@ -108,7 +108,7 @@ by removing the call to `.collect()`.
|
||||
[#751]: https://github.com/ratatui-org/ratatui/pull/751
|
||||
|
||||
The default() implementation of Table now sets the column_spacing field to 1 and the segment_size
|
||||
field to SegmentSize::None. This will affect the rendering of a small amount of apps.
|
||||
field to `SegmentSize::None`. This will affect the rendering of a small amount of apps.
|
||||
|
||||
To use the previous default values, call `table.segment_size(Default::default())` and
|
||||
`table.column_spacing(0)`.
|
||||
@@ -222,8 +222,8 @@ widget in the default configuration would not show any indication of the selecte
|
||||
|
||||
[#664]: https://github.com/ratatui-org/ratatui/pull/664
|
||||
|
||||
Previously `Table`s could be constructed without widths. In almost all cases this is an error.
|
||||
A new widths parameter is now mandatory on `Table::new()`. Existing code of the form:
|
||||
Previously `Table`s could be constructed without `widths`. In almost all cases this is an error.
|
||||
A new `widths` parameter is now mandatory on `Table::new()`. Existing code of the form:
|
||||
|
||||
```diff
|
||||
- Table::new(rows).widths(widths)
|
||||
@@ -281,7 +281,7 @@ let layout = layout::new(Direction::Vertical, [Constraint::Min(1), Constraint::M
|
||||
|
||||
## [v0.24.0](https://github.com/ratatui-org/ratatui/releases/tag/v0.24.0)
|
||||
|
||||
### ScrollbarState field type changed from `u16` to `usize` ([#456])
|
||||
### `ScrollbarState` field type changed from `u16` to `usize` ([#456])
|
||||
|
||||
[#456]: https://github.com/ratatui-org/ratatui/pull/456
|
||||
|
||||
@@ -385,12 +385,12 @@ The MSRV of ratatui is now 1.67 due to an MSRV update in a dependency (`time`).
|
||||
|
||||
## [v0.22.0](https://github.com/ratatui-org/ratatui/releases/tag/v0.22.0)
|
||||
|
||||
### bitflags updated to 2.3 ([#205])
|
||||
### `bitflags` updated to 2.3 ([#205])
|
||||
|
||||
[#205]: https://github.com/ratatui-org/ratatui/issues/205
|
||||
|
||||
The serde representation of bitflags has changed. Any existing serialized types that have Borders or
|
||||
Modifiers will need to be re-serialized. This is documented in the [bitflags
|
||||
The `serde` representation of `bitflags` has changed. Any existing serialized types that have Borders or
|
||||
Modifiers will need to be re-serialized. This is documented in the [`bitflags`
|
||||
changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md#200-rc2)..
|
||||
|
||||
## [v0.21.0](https://github.com/ratatui-org/ratatui/releases/tag/v0.21.0)
|
||||
@@ -422,9 +422,9 @@ let terminal = Terminal::with_options(backend, TerminalOptions {
|
||||
|
||||
[#168]: https://github.com/ratatui-org/ratatui/issues/168
|
||||
|
||||
A new type `Masked` was introduced that implements `From<Text<'a>>`. This causes any code that did
|
||||
A new type `Masked` was introduced that implements `From<Text<'a>>`. This causes any code that
|
||||
previously did not need to use type annotations to fail to compile. To fix this, annotate or call
|
||||
to_string() / to_owned() / as_str() on the value. E.g.:
|
||||
`to_string()` / `to_owned()` / `as_str()` on the value. E.g.:
|
||||
|
||||
```diff
|
||||
- let paragraph = Paragraph::new("".as_ref());
|
||||
|
||||
458
CHANGELOG.md
458
CHANGELOG.md
@@ -2,7 +2,457 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.26.1](https://github.com/ratatui-org/ratatui/releases/tag/0.26.1) - 2024-02-12
|
||||
## [0.26.2](https://github.com/ratatui-org/ratatui/releases/tag/v0.26.2) - 2024-04-15
|
||||
|
||||
This is a patch release that fixes bugs and adds enhancements, including new iterator constructors, List scroll padding, and various rendering improvements. ✨
|
||||
|
||||
✨ **Release highlights**: <https://ratatui.rs/highlights/v0262/>
|
||||
|
||||
### Features
|
||||
|
||||
- [11b452d](https://github.com/ratatui-org/ratatui/commit/11b452d56fe590188ee7a53fa2dde95513b1a4c7)
|
||||
*(layout)* Mark various functions as const by @EdJoPaTo in [#951](https://github.com/ratatui-org/ratatui/pull/951)
|
||||
|
||||
- [1cff511](https://github.com/ratatui-org/ratatui/commit/1cff51193466f5a94d202b6233d56889eccf6d7b)
|
||||
*(line)* Impl Styled for Line by @joshka in [#968](https://github.com/ratatui-org/ratatui/pull/968)
|
||||
|
||||
````text
|
||||
This adds `FromIterator` impls for `Line` and `Text` that allow creating
|
||||
`Line` and `Text` instances from iterators of `Span` and `Line`
|
||||
instances, respectively.
|
||||
|
||||
```rust
|
||||
let line = Line::from_iter(vec!["Hello".blue(), " world!".green()]);
|
||||
let line: Line = iter::once("Hello".blue())
|
||||
.chain(iter::once(" world!".green()))
|
||||
.collect();
|
||||
let text = Text::from_iter(vec!["The first line", "The second line"]);
|
||||
let text: Text = iter::once("The first line")
|
||||
.chain(iter::once("The second line"))
|
||||
.collect();
|
||||
```
|
||||
````
|
||||
|
||||
- [654949b](https://github.com/ratatui-org/ratatui/commit/654949bb00b4522130642f9ad50ab4d9095d921b)
|
||||
*(list)* Add Scroll Padding to Lists by @CameronBarnes in [#958](https://github.com/ratatui-org/ratatui/pull/958)
|
||||
|
||||
````text
|
||||
Introduces scroll padding, which allows the api user to request that a certain number of ListItems be kept visible above and below the currently selected item while scrolling.
|
||||
|
||||
```rust
|
||||
let list = List::new(items).scroll_padding(1);
|
||||
```
|
||||
````
|
||||
|
||||
Fixes:https://github.com/ratatui-org/ratatui/pull/955
|
||||
|
||||
- [26af650](https://github.com/ratatui-org/ratatui/commit/26af65043ee9f165459dec228d12eaeed9997d92)
|
||||
*(text)* Add push methods for text and line by @joshka in [#998](https://github.com/ratatui-org/ratatui/pull/998)
|
||||
|
||||
````text
|
||||
Adds the following methods to the `Text` and `Line` structs:
|
||||
- Text::push_line
|
||||
- Text::push_span
|
||||
- Line::push_span
|
||||
|
||||
This allows for adding lines and spans to a text object without having
|
||||
to call methods on the fields directly, which is useful for incremental
|
||||
construction of text objects.
|
||||
````
|
||||
|
||||
- [b5bdde0](https://github.com/ratatui-org/ratatui/commit/b5bdde079e0e1eda98b9b1bbbba011b770e5b167)
|
||||
*(text)* Add `FromIterator` impls for `Line` and `Text` by @joshka in [#967](https://github.com/ratatui-org/ratatui/pull/967)
|
||||
|
||||
````text
|
||||
This adds `FromIterator` impls for `Line` and `Text` that allow creating
|
||||
`Line` and `Text` instances from iterators of `Span` and `Line`
|
||||
instances, respectively.
|
||||
|
||||
```rust
|
||||
let line = Line::from_iter(vec!["Hello".blue(), " world!".green()]);
|
||||
let line: Line = iter::once("Hello".blue())
|
||||
.chain(iter::once(" world!".green()))
|
||||
.collect();
|
||||
let text = Text::from_iter(vec!["The first line", "The second line"]);
|
||||
let text: Text = iter::once("The first line")
|
||||
.chain(iter::once("The second line"))
|
||||
.collect();
|
||||
```
|
||||
````
|
||||
|
||||
- [12f67e8](https://github.com/ratatui-org/ratatui/commit/12f67e810fad0f907546408192a2380b590ff7bd)
|
||||
*(uncategorized)* Impl Widget for `&str` and `String` by @kdheepak in [#952](https://github.com/ratatui-org/ratatui/pull/952)
|
||||
|
||||
````text
|
||||
Currently, `f.render_widget("hello world".bold(), area)` works but
|
||||
`f.render_widget("hello world", area)` doesn't. This PR changes that my
|
||||
implementing `Widget` for `&str` and `String`. This makes it easier to
|
||||
render strings with no styles as widgets.
|
||||
|
||||
Example usage:
|
||||
|
||||
```rust
|
||||
terminal.draw(|f| f.render_widget("Hello World!", f.size()))?;
|
||||
```
|
||||
|
||||
---------
|
||||
````
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [0207160](https://github.com/ratatui-org/ratatui/commit/02071607848c51250b4663722c52e19c8ce1c5e2)
|
||||
*(line)* Line truncation respects alignment by @TadoTheMiner in [#987](https://github.com/ratatui-org/ratatui/pull/987)
|
||||
|
||||
````text
|
||||
When rendering a `Line`, the line will be truncated:
|
||||
- on the right for left aligned lines
|
||||
- on the left for right aligned lines
|
||||
- on bot sides for centered lines
|
||||
|
||||
E.g. "Hello World" will be rendered as "Hello", "World", "lo wo" for
|
||||
left, right, centered lines respectively.
|
||||
````
|
||||
|
||||
Fixes:https://github.com/ratatui-org/ratatui/issues/932
|
||||
|
||||
- [c56f49b](https://github.com/ratatui-org/ratatui/commit/c56f49b9fb1c7f1c8c97749119e85f81882ca9a9)
|
||||
*(list)* Saturating_sub to fix highlight_symbol overflow by @mrjackwills in [#949](https://github.com/ratatui-org/ratatui/pull/949)
|
||||
|
||||
````text
|
||||
An overflow (pedantically an underflow) can occur if the
|
||||
highlight_symbol is a multi-byte char, and area is reduced to a size
|
||||
less than that char length.
|
||||
````
|
||||
|
||||
- [b7778e5](https://github.com/ratatui-org/ratatui/commit/b7778e5cd15d0d4b28f7bbb8b3c62950748e333a)
|
||||
*(paragraph)* Unit test typo by @joshka in [#1022](https://github.com/ratatui-org/ratatui/pull/1022)
|
||||
|
||||
- [943c043](https://github.com/ratatui-org/ratatui/commit/943c0431d968a82b23a2f31527f32e57f86f8a7c)
|
||||
*(scrollbar)* Dont render on 0 length track by @EdJoPaTo in [#964](https://github.com/ratatui-org/ratatui/pull/964)
|
||||
|
||||
````text
|
||||
Fixes a panic when `track_length - 1` is used. (clamp panics on `-1.0`
|
||||
being smaller than `0.0`)
|
||||
````
|
||||
|
||||
- [742a5ea](https://github.com/ratatui-org/ratatui/commit/742a5ead066bec14047f6ab7ffa3ac8307eea715)
|
||||
*(text)* Fix panic when rendering out of bounds by @joshka in [#997](https://github.com/ratatui-org/ratatui/pull/997)
|
||||
|
||||
````text
|
||||
Previously it was possible to cause a panic when rendering to an area
|
||||
outside of the buffer bounds. Instead this now correctly renders nothing
|
||||
to the buffer.
|
||||
````
|
||||
|
||||
- [f6c4e44](https://github.com/ratatui-org/ratatui/commit/f6c4e447e65fe10f4fc7fcc9e9c4312acad41096)
|
||||
*(uncategorized)* Ensure that paragraph correctly renders styled text by @joshka in [#992](https://github.com/ratatui-org/ratatui/pull/992)
|
||||
|
||||
````text
|
||||
Paragraph was ignoring the new `Text::style` field added in 0.26.0
|
||||
````
|
||||
|
||||
Fixes:https://github.com/ratatui-org/ratatui/issues/990
|
||||
|
||||
- [35e971f](https://github.com/ratatui-org/ratatui/commit/35e971f7ebb0deadc613b561b15511abd48bdb54)
|
||||
*(uncategorized)* Scrollbar thumb not visible on long lists by @ThomasMiz in [#959](https://github.com/ratatui-org/ratatui/pull/959)
|
||||
|
||||
````text
|
||||
When displaying somewhat-long lists, the `Scrollbar` widget sometimes did not display a thumb character, and only the track will be visible.
|
||||
````
|
||||
|
||||
### Refactor
|
||||
|
||||
- [6fd5f63](https://github.com/ratatui-org/ratatui/commit/6fd5f631bbd58156d9fcae196040bb0248097819)
|
||||
*(lint)* Prefer idiomatic for loops by @EdJoPaTo
|
||||
|
||||
- [37b957c](https://github.com/ratatui-org/ratatui/commit/37b957c7e167a7ecda07b8a60cee5de71efcc55e)
|
||||
*(lints)* Add lints to scrollbar by @EdJoPaTo
|
||||
|
||||
- [c12bcfe](https://github.com/ratatui-org/ratatui/commit/c12bcfefa26529610886040bd96f2b6762436b15)
|
||||
*(non-src)* Apply pedantic lints by @EdJoPaTo in [#976](https://github.com/ratatui-org/ratatui/pull/976)
|
||||
|
||||
````text
|
||||
Fixes many not yet enabled lints (mostly pedantic) on everything that is
|
||||
not the lib (examples, benches, tests). Therefore, this is not containing
|
||||
anything that can be a breaking change.
|
||||
|
||||
Lints are not enabled as that should be the job of #974. I created this
|
||||
as a separate PR as its mostly independent and would only clutter up the
|
||||
diff of #974 even more.
|
||||
|
||||
Also see
|
||||
https://github.com/ratatui-org/ratatui/pull/974#discussion_r1506458743
|
||||
|
||||
---------
|
||||
````
|
||||
|
||||
- [8719608](https://github.com/ratatui-org/ratatui/commit/8719608bdaf32ba92bdfdd60569cf73f7070a618)
|
||||
*(span)* Rename to_aligned_line into into_aligned_line by @EdJoPaTo in [#993](https://github.com/ratatui-org/ratatui/pull/993)
|
||||
|
||||
````text
|
||||
With the Rust method naming conventions these methods are into methods
|
||||
consuming the Span. Therefore, it's more consistent to use `into_`
|
||||
instead of `to_`.
|
||||
|
||||
```rust
|
||||
Span::to_centered_line
|
||||
Span::to_left_aligned_line
|
||||
Span::to_right_aligned_line
|
||||
```
|
||||
|
||||
Are marked deprecated and replaced with the following
|
||||
|
||||
```rust
|
||||
Span::into_centered_line
|
||||
Span::into_left_aligned_line
|
||||
Span::into_right_aligned_line
|
||||
```
|
||||
````
|
||||
|
||||
- [b831c56](https://github.com/ratatui-org/ratatui/commit/b831c5688c6f1fbfa6ae2bcd70d803a54fcf0196)
|
||||
*(widget-ref)* Clippy::needless_pass_by_value by @EdJoPaTo
|
||||
|
||||
- [359204c](https://github.com/ratatui-org/ratatui/commit/359204c9298cc26ea21807d886d596de0329bacc)
|
||||
*(uncategorized)* Simplify to io::Result by @EdJoPaTo in [#1016](https://github.com/ratatui-org/ratatui/pull/1016)
|
||||
|
||||
````text
|
||||
Simplifies the code, logic stays exactly the same.
|
||||
````
|
||||
|
||||
- [8e68db9](https://github.com/ratatui-org/ratatui/commit/8e68db9e2f57fcbf7cb5140006bbbd4dd80bf907)
|
||||
*(uncategorized)* Remove pointless default on internal structs by @EdJoPaTo in [#980](https://github.com/ratatui-org/ratatui/pull/980)
|
||||
|
||||
See #978
|
||||
|
||||
Also remove other derives. They are unused and just slow down
|
||||
compilation.
|
||||
|
||||
- [3be189e](https://github.com/ratatui-org/ratatui/commit/3be189e3c6ebd418d13138ff32bc4a749dc840cf)
|
||||
*(uncategorized)* Clippy::thread_local_initializer_can_be_made_const by @EdJoPaTo
|
||||
|
||||
````text
|
||||
enabled by default on nightly
|
||||
````
|
||||
|
||||
- [5c4efac](https://github.com/ratatui-org/ratatui/commit/5c4efacd1d70bb295d90ffaa73853dc206c187fb)
|
||||
*(uncategorized)* Clippy::map_err_ignore by @EdJoPaTo
|
||||
|
||||
- [bbb6d65](https://github.com/ratatui-org/ratatui/commit/bbb6d65e063df9a74ab6487b2216183c1fdd7230)
|
||||
*(uncategorized)* Clippy::else_if_without_else by @EdJoPaTo
|
||||
|
||||
- [fdb14dc](https://github.com/ratatui-org/ratatui/commit/fdb14dc7cd69788e2ed20709e767f7631b11ffa2)
|
||||
*(uncategorized)* Clippy::redundant_type_annotations by @EdJoPaTo
|
||||
|
||||
- [9b3b23a](https://github.com/ratatui-org/ratatui/commit/9b3b23ac14518a1ef23065d4a5da0fb047b18213)
|
||||
*(uncategorized)* Remove literal suffix by @EdJoPaTo
|
||||
|
||||
````text
|
||||
its not needed and can just be assumed
|
||||
````
|
||||
|
||||
related:clippy::(un)separated_literal_suffix
|
||||
|
||||
- [58b6e0b](https://github.com/ratatui-org/ratatui/commit/58b6e0be0f4db3d90005e130e4b84cd865179785)
|
||||
*(uncategorized)* Clippy::should_panic_without_expect by @EdJoPaTo
|
||||
|
||||
- [c870a41](https://github.com/ratatui-org/ratatui/commit/c870a41057ac0c14c2e72e762b37689dc32e7b23)
|
||||
*(uncategorized)* Clippy::many_single_char_names by @EdJoPaTo
|
||||
|
||||
- [a6036ad](https://github.com/ratatui-org/ratatui/commit/a6036ad78911653407f607f5efa556a055d3dce9)
|
||||
*(uncategorized)* Clippy::similar_names by @EdJoPaTo
|
||||
|
||||
- [060d26b](https://github.com/ratatui-org/ratatui/commit/060d26b6dc6e1027dbf46ae98b0ebba83701f941)
|
||||
*(uncategorized)* Clippy::match_same_arms by @EdJoPaTo
|
||||
|
||||
- [fcbea9e](https://github.com/ratatui-org/ratatui/commit/fcbea9ee68591344a29a7b2e83f1c8c878857aeb)
|
||||
*(uncategorized)* Clippy::uninlined_format_args by @EdJoPaTo
|
||||
|
||||
- [14b24e7](https://github.com/ratatui-org/ratatui/commit/14b24e75858af48f39d5880e7f6c9adeac1b1da9)
|
||||
*(uncategorized)* Clippy::if_not_else by @EdJoPaTo
|
||||
|
||||
- [5ed1f43](https://github.com/ratatui-org/ratatui/commit/5ed1f43c627053f25d9ee711677ebec6cb8fcd85)
|
||||
*(uncategorized)* Clippy::redundant_closure_for_method_calls by @EdJoPaTo
|
||||
|
||||
- [c8c7924](https://github.com/ratatui-org/ratatui/commit/c8c7924e0ca84351f5ed5c54e79611ce16d4dc37)
|
||||
*(uncategorized)* Clippy::too_many_lines by @EdJoPaTo
|
||||
|
||||
- [e3afe7c](https://github.com/ratatui-org/ratatui/commit/e3afe7c8a14c1cffd7de50782a7acf0f95f41673)
|
||||
*(uncategorized)* Clippy::unreadable_literal by @EdJoPaTo
|
||||
|
||||
- [a1f54de](https://github.com/ratatui-org/ratatui/commit/a1f54de7d60fa6c57be29bf8f02a675e58b7b9c2)
|
||||
*(uncategorized)* Clippy::bool_to_int_with_if by @EdJoPaTo
|
||||
|
||||
- [b8ea190](https://github.com/ratatui-org/ratatui/commit/b8ea190bf2cde8c18e2ac8276d2eb57d219db263)
|
||||
*(uncategorized)* Clippy::cast_lossless by @EdJoPaTo
|
||||
|
||||
- [0de5238](https://github.com/ratatui-org/ratatui/commit/0de5238ed3613f2d663f5e9628ca7b2aa205ed02)
|
||||
*(uncategorized)* Dead_code by @EdJoPaTo
|
||||
|
||||
````text
|
||||
enabled by default, only detected by nightly yet
|
||||
````
|
||||
|
||||
- [df5dddf](https://github.com/ratatui-org/ratatui/commit/df5dddfbc9c679d15a5a90ea79bb1f8946d5cb9c)
|
||||
*(uncategorized)* Unused_imports by @EdJoPaTo
|
||||
|
||||
````text
|
||||
enabled by default, only detected on nightly yet
|
||||
````
|
||||
|
||||
- [f1398ae](https://github.com/ratatui-org/ratatui/commit/f1398ae6cb1abd32106923d64844b482c7ba6f82)
|
||||
*(uncategorized)* Clippy::useless_vec by @EdJoPaTo
|
||||
|
||||
````text
|
||||
Lint enabled by default but only nightly finds this yet
|
||||
````
|
||||
|
||||
- [525848f](https://github.com/ratatui-org/ratatui/commit/525848ff4e066526d402fecf1d5b9c63cff1f22a)
|
||||
*(uncategorized)* Manually apply clippy::use_self for impl with lifetimes by @EdJoPaTo
|
||||
|
||||
- [660c718](https://github.com/ratatui-org/ratatui/commit/660c7183c7a10dc453d80dfb651d9534536960b9)
|
||||
*(uncategorized)* Clippy::empty_line_after_doc_comments by @EdJoPaTo
|
||||
|
||||
- [ab951fa](https://github.com/ratatui-org/ratatui/commit/ab951fae8166c9321728ba942b48552dfe4d9c55)
|
||||
*(uncategorized)* Clippy::return_self_not_must_use by @EdJoPaTo
|
||||
|
||||
- [3cd4369](https://github.com/ratatui-org/ratatui/commit/3cd436917649a93b4b80d0c4a0343284e0585522)
|
||||
*(uncategorized)* Clippy::doc_markdown by @EdJoPaTo
|
||||
|
||||
- [9bc014d](https://github.com/ratatui-org/ratatui/commit/9bc014d7f16efdb70fcd6b6b786fe74eac7b9bdf)
|
||||
*(uncategorized)* Clippy::items_after_statements by @EdJoPaTo
|
||||
|
||||
- [36a0cd5](https://github.com/ratatui-org/ratatui/commit/36a0cd56e5645533a1d6c2720536fa10a56b0d40)
|
||||
*(uncategorized)* Clippy::deref_by_slicing by @EdJoPaTo
|
||||
|
||||
- [f7f6692](https://github.com/ratatui-org/ratatui/commit/f7f66928a8833532a3bc97292665640285e7aafa)
|
||||
*(uncategorized)* Clippy::equatable_if_let by @EdJoPaTo
|
||||
|
||||
- [01418eb](https://github.com/ratatui-org/ratatui/commit/01418eb7c2e1874cb4070828c485d81ea171b18d)
|
||||
*(uncategorized)* Clippy::default_trait_access by @EdJoPaTo
|
||||
|
||||
- [8536760](https://github.com/ratatui-org/ratatui/commit/8536760e7802a498f7c6d9fe8fb4c7920a1c6e71)
|
||||
*(uncategorized)* Clippy::inefficient_to_string by @EdJoPaTo
|
||||
|
||||
- [a558b19](https://github.com/ratatui-org/ratatui/commit/a558b19c9a7b90a1ed3f309301f49f0b483e02ec)
|
||||
*(uncategorized)* Clippy::implicit_clone by @EdJoPaTo
|
||||
|
||||
- [5b00e3a](https://github.com/ratatui-org/ratatui/commit/5b00e3aae98cb5c20c10bec944948a75ac83f956)
|
||||
*(uncategorized)* Clippy::use_self by @EdJoPaTo
|
||||
|
||||
- [27680c0](https://github.com/ratatui-org/ratatui/commit/27680c05ce1670f026ad23c446ada321c1c755f0)
|
||||
*(uncategorized)* Clippy::semicolon_if_nothing_returned by @EdJoPaTo
|
||||
|
||||
### Documentation
|
||||
|
||||
- [14461c3](https://github.com/ratatui-org/ratatui/commit/14461c3a3554c95905ebca433fc3d4dae1e1acda)
|
||||
*(breaking-changes)* Typos and markdownlint by @EdJoPaTo in [#1009](https://github.com/ratatui-org/ratatui/pull/1009)
|
||||
|
||||
- [d0067c8](https://github.com/ratatui-org/ratatui/commit/d0067c8815d5244d319934d58a9366c8ad36b3e5)
|
||||
*(license)* Update copyright years by @orhun in [#962](https://github.com/ratatui-org/ratatui/pull/962)
|
||||
|
||||
- [88bfb5a](https://github.com/ratatui-org/ratatui/commit/88bfb5a43027cf3410ad560772c5bfdbaa3d58b7)
|
||||
*(text)* Update Text and Line docs by @joshka in [#969](https://github.com/ratatui-org/ratatui/pull/969)
|
||||
|
||||
- [3b002fd](https://github.com/ratatui-org/ratatui/commit/3b002fdcab964ce3f65f55dc8053d9678ae247a3)
|
||||
*(uncategorized)* Update incompatible code warning in examples readme by @joshka in [#1013](https://github.com/ratatui-org/ratatui/pull/1013)
|
||||
|
||||
### Performance
|
||||
|
||||
- [e02f476](https://github.com/ratatui-org/ratatui/commit/e02f4768ce2ee30473200fe98e2687e42acb9c33)
|
||||
*(borders)* Allow border!() in const by @EdJoPaTo in [#977](https://github.com/ratatui-org/ratatui/pull/977)
|
||||
|
||||
````text
|
||||
This allows more compiler optimizations when the macro is used.
|
||||
````
|
||||
|
||||
- [541f0f9](https://github.com/ratatui-org/ratatui/commit/541f0f99538762a07d68a71b2989ecc6ff6f71ef)
|
||||
*(cell)* Use const CompactString::new_inline by @EdJoPaTo in [#979](https://github.com/ratatui-org/ratatui/pull/979)
|
||||
|
||||
````text
|
||||
Some minor find when messing around trying to `const` all the things.
|
||||
|
||||
While `reset()` and `default()` can not be `const` it's still a benefit
|
||||
when their contents are.
|
||||
````
|
||||
|
||||
- [65e7923](https://github.com/ratatui-org/ratatui/commit/65e792375396c3160d76964ef0dfc4fb1e53be41)
|
||||
*(scrollbar)* Const creation by @EdJoPaTo in [#963](https://github.com/ratatui-org/ratatui/pull/963)
|
||||
|
||||
````text
|
||||
A bunch of `const fn` allow for more performance and `Default` now uses the `const` new implementations.
|
||||
````
|
||||
|
||||
- [8195f52](https://github.com/ratatui-org/ratatui/commit/8195f526cb4b321f337dcbe9e689cc7f6eb84065)
|
||||
*(uncategorized)* Clippy::needless_pass_by_value by @EdJoPaTo
|
||||
|
||||
- [183c07e](https://github.com/ratatui-org/ratatui/commit/183c07ef436cbb8fb0bec418042b44b4fedd836f)
|
||||
*(uncategorized)* Clippy::trivially_copy_pass_by_ref by @EdJoPaTo
|
||||
|
||||
- [a13867f](https://github.com/ratatui-org/ratatui/commit/a13867ffceb2f8f57f4540049754c2f916fd3efc)
|
||||
*(uncategorized)* Clippy::cloned_instead_of_copied by @EdJoPaTo
|
||||
|
||||
- [3834374](https://github.com/ratatui-org/ratatui/commit/3834374652b46c5ddbfedcf8dea2086fd762f884)
|
||||
*(uncategorized)* Clippy::missing_const_for_fn by @EdJoPaTo
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- [125ee92](https://github.com/ratatui-org/ratatui/commit/125ee929ee9009b97a270e2e105a3f1167ab13d7)
|
||||
*(docs)* Fix: fix typos in crate documentation by @orhun in [#1002](https://github.com/ratatui-org/ratatui/pull/1002)
|
||||
|
||||
- [38c17e0](https://github.com/ratatui-org/ratatui/commit/38c17e091cf3f4de2d196ecdd6a40129019eafc4)
|
||||
*(editorconfig)* Set and apply some defaults by @EdJoPaTo
|
||||
|
||||
- [07da90a](https://github.com/ratatui-org/ratatui/commit/07da90a7182035b24f870bcbf0a0ffaad75eb48b)
|
||||
*(funding)* Add eth address for receiving funds from drips.network by @BenJam in [#994](https://github.com/ratatui-org/ratatui/pull/994)
|
||||
|
||||
- [078e97e](https://github.com/ratatui-org/ratatui/commit/078e97e4ff65c02afa7c884914ecd38a6e959b58)
|
||||
*(github)* Add EdJoPaTo as a maintainer by @orhun in [#986](https://github.com/ratatui-org/ratatui/pull/986)
|
||||
|
||||
- [b0314c5](https://github.com/ratatui-org/ratatui/commit/b0314c5731b32f51f5b6ca71a5194c6d7f265972)
|
||||
*(uncategorized)* Remove conventional commit check for PR by @Valentin271 in [#950](https://github.com/ratatui-org/ratatui/pull/950)
|
||||
|
||||
````text
|
||||
This removes conventional commit check for PRs.
|
||||
|
||||
Since we use the PR title and description this is useless. It fails a
|
||||
lot of time and we ignore it.
|
||||
|
||||
IMPORTANT NOTE: This does **not** mean Ratatui abandons conventional
|
||||
commits. This only relates to commits in PRs.
|
||||
````
|
||||
|
||||
### Build
|
||||
|
||||
- [6e6ba27](https://github.com/ratatui-org/ratatui/commit/6e6ba27a122560bcf47b0efd20b7095f1bfd8714)
|
||||
*(lint)* Warn on pedantic and allow the rest by @EdJoPaTo
|
||||
|
||||
- [c4ce7e8](https://github.com/ratatui-org/ratatui/commit/c4ce7e8ff6f00875e1ead5b68052f0db737bd44d)
|
||||
*(uncategorized)* Enable more satisfied lints by @EdJoPaTo
|
||||
|
||||
````text
|
||||
These lints dont generate warnings and therefore dont need refactoring.
|
||||
I think they are useful in the future.
|
||||
````
|
||||
|
||||
- [a4e84a6](https://github.com/ratatui-org/ratatui/commit/a4e84a6a7f6f5b80903799028f30e2a4438f2807)
|
||||
*(uncategorized)* Increase msrv to 1.74.0 by @EdJoPaTo [**breaking**]
|
||||
|
||||
````text
|
||||
configure lints in Cargo.toml requires 1.74.0
|
||||
````
|
||||
|
||||
BREAKING CHANGE:rust 1.74 is required now
|
||||
|
||||
### New Contributors
|
||||
|
||||
* @TadoTheMiner made their first contribution in [#987](https://github.com/ratatui-org/ratatui/pull/987)
|
||||
* @BenJam made their first contribution in [#994](https://github.com/ratatui-org/ratatui/pull/994)
|
||||
* @CameronBarnes made their first contribution in [#958](https://github.com/ratatui-org/ratatui/pull/958)
|
||||
* @ThomasMiz made their first contribution in [#959](https://github.com/ratatui-org/ratatui/pull/959)
|
||||
|
||||
**Full Changelog**: https://github.com/ratatui-org/ratatui/compare/v0.26.1...0.26.2
|
||||
|
||||
## [0.26.1](https://github.com/ratatui-org/ratatui/releases/tag/v0.26.1) - 2024-02-12
|
||||
|
||||
This is a patch release that fixes bugs and adds enhancements, including new iterators, title options for blocks, and various rendering improvements. ✨
|
||||
|
||||
@@ -161,7 +611,7 @@ Here is the list of contributors who have contributed to `ratatui` for the first
|
||||
* @mo8it
|
||||
* @m4rch3n1ng
|
||||
|
||||
## [0.26.0](https://github.com/ratatui-org/ratatui/releases/tag/0.26.0) - 2024-02-02
|
||||
## [0.26.0](https://github.com/ratatui-org/ratatui/releases/tag/v0.26.0) - 2024-02-02
|
||||
|
||||
We are excited to announce the new version of `ratatui` - a Rust library that's all about cooking up TUIs 🐭
|
||||
|
||||
@@ -1818,7 +2268,7 @@ Shout out to our new sponsors!
|
||||
* @atuinsh
|
||||
* @JeftavanderHorst!
|
||||
|
||||
## [0.25.0](https://github.com/ratatui-org/ratatui/releases/tag/0.25.0) - 2023-12-18
|
||||
## [0.25.0](https://github.com/ratatui-org/ratatui/releases/tag/v0.25.0) - 2023-12-18
|
||||
|
||||
We are thrilled to announce the new version of `ratatui` - a Rust library that's all about cooking up TUIs 🐭
|
||||
|
||||
@@ -2303,7 +2753,7 @@ Here is the list of contributors who have contributed to `ratatui` for the first
|
||||
* @YeungKC
|
||||
* @lyuha
|
||||
|
||||
## [0.24.0](https://github.com/ratatui-org/ratatui/releases/tag/0.24.0) - 2023-10-23
|
||||
## [0.24.0](https://github.com/ratatui-org/ratatui/releases/tag/v0.24.0) - 2023-10-23
|
||||
|
||||
We are excited to announce the new version of `ratatui` - a Rust library that's all about cooking up TUIs 🐭
|
||||
|
||||
|
||||
34
Cargo.toml
34
Cargo.toml
@@ -1,11 +1,13 @@
|
||||
[package]
|
||||
name = "ratatui"
|
||||
version = "0.26.1" # crate version
|
||||
version = "0.26.2" # crate version
|
||||
authors = ["Florian Dehau <work@fdehau.com>", "The Ratatui Developers"]
|
||||
description = "A library that's all about cooking up terminal user interfaces"
|
||||
documentation = "https://docs.rs/ratatui/latest/ratatui/"
|
||||
keywords = ["tui", "terminal", "dashboard"]
|
||||
repository = "https://github.com/ratatui-org/ratatui"
|
||||
homepage = "https://ratatui.rs"
|
||||
keywords = ["tui", "terminal", "dashboard"]
|
||||
categories = ["command-line-interface"]
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
exclude = [
|
||||
@@ -23,24 +25,23 @@ rust-version = "1.74.0"
|
||||
[badges]
|
||||
|
||||
[dependencies]
|
||||
crossterm = { version = "0.27", optional = true }
|
||||
termion = { version = "3.0", optional = true }
|
||||
termwiz = { version = "0.22.0", optional = true }
|
||||
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
bitflags = "2.3"
|
||||
cassowary = "0.3"
|
||||
indoc = "2.0"
|
||||
compact_str = "0.7.1"
|
||||
crossterm = { version = "0.27", optional = true }
|
||||
document-features = { version = "0.2.7", optional = true }
|
||||
itertools = "0.12"
|
||||
lru = "0.12.0"
|
||||
paste = "1.0.2"
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
stability = "0.2.0"
|
||||
strum = { version = "0.26", features = ["derive"] }
|
||||
termion = { version = "3.0", optional = true }
|
||||
termwiz = { version = "0.22.0", optional = true }
|
||||
time = { version = "0.3.11", optional = true, features = ["local-offset"] }
|
||||
unicode-segmentation = "1.10"
|
||||
unicode-truncate = "1"
|
||||
unicode-width = "0.1"
|
||||
document-features = { version = "0.2.7", optional = true }
|
||||
lru = "0.12.0"
|
||||
stability = "0.1.1"
|
||||
compact_str = "0.7.1"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.71"
|
||||
@@ -54,16 +55,19 @@ criterion = { version = "0.5.1", features = ["html_reports"] }
|
||||
derive_builder = "0.20.0"
|
||||
fakeit = "1.1"
|
||||
font8x8 = "0.3.1"
|
||||
indoc = "2"
|
||||
palette = "0.7.3"
|
||||
pretty_assertions = "1.4.0"
|
||||
rand = "0.8.5"
|
||||
rand_chacha = "0.3.1"
|
||||
rstest = "0.18.2"
|
||||
rstest = "0.19.0"
|
||||
serde_json = "1.0.109"
|
||||
|
||||
[lints.rust]
|
||||
unsafe_code = "forbid"
|
||||
|
||||
[lints.clippy]
|
||||
cargo = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
cast_possible_truncation = "allow"
|
||||
cast_possible_wrap = "allow"
|
||||
@@ -159,6 +163,10 @@ harness = false
|
||||
name = "block"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "line"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "list"
|
||||
harness = false
|
||||
|
||||
@@ -7,11 +7,12 @@ skip_core_tasks = true
|
||||
# all features except the backend ones
|
||||
ALL_FEATURES = "all-widgets,macros,serde"
|
||||
|
||||
[env.ALL_FEATURES_FLAG]
|
||||
# Windows does not support building termion, so this avoids the build failure by providing two
|
||||
# sets of flags, one for Windows and one for other platforms.
|
||||
# Windows: --features=all-widgets,macros,serde,crossterm,termwiz,underline-color
|
||||
# Other: --features=all-widgets,macros,serde,crossterm,termion,termwiz,underline-color
|
||||
ALL_FEATURES_FLAG = { source = "${CARGO_MAKE_RUST_TARGET_OS}", default_value = "--features=all-widgets,macros,serde,crossterm,termion,termwiz,unstable", mapping = { "windows" = "--features=all-widgets,macros,serde,crossterm,termwiz,unstable" } }
|
||||
source = "${CARGO_MAKE_RUST_TARGET_OS}"
|
||||
default_value = "--features=all-widgets,macros,serde,crossterm,termion,termwiz,underline-color,unstable"
|
||||
mapping = { "windows" = "--features=all-widgets,macros,serde,crossterm,termwiz,underline-color,unstable" }
|
||||
|
||||
[tasks.default]
|
||||
alias = "ci"
|
||||
|
||||
23
README.md
23
README.md
@@ -162,7 +162,7 @@ fn handle_events() -> io::Result<bool> {
|
||||
fn ui(frame: &mut Frame) {
|
||||
frame.render_widget(
|
||||
Paragraph::new("Hello World!")
|
||||
.block(Block::default().title("Greeting").borders(Borders::ALL)),
|
||||
.block(Block::bordered().title("Greeting")),
|
||||
frame.size(),
|
||||
);
|
||||
}
|
||||
@@ -207,11 +207,11 @@ fn ui(frame: &mut Frame) {
|
||||
)
|
||||
.split(main_layout[1]);
|
||||
frame.render_widget(
|
||||
Block::default().borders(Borders::ALL).title("Left"),
|
||||
Block::bordered().title("Left"),
|
||||
inner_layout[0],
|
||||
);
|
||||
frame.render_widget(
|
||||
Block::default().borders(Borders::ALL).title("Right"),
|
||||
Block::bordered().title("Right"),
|
||||
inner_layout[1],
|
||||
);
|
||||
}
|
||||
@@ -327,24 +327,23 @@ Running this example produces the following output:
|
||||
[Termwiz]: https://crates.io/crates/termwiz
|
||||
[tui-rs]: https://crates.io/crates/tui
|
||||
[GitHub Sponsors]: https://github.com/sponsors/ratatui-org
|
||||
[Crate Badge]: https://img.shields.io/crates/v/ratatui?logo=rust&style=flat-square
|
||||
[License Badge]: https://img.shields.io/crates/l/ratatui?style=flat-square
|
||||
[CI Badge]:
|
||||
https://img.shields.io/github/actions/workflow/status/ratatui-org/ratatui/ci.yml?style=flat-square&logo=github
|
||||
[Crate Badge]: https://img.shields.io/crates/v/ratatui?logo=rust&style=flat-square&logoColor=E05D44&color=E05D44
|
||||
[License Badge]: https://img.shields.io/crates/l/ratatui?style=flat-square&color=1370D3
|
||||
[CI Badge]: https://img.shields.io/github/actions/workflow/status/ratatui-org/ratatui/ci.yml?style=flat-square&logo=github
|
||||
[CI Workflow]: https://github.com/ratatui-org/ratatui/actions/workflows/ci.yml
|
||||
[Codecov Badge]:
|
||||
https://img.shields.io/codecov/c/github/ratatui-org/ratatui?logo=codecov&style=flat-square&token=BAQ8SOKEST
|
||||
https://img.shields.io/codecov/c/github/ratatui-org/ratatui?logo=codecov&style=flat-square&token=BAQ8SOKEST&color=C43AC3&logoColor=C43AC3
|
||||
[Codecov]: https://app.codecov.io/gh/ratatui-org/ratatui
|
||||
[Deps.rs Badge]: https://deps.rs/repo/github/ratatui-org/ratatui/status.svg?style=flat-square
|
||||
[Deps.rs]: https://deps.rs/repo/github/ratatui-org/ratatui
|
||||
[Discord Badge]:
|
||||
https://img.shields.io/discord/1070692720437383208?label=discord&logo=discord&style=flat-square
|
||||
https://img.shields.io/discord/1070692720437383208?label=discord&logo=discord&style=flat-square&color=1370D3&logoColor=1370D3
|
||||
[Discord Server]: https://discord.gg/pMCEU9hNEj
|
||||
[Docs Badge]: https://img.shields.io/docsrs/ratatui?logo=rust&style=flat-square
|
||||
[Docs Badge]: https://img.shields.io/docsrs/ratatui?logo=rust&style=flat-square&logoColor=E05D44
|
||||
[Matrix Badge]:
|
||||
https://img.shields.io/matrix/ratatui-general%3Amatrix.org?style=flat-square&logo=matrix&label=Matrix
|
||||
https://img.shields.io/matrix/ratatui-general%3Amatrix.org?style=flat-square&logo=matrix&label=Matrix&color=C43AC3
|
||||
[Matrix]: https://matrix.to/#/#ratatui:matrix.org
|
||||
[Sponsors Badge]: https://img.shields.io/github/sponsors/ratatui-org?logo=github&style=flat-square
|
||||
[Sponsors Badge]: https://img.shields.io/github/sponsors/ratatui-org?logo=github&style=flat-square&color=1370D3
|
||||
|
||||
<!-- cargo-rdme end -->
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use criterion::{criterion_group, criterion_main, BatchSize, Bencher, BenchmarkId, Criterion};
|
||||
use criterion::{criterion_group, criterion_main, BatchSize, Bencher, Criterion};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
prelude::Alignment,
|
||||
layout::{Alignment, Rect},
|
||||
widgets::{
|
||||
block::{Position, Title},
|
||||
Block, Borders, Padding, Widget,
|
||||
Block, Padding, Widget,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,32 +12,31 @@ use ratatui::{
|
||||
fn block(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("block");
|
||||
|
||||
for buffer_size in [
|
||||
Rect::new(0, 0, 100, 50), // vertically split screen
|
||||
Rect::new(0, 0, 200, 50), // 1080p fullscreen with medium font
|
||||
Rect::new(0, 0, 256, 256), // Max sized area
|
||||
for (width, height) in [
|
||||
(100, 50), // vertically split screen
|
||||
(200, 50), // 1080p fullscreen with medium font
|
||||
(256, 256), // Max sized area
|
||||
] {
|
||||
let buffer_area = buffer_size.area();
|
||||
let buffer_size = Rect::new(0, 0, width, height);
|
||||
|
||||
// Render an empty block
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("render_empty", buffer_area),
|
||||
format!("render_empty/{width}x{height}"),
|
||||
&Block::new(),
|
||||
|b, block| render(b, block, buffer_size),
|
||||
);
|
||||
|
||||
// Render with all features
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("render_all_feature", buffer_area),
|
||||
&Block::new()
|
||||
.borders(Borders::ALL)
|
||||
format!("render_all_feature/{width}x{height}"),
|
||||
&Block::bordered()
|
||||
.padding(Padding::new(5, 5, 2, 2))
|
||||
.title("test title")
|
||||
.title(
|
||||
Title::from("bottom left title")
|
||||
.alignment(Alignment::Right)
|
||||
.position(Position::Bottom),
|
||||
)
|
||||
.padding(Padding::new(5, 5, 2, 2)),
|
||||
),
|
||||
|b, block| render(b, block, buffer_size),
|
||||
);
|
||||
}
|
||||
|
||||
39
benches/line.rs
Normal file
39
benches/line.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use std::hint::black_box;
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Rect},
|
||||
style::Stylize,
|
||||
text::Line,
|
||||
widgets::Widget,
|
||||
};
|
||||
|
||||
fn line_render(criterion: &mut Criterion) {
|
||||
for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
|
||||
let mut group = criterion.benchmark_group(format!("line_render/{alignment}"));
|
||||
group.sample_size(1000);
|
||||
|
||||
let line = &Line::from(vec![
|
||||
"This".red(),
|
||||
" ".green(),
|
||||
"is".italic(),
|
||||
" ".blue(),
|
||||
"SPARTA!!".bold(),
|
||||
])
|
||||
.alignment(alignment);
|
||||
|
||||
for width in [0, 3, 4, 6, 7, 10, 42] {
|
||||
let area = Rect::new(0, 0, width, 1);
|
||||
|
||||
group.bench_function(width.to_string(), |bencher| {
|
||||
let mut buffer = Buffer::empty(area);
|
||||
bencher.iter(|| black_box(line).render(area, &mut buffer));
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(benches, line_render);
|
||||
criterion_main!(benches);
|
||||
37
cliff.toml
37
cliff.toml
@@ -1,4 +1,9 @@
|
||||
# configuration for https://github.com/orhun/git-cliff
|
||||
# git-cliff ~ configuration file
|
||||
# https://git-cliff.org/docs/configuration
|
||||
|
||||
[remote.github]
|
||||
owner = "ratatui-org"
|
||||
repo = "ratatui"
|
||||
|
||||
[changelog]
|
||||
# changelog header
|
||||
@@ -21,8 +26,10 @@ body = """
|
||||
{% endif -%}
|
||||
|
||||
{% macro commit(commit) -%}
|
||||
- [{{ commit.id | truncate(length=7, end="") }}]({{ "https://github.com/ratatui-org/ratatui/commit/" ~ commit.id }})
|
||||
*({{commit.scope | default(value = "uncategorized") | lower }})* {{ commit.message | upper_first }}
|
||||
- [{{ commit.id | truncate(length=7, end="") }}]({{ "https://github.com/ratatui-org/ratatui/commit/" ~ commit.id }}) \
|
||||
*({{commit.scope | default(value = "uncategorized") | lower }})* {{ commit.message | upper_first | trim }}\
|
||||
{% if commit.github.username %} by @{{ commit.github.username }}{%- endif -%}\
|
||||
{% if commit.github.pr_number %} in [#{{ commit.github.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.github.pr_number }}){%- endif %}\
|
||||
{%- if commit.breaking %} [**breaking**]{% endif %}
|
||||
{%- if commit.body %}
|
||||
|
||||
@@ -49,6 +56,28 @@ body = """
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- endfor %}
|
||||
|
||||
{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
|
||||
### New Contributors
|
||||
{%- endif %}\
|
||||
{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
|
||||
* @{{ contributor.username }} made their first contribution
|
||||
{%- if contributor.pr_number %} in \
|
||||
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
|
||||
{%- endif %}
|
||||
{%- endfor -%}
|
||||
|
||||
{% if version %}
|
||||
{% if previous.version %}
|
||||
**Full Changelog**: {{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }}
|
||||
{% endif %}
|
||||
{% else -%}
|
||||
{% raw %}\n{% endraw %}
|
||||
{% endif %}
|
||||
|
||||
{%- macro remote_url() -%}
|
||||
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}\
|
||||
{% endmacro %}
|
||||
"""
|
||||
|
||||
|
||||
@@ -68,7 +97,7 @@ filter_unconventional = true
|
||||
split_commits = false
|
||||
# regex for preprocessing the commit messages
|
||||
commit_preprocessors = [
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/ratatui-org/ratatui/issues/${2}))" },
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" },
|
||||
{ pattern = '(better safe shared layout cache)', replace = "perf(layout): ${1}" },
|
||||
{ pattern = '(Clarify README.md)', replace = "docs(readme): ${1}" },
|
||||
{ pattern = '(Update README.md)', replace = "docs(readme): ${1}" },
|
||||
|
||||
17
clippy.toml
Normal file
17
clippy.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
avoid-breaking-exported-api = false
|
||||
|
||||
# https://rust-lang.github.io/rust-clippy/master/index.html#/multiple_crate_versions
|
||||
# ratatui -> bitflags v2.3
|
||||
# termwiz -> wezterm-blob-leases -> mac_address -> nix -> bitflags v1.3.2
|
||||
# crossterm -> all the windows- deps https://github.com/ratatui-org/ratatui/pull/1064#issuecomment-2078848980
|
||||
allowed-duplicate-crates = [
|
||||
"bitflags",
|
||||
"windows-targets",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
@@ -1,20 +1,20 @@
|
||||
# Examples
|
||||
|
||||
This folder contains unreleased code. View the [examples for the latest release
|
||||
(0.25.0)](https://github.com/ratatui-org/ratatui/tree/v0.25.0/examples) instead.
|
||||
This folder might use unreleased code. View the examples for the latest release instead.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> There are backwards incompatible changes in these examples, as they are designed to compile
|
||||
> There may be backwards incompatible changes in these examples, as they are designed to compile
|
||||
> against the `main` branch.
|
||||
>
|
||||
> There are a few workaround for this problem:
|
||||
>
|
||||
> - View the examples as they were when the latest version was release by selecting the tag that
|
||||
> matches that version. E.g. <https://github.com/ratatui-org/ratatui/tree/v0.25.0/examples>. There
|
||||
> is a combo box at the top of this page which allows you to select any previous tagged version.
|
||||
> - To view the code locally, checkout the tag using `git switch --detach v0.25.0`.
|
||||
> - Use the latest [alpha version of Ratatui]. These are released weekly on Saturdays.
|
||||
> matches that version. E.g. <https://github.com/ratatui-org/ratatui/tree/v0.26.1/examples>.
|
||||
> - If you're viewing this file on GitHub, there is a combo box at the top of this page which
|
||||
> allows you to select any previous tagged version.
|
||||
> - To view the code locally, checkout the tag. E.g. `git switch --detach v0.26.1`.
|
||||
> - Use the latest [alpha version of Ratatui] in your app. These are released weekly on Saturdays.
|
||||
> - Compile your code against the main branch either locally by adding e.g. `path = "../ratatui"` to
|
||||
> the dependency, or remotely by adding `git = "https://github.com/ratatui-org/ratatui"`
|
||||
>
|
||||
|
||||
@@ -26,7 +26,7 @@ use crossterm::{
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Bar, BarChart, BarGroup, Block, Borders, Paragraph},
|
||||
widgets::{Bar, BarChart, BarGroup, Block, Paragraph},
|
||||
};
|
||||
|
||||
struct Company<'a> {
|
||||
@@ -159,7 +159,7 @@ fn ui(frame: &mut Frame, app: &App) {
|
||||
let [left, right] = horizontal.areas(bottom);
|
||||
|
||||
let barchart = BarChart::default()
|
||||
.block(Block::default().title("Data1").borders(Borders::ALL))
|
||||
.block(Block::bordered().title("Data1"))
|
||||
.data(&app.data)
|
||||
.bar_width(9)
|
||||
.bar_style(Style::default().fg(Color::Yellow))
|
||||
@@ -217,7 +217,7 @@ fn draw_bar_with_group_labels(f: &mut Frame, app: &App, area: Rect) {
|
||||
let groups = create_groups(app, false);
|
||||
|
||||
let mut barchart = BarChart::default()
|
||||
.block(Block::default().title("Data1").borders(Borders::ALL))
|
||||
.block(Block::bordered().title("Data1"))
|
||||
.bar_width(7)
|
||||
.group_gap(3);
|
||||
|
||||
@@ -246,7 +246,7 @@ fn draw_horizontal_bars(f: &mut Frame, app: &App, area: Rect) {
|
||||
let groups = create_groups(app, true);
|
||||
|
||||
let mut barchart = BarChart::default()
|
||||
.block(Block::default().title("Data1").borders(Borders::ALL))
|
||||
.block(Block::bordered().title("Data1"))
|
||||
.bar_width(1)
|
||||
.group_gap(1)
|
||||
.bar_gap(0)
|
||||
@@ -286,15 +286,13 @@ fn draw_legend(f: &mut Frame, area: Rect) {
|
||||
"- Company B",
|
||||
Style::default().fg(Color::Yellow),
|
||||
)),
|
||||
Line::from(vec![Span::styled(
|
||||
Line::from(Span::styled(
|
||||
"- Company C",
|
||||
Style::default().fg(Color::White),
|
||||
)]),
|
||||
)),
|
||||
];
|
||||
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default().fg(Color::White));
|
||||
let block = Block::bordered().style(Style::default().fg(Color::White));
|
||||
let paragraph = Paragraph::new(text).block(block);
|
||||
f.render_widget(paragraph, area);
|
||||
}
|
||||
|
||||
@@ -160,23 +160,20 @@ fn render_border_type(
|
||||
frame: &mut Frame,
|
||||
area: Rect,
|
||||
) {
|
||||
let block = Block::new()
|
||||
.borders(Borders::ALL)
|
||||
let block = Block::bordered()
|
||||
.border_type(border_type)
|
||||
.title(format!("BorderType::{border_type:#?}"));
|
||||
frame.render_widget(paragraph.clone().block(block), area);
|
||||
}
|
||||
fn render_styled_borders(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||
let block = Block::new()
|
||||
.borders(Borders::ALL)
|
||||
let block = Block::bordered()
|
||||
.border_style(Style::new().blue().on_white().bold().italic())
|
||||
.title("Styled borders");
|
||||
frame.render_widget(paragraph.clone().block(block), area);
|
||||
}
|
||||
|
||||
fn render_styled_block(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||
let block = Block::new()
|
||||
.borders(Borders::ALL)
|
||||
let block = Block::bordered()
|
||||
.style(Style::new().blue().on_white().bold().italic())
|
||||
.title("Styled block");
|
||||
frame.render_widget(paragraph.clone().block(block), area);
|
||||
@@ -184,8 +181,7 @@ fn render_styled_block(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||
|
||||
// Note: this currently renders incorrectly, see https://github.com/ratatui-org/ratatui/issues/349
|
||||
fn render_styled_title(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||
let block = Block::new()
|
||||
.borders(Borders::ALL)
|
||||
let block = Block::bordered()
|
||||
.title("Styled title")
|
||||
.title_style(Style::new().blue().on_white().bold().italic());
|
||||
frame.render_widget(paragraph.clone().block(block), area);
|
||||
@@ -196,21 +192,19 @@ fn render_styled_title_content(paragraph: &Paragraph, frame: &mut Frame, area: R
|
||||
"Styled ".blue().on_white().bold().italic(),
|
||||
"title content".red().on_white().bold().italic(),
|
||||
]);
|
||||
let block = Block::new().borders(Borders::ALL).title(title);
|
||||
let block = Block::bordered().title(title);
|
||||
frame.render_widget(paragraph.clone().block(block), area);
|
||||
}
|
||||
|
||||
fn render_multiple_titles(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||
let block = Block::new()
|
||||
.borders(Borders::ALL)
|
||||
let block = Block::bordered()
|
||||
.title("Multiple".blue().on_white().bold().italic())
|
||||
.title("Titles".red().on_white().bold().italic());
|
||||
frame.render_widget(paragraph.clone().block(block), area);
|
||||
}
|
||||
|
||||
fn render_multiple_title_positions(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||
let block = Block::new()
|
||||
.borders(Borders::ALL)
|
||||
let block = Block::bordered()
|
||||
.title(
|
||||
Title::from("top left")
|
||||
.position(Position::Top)
|
||||
@@ -245,16 +239,15 @@ fn render_multiple_title_positions(paragraph: &Paragraph, frame: &mut Frame, are
|
||||
}
|
||||
|
||||
fn render_padding(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||
let block = Block::new()
|
||||
.borders(Borders::ALL)
|
||||
.title("Padding")
|
||||
.padding(Padding::new(5, 10, 1, 2));
|
||||
let block = Block::bordered()
|
||||
.padding(Padding::new(5, 10, 1, 2))
|
||||
.title("Padding");
|
||||
frame.render_widget(paragraph.clone().block(block), area);
|
||||
}
|
||||
|
||||
fn render_nested_blocks(paragraph: &Paragraph, frame: &mut Frame, area: Rect) {
|
||||
let outer_block = Block::new().borders(Borders::ALL).title("Outer block");
|
||||
let inner_block = Block::new().borders(Borders::ALL).title("Inner block");
|
||||
let outer_block = Block::bordered().title("Outer block");
|
||||
let inner_block = Block::bordered().title("Inner block");
|
||||
let inner = outer_block.inner(area);
|
||||
frame.render_widget(outer_block, area);
|
||||
frame.render_widget(paragraph.clone().block(inner_block), inner);
|
||||
|
||||
@@ -137,7 +137,7 @@ impl App {
|
||||
|
||||
fn map_canvas(&self) -> impl Widget + '_ {
|
||||
Canvas::default()
|
||||
.block(Block::default().borders(Borders::ALL).title("World"))
|
||||
.block(Block::bordered().title("World"))
|
||||
.marker(self.marker)
|
||||
.paint(|ctx| {
|
||||
ctx.draw(&Map {
|
||||
@@ -152,7 +152,7 @@ impl App {
|
||||
|
||||
fn pong_canvas(&self) -> impl Widget + '_ {
|
||||
Canvas::default()
|
||||
.block(Block::default().borders(Borders::ALL).title("Pong"))
|
||||
.block(Block::bordered().title("Pong"))
|
||||
.marker(self.marker)
|
||||
.paint(|ctx| {
|
||||
ctx.draw(&self.ball);
|
||||
@@ -167,7 +167,7 @@ impl App {
|
||||
let bottom = 0.0;
|
||||
let top = f64::from(area.height).mul_add(2.0, -4.0);
|
||||
Canvas::default()
|
||||
.block(Block::default().borders(Borders::ALL).title("Rects"))
|
||||
.block(Block::bordered().title("Rects"))
|
||||
.marker(self.marker)
|
||||
.x_bounds([left, right])
|
||||
.y_bounds([bottom, top])
|
||||
|
||||
@@ -26,7 +26,7 @@ use crossterm::{
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{block::Title, Axis, Block, Borders, Chart, Dataset, GraphType, LegendPosition},
|
||||
widgets::{block::Title, Axis, Block, Chart, Dataset, GraphType, LegendPosition},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -184,11 +184,7 @@ fn render_chart1(f: &mut Frame, area: Rect, app: &App) {
|
||||
];
|
||||
|
||||
let chart = Chart::new(datasets)
|
||||
.block(
|
||||
Block::default()
|
||||
.title("Chart 1".cyan().bold())
|
||||
.borders(Borders::ALL),
|
||||
)
|
||||
.block(Block::bordered().title("Chart 1".cyan().bold()))
|
||||
.x_axis(
|
||||
Axis::default()
|
||||
.title("X Axis")
|
||||
@@ -217,13 +213,11 @@ fn render_line_chart(f: &mut Frame, area: Rect) {
|
||||
|
||||
let chart = Chart::new(datasets)
|
||||
.block(
|
||||
Block::default()
|
||||
.title(
|
||||
Title::default()
|
||||
.content("Line chart".cyan().bold())
|
||||
.alignment(Alignment::Center),
|
||||
)
|
||||
.borders(Borders::ALL),
|
||||
Block::bordered().title(
|
||||
Title::default()
|
||||
.content("Line chart".cyan().bold())
|
||||
.alignment(Alignment::Center),
|
||||
),
|
||||
)
|
||||
.x_axis(
|
||||
Axis::default()
|
||||
@@ -269,7 +263,7 @@ fn render_scatter(f: &mut Frame, area: Rect) {
|
||||
|
||||
let chart = Chart::new(datasets)
|
||||
.block(
|
||||
Block::new().borders(Borders::all()).title(
|
||||
Block::bordered().title(
|
||||
Title::default()
|
||||
.content("Scatter chart".cyan().bold())
|
||||
.alignment(Alignment::Center),
|
||||
|
||||
@@ -230,12 +230,12 @@ fn render_indexed_colors(frame: &mut Frame, area: Rect) {
|
||||
}
|
||||
|
||||
fn title_block(title: String) -> Block<'static> {
|
||||
Block::default()
|
||||
Block::new()
|
||||
.borders(Borders::TOP)
|
||||
.border_style(Style::new().dark_gray())
|
||||
.title(title)
|
||||
.title_alignment(Alignment::Center)
|
||||
.border_style(Style::new().dark_gray())
|
||||
.title_style(Style::new().reset())
|
||||
.title(title)
|
||||
}
|
||||
|
||||
fn render_indexed_grayscale(frame: &mut Frame, area: Rect) {
|
||||
|
||||
@@ -436,7 +436,7 @@ impl ConstraintBlock {
|
||||
} else {
|
||||
main_color
|
||||
};
|
||||
Block::default()
|
||||
Block::new()
|
||||
.fg(Self::TEXT_COLOR)
|
||||
.bg(selected_color)
|
||||
.render(area, buf);
|
||||
|
||||
@@ -19,7 +19,7 @@ use std::io::{self, stdout};
|
||||
|
||||
use color_eyre::{config::HookBuilder, Result};
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
};
|
||||
@@ -109,6 +109,9 @@ impl App {
|
||||
fn handle_events(&mut self) -> Result<()> {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
use KeyCode::*;
|
||||
if key.kind != KeyEventKind::Press {
|
||||
return Ok(());
|
||||
}
|
||||
match key.code {
|
||||
Char('q') | Esc => self.quit(),
|
||||
Char('l') | Right => self.next(),
|
||||
@@ -194,7 +197,7 @@ impl App {
|
||||
);
|
||||
Paragraph::new(width_bar.dark_gray())
|
||||
.centered()
|
||||
.block(Block::default().padding(Padding {
|
||||
.block(Block::new().padding(Padding {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 1,
|
||||
|
||||
@@ -14,7 +14,7 @@ pub fn draw(f: &mut Frame, app: &mut App) {
|
||||
.iter()
|
||||
.map(|t| text::Line::from(Span::styled(*t, Style::default().fg(Color::Green))))
|
||||
.collect::<Tabs>()
|
||||
.block(Block::default().borders(Borders::ALL).title(app.title))
|
||||
.block(Block::bordered().title(app.title))
|
||||
.highlight_style(Style::default().fg(Color::Yellow))
|
||||
.select(app.tabs.index);
|
||||
f.render_widget(tabs, chunks[0]);
|
||||
@@ -46,12 +46,12 @@ fn draw_gauges(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
])
|
||||
.margin(1)
|
||||
.split(area);
|
||||
let block = Block::default().borders(Borders::ALL).title("Graphs");
|
||||
let block = Block::bordered().title("Graphs");
|
||||
f.render_widget(block, area);
|
||||
|
||||
let label = format!("{:.2}%", app.progress * 100.0);
|
||||
let gauge = Gauge::default()
|
||||
.block(Block::default().title("Gauge:"))
|
||||
.block(Block::new().title("Gauge:"))
|
||||
.gauge_style(
|
||||
Style::default()
|
||||
.fg(Color::Magenta)
|
||||
@@ -64,7 +64,7 @@ fn draw_gauges(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
f.render_widget(gauge, chunks[0]);
|
||||
|
||||
let sparkline = Sparkline::default()
|
||||
.block(Block::default().title("Sparkline:"))
|
||||
.block(Block::new().title("Sparkline:"))
|
||||
.style(Style::default().fg(Color::Green))
|
||||
.data(&app.sparkline.points)
|
||||
.bar_set(if app.enhanced_graphics {
|
||||
@@ -75,7 +75,7 @@ fn draw_gauges(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
f.render_widget(sparkline, chunks[1]);
|
||||
|
||||
let line_gauge = LineGauge::default()
|
||||
.block(Block::default().title("LineGauge:"))
|
||||
.block(Block::new().title("LineGauge:"))
|
||||
.gauge_style(Style::default().fg(Color::Magenta))
|
||||
.line_set(if app.enhanced_graphics {
|
||||
symbols::line::THICK
|
||||
@@ -110,7 +110,7 @@ fn draw_charts(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
.map(|i| ListItem::new(vec![text::Line::from(Span::raw(*i))]))
|
||||
.collect();
|
||||
let tasks = List::new(tasks)
|
||||
.block(Block::default().borders(Borders::ALL).title("List"))
|
||||
.block(Block::bordered().title("List"))
|
||||
.highlight_style(Style::default().add_modifier(Modifier::BOLD))
|
||||
.highlight_symbol("> ");
|
||||
f.render_stateful_widget(tasks, chunks[0], &mut app.tasks.state);
|
||||
@@ -138,12 +138,12 @@ fn draw_charts(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
ListItem::new(content)
|
||||
})
|
||||
.collect();
|
||||
let logs = List::new(logs).block(Block::default().borders(Borders::ALL).title("List"));
|
||||
let logs = List::new(logs).block(Block::bordered().title("List"));
|
||||
f.render_stateful_widget(logs, chunks[1], &mut app.logs.state);
|
||||
}
|
||||
|
||||
let barchart = BarChart::default()
|
||||
.block(Block::default().borders(Borders::ALL).title("Bar chart"))
|
||||
.block(Block::bordered().title("Bar chart"))
|
||||
.data(&app.barchart)
|
||||
.bar_width(3)
|
||||
.bar_gap(2)
|
||||
@@ -195,14 +195,12 @@ fn draw_charts(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
];
|
||||
let chart = Chart::new(datasets)
|
||||
.block(
|
||||
Block::default()
|
||||
.title(Span::styled(
|
||||
"Chart",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
))
|
||||
.borders(Borders::ALL),
|
||||
Block::bordered().title(Span::styled(
|
||||
"Chart",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)),
|
||||
)
|
||||
.x_axis(
|
||||
Axis::default()
|
||||
@@ -254,7 +252,7 @@ fn draw_text(f: &mut Frame, area: Rect) {
|
||||
"One more thing is that it should display unicode characters: 10€"
|
||||
),
|
||||
];
|
||||
let block = Block::default().borders(Borders::ALL).title(Span::styled(
|
||||
let block = Block::bordered().title(Span::styled(
|
||||
"Footer",
|
||||
Style::default()
|
||||
.fg(Color::Magenta)
|
||||
@@ -292,11 +290,11 @@ fn draw_second_tab(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
.style(Style::default().fg(Color::Yellow))
|
||||
.bottom_margin(1),
|
||||
)
|
||||
.block(Block::default().title("Servers").borders(Borders::ALL));
|
||||
.block(Block::bordered().title("Servers"));
|
||||
f.render_widget(table, chunks[0]);
|
||||
|
||||
let map = Canvas::default()
|
||||
.block(Block::default().title("World").borders(Borders::ALL))
|
||||
.block(Block::bordered().title("World"))
|
||||
.paint(|ctx| {
|
||||
ctx.draw(&Map {
|
||||
color: Color::White,
|
||||
@@ -390,6 +388,6 @@ fn draw_third_tab(f: &mut Frame, _app: &mut App, area: Rect) {
|
||||
Constraint::Ratio(1, 3),
|
||||
],
|
||||
)
|
||||
.block(Block::default().title("Colors").borders(Borders::ALL));
|
||||
.block(Block::bordered().title("Colors"));
|
||||
f.render_widget(table, chunks[0]);
|
||||
}
|
||||
|
||||
@@ -38,14 +38,10 @@ enum Tab {
|
||||
}
|
||||
|
||||
pub fn run(terminal: &mut Terminal<impl Backend>) -> Result<()> {
|
||||
App::new().run(terminal)
|
||||
App::default().run(terminal)
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Run the app until the user quits.
|
||||
pub fn run(&mut self, terminal: &mut Terminal<impl Backend>) -> Result<()> {
|
||||
while self.is_running() {
|
||||
|
||||
@@ -276,8 +276,6 @@ fn render_glyph(glyph: [u8; 8], area: Rect, buf: &mut Buffer, pixel_size: PixelS
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ratatui::assert_buffer_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
@@ -308,7 +306,7 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 80, 8));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
" ████ ██ ███ ████ ██ ",
|
||||
"██ ██ ██ ██ ",
|
||||
"███ ███ █████ ███ ██ ██ ████ ██ ███ █████ ████ ",
|
||||
@@ -318,7 +316,7 @@ mod tests {
|
||||
" ████ ████ ██ ██ ██ ████ ████ ███████ ████ ██ ██ ████ ",
|
||||
" █████ ",
|
||||
]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -329,7 +327,7 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 70, 6));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
"██████ █ ███",
|
||||
"█ ██ █ ██ ██",
|
||||
" ██ ██ ███ ██ ██ █████ ████ ████ █████ ████ ██",
|
||||
@@ -337,7 +335,7 @@ mod tests {
|
||||
" ██ ██ ██ ██ ██ ██ ██ ██ █████ ██ ██████ ██ ██",
|
||||
" ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ██ ██",
|
||||
]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -348,7 +346,7 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 16));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
"██ ██ ███ █ ██ ",
|
||||
"███ ███ ██ ██ ",
|
||||
"███████ ██ ██ ██ █████ ███ ",
|
||||
@@ -366,7 +364,7 @@ mod tests {
|
||||
"███████ ████ ██ ██ ████ █████ ",
|
||||
" ",
|
||||
]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -378,18 +376,17 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 48, 8));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
" ████ █ ███ ███ ",
|
||||
"██ ██ ██ ██ ██ ",
|
||||
"███ █████ ██ ██ ██ ████ ██ ",
|
||||
" ███ ██ ██ ██ ██ ██ ██ █████ ",
|
||||
" ███ ██ ██ ██ ██ ██████ ██ ██ ",
|
||||
"██ ██ ██ █ █████ ██ ██ ██ ██ ",
|
||||
" ████ ██ ██ ████ ████ ███ ██ ",
|
||||
" █████ ",
|
||||
let expected = Buffer::with_lines([
|
||||
" ████ █ ███ ███ ".bold(),
|
||||
"██ ██ ██ ██ ██ ".bold(),
|
||||
"███ █████ ██ ██ ██ ████ ██ ".bold(),
|
||||
" ███ ██ ██ ██ ██ ██ ██ █████ ".bold(),
|
||||
" ███ ██ ██ ██ ██ ██████ ██ ██ ".bold(),
|
||||
"██ ██ ██ █ █████ ██ ██ ██ ██ ".bold(),
|
||||
" ████ ██ ██ ████ ████ ███ ██ ".bold(),
|
||||
" █████ ".bold(),
|
||||
]);
|
||||
expected.set_style(Rect::new(0, 0, 48, 8), Style::new().bold());
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -404,7 +401,7 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 24));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
"██████ ███ ",
|
||||
" ██ ██ ██ ",
|
||||
" ██ ██ ████ ██ ",
|
||||
@@ -433,7 +430,7 @@ mod tests {
|
||||
expected.set_style(Rect::new(0, 0, 24, 8), Style::new().red());
|
||||
expected.set_style(Rect::new(0, 8, 40, 8), Style::new().green());
|
||||
expected.set_style(Rect::new(0, 16, 32, 8), Style::new().blue());
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -445,13 +442,13 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 80, 4));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
"▄█▀▀█▄ ▀▀ ▀██ ▀██▀ ▀▀ ",
|
||||
"▀██▄ ▀██ ██▀▀█▄ ▄█▀▀▄█▀ ██ ▄█▀▀█▄ ██ ▀██ ██▀▀█▄ ▄█▀▀█▄ ",
|
||||
"▄▄ ▀██ ██ ██ ██ ▀█▄▄██ ██ ██▀▀▀▀ ██ ▄█ ██ ██ ██ ██▀▀▀▀ ",
|
||||
" ▀▀▀▀ ▀▀▀▀ ▀▀ ▀▀ ▄▄▄▄█▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀▀▀▀ ▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ",
|
||||
]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -463,12 +460,12 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 70, 3));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
"█▀██▀█ ▄█ ▀██",
|
||||
" ██ ▀█▄█▀█▄ ██ ██ ██▀▀█▄ ▄█▀▀█▄ ▀▀▀█▄ ▀██▀▀ ▄█▀▀█▄ ▄▄▄██",
|
||||
" ██ ██ ▀▀ ██ ██ ██ ██ ██ ▄▄ ▄█▀▀██ ██ ▄ ██▀▀▀▀ ██ ██",
|
||||
]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -480,7 +477,7 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 8));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
"██▄ ▄██ ▀██ ▄█ ▀▀ ",
|
||||
"███████ ██ ██ ██ ▀██▀▀ ▀██ ",
|
||||
"██ ▀ ██ ██ ██ ██ ██ ▄ ██ ",
|
||||
@@ -490,7 +487,7 @@ mod tests {
|
||||
" ██ ▄█ ██ ██ ██ ██▀▀▀▀ ▀▀▀█▄ ",
|
||||
"▀▀▀▀▀▀▀ ▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀▀▀▀ ",
|
||||
]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -503,14 +500,13 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 48, 4));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
"▄█▀▀█▄ ▄█ ▀██ ▀██ ",
|
||||
"▀██▄ ▀██▀▀ ██ ██ ██ ▄█▀▀█▄ ▄▄▄██ ",
|
||||
"▄▄ ▀██ ██ ▄ ▀█▄▄██ ██ ██▀▀▀▀ ██ ██ ",
|
||||
" ▀▀▀▀ ▀▀ ▄▄▄▄█▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀ ",
|
||||
let expected = Buffer::with_lines([
|
||||
"▄█▀▀█▄ ▄█ ▀██ ▀██ ".bold(),
|
||||
"▀██▄ ▀██▀▀ ██ ██ ██ ▄█▀▀█▄ ▄▄▄██ ".bold(),
|
||||
"▄▄ ▀██ ██ ▄ ▀█▄▄██ ██ ██▀▀▀▀ ██ ██ ".bold(),
|
||||
" ▀▀▀▀ ▀▀ ▄▄▄▄█▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀ ".bold(),
|
||||
]);
|
||||
expected.set_style(Rect::new(0, 0, 48, 4), Style::new().bold());
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -526,7 +522,7 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 12));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
"▀██▀▀█▄ ▀██ ",
|
||||
" ██▄▄█▀ ▄█▀▀█▄ ▄▄▄██ ",
|
||||
" ██ ▀█▄ ██▀▀▀▀ ██ ██ ",
|
||||
@@ -543,7 +539,7 @@ mod tests {
|
||||
expected.set_style(Rect::new(0, 0, 24, 4), Style::new().red());
|
||||
expected.set_style(Rect::new(0, 4, 40, 4), Style::new().green());
|
||||
expected.set_style(Rect::new(0, 8, 32, 4), Style::new().blue());
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -555,7 +551,7 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 8));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
"▐█▌ █ ▐█ ██ █ ",
|
||||
"█ █ █ ▐▌ ",
|
||||
"█▌ ▐█ ██▌ ▐█▐▌ █ ▐█▌ ▐▌ ▐█ ██▌ ▐█▌ ",
|
||||
@@ -565,7 +561,7 @@ mod tests {
|
||||
"▐█▌ ▐█▌ █ █ █ ▐█▌ ▐█▌ ███▌▐█▌ █ █ ▐█▌ ",
|
||||
" ██▌ ",
|
||||
]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -577,7 +573,7 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 35, 6));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
"███ ▐ ▐█",
|
||||
"▌█▐ █ █",
|
||||
" █ █▐█ █ █ ██▌ ▐█▌ ▐█▌ ▐██ ▐█▌ █",
|
||||
@@ -585,7 +581,7 @@ mod tests {
|
||||
" █ ▐▌▐▌█ █ █ █ █ ▐██ █ ███ █ █",
|
||||
" █ ▐▌ █ █ █ █ █ █ █ █ █▐ █ █ █",
|
||||
]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -597,7 +593,7 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 16));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
"█ ▐▌ ▐█ ▐ █ ",
|
||||
"█▌█▌ █ █ ",
|
||||
"███▌█ █ █ ▐██ ▐█ ",
|
||||
@@ -615,7 +611,7 @@ mod tests {
|
||||
"███▌▐█▌ █ █ ▐█▌ ██▌ ",
|
||||
" ",
|
||||
]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -628,18 +624,17 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 24, 8));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
"▐█▌ ▐ ▐█ ▐█ ",
|
||||
"█ █ █ █ █ ",
|
||||
"█▌ ▐██ █ █ █ ▐█▌ █ ",
|
||||
"▐█ █ █ █ █ █ █ ▐██ ",
|
||||
" ▐█ █ █ █ █ ███ █ █ ",
|
||||
"█ █ █▐ ▐██ █ █ █ █ ",
|
||||
"▐█▌ ▐▌ █ ▐█▌ ▐█▌ ▐█▐▌",
|
||||
" ██▌ ",
|
||||
let expected = Buffer::with_lines([
|
||||
"▐█▌ ▐ ▐█ ▐█ ".bold(),
|
||||
"█ █ █ █ █ ".bold(),
|
||||
"█▌ ▐██ █ █ █ ▐█▌ █ ".bold(),
|
||||
"▐█ █ █ █ █ █ █ ▐██ ".bold(),
|
||||
" ▐█ █ █ █ █ ███ █ █ ".bold(),
|
||||
"█ █ █▐ ▐██ █ █ █ █ ".bold(),
|
||||
"▐█▌ ▐▌ █ ▐█▌ ▐█▌ ▐█▐▌".bold(),
|
||||
" ██▌ ".bold(),
|
||||
]);
|
||||
expected.set_style(Rect::new(0, 0, 24, 8), Style::new().bold());
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -655,7 +650,7 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 24));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
"███ ▐█ ",
|
||||
"▐▌▐▌ █ ",
|
||||
"▐▌▐▌▐█▌ █ ",
|
||||
@@ -684,7 +679,7 @@ mod tests {
|
||||
expected.set_style(Rect::new(0, 0, 12, 8), Style::new().red());
|
||||
expected.set_style(Rect::new(0, 8, 20, 8), Style::new().green());
|
||||
expected.set_style(Rect::new(0, 16, 16, 8), Style::new().blue());
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -717,13 +712,13 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 4));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
"▟▀▙ ▀ ▝█ ▜▛ ▀ ",
|
||||
"▜▙ ▝█ █▀▙ ▟▀▟▘ █ ▟▀▙ ▐▌ ▝█ █▀▙ ▟▀▙ ",
|
||||
"▄▝█ █ █ █ ▜▄█ █ █▀▀ ▐▌▗▌ █ █ █ █▀▀ ",
|
||||
"▝▀▘ ▝▀▘ ▀ ▀ ▄▄▛ ▝▀▘ ▝▀▘ ▀▀▀▘▝▀▘ ▀ ▀ ▝▀▘ ",
|
||||
]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -735,12 +730,12 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 35, 3));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
"▛█▜ ▟ ▝█",
|
||||
" █ ▜▟▜▖█ █ █▀▙ ▟▀▙ ▝▀▙ ▝█▀ ▟▀▙ ▗▄█",
|
||||
" █ ▐▌▝▘█ █ █ █ █ ▄ ▟▀█ █▗ █▀▀ █ █",
|
||||
]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -752,7 +747,7 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 8));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
"█▖▟▌ ▝█ ▟ ▀ ",
|
||||
"███▌█ █ █ ▝█▀ ▝█ ",
|
||||
"█▝▐▌█ █ █ █▗ █ ",
|
||||
@@ -762,7 +757,7 @@ mod tests {
|
||||
"▐▌▗▌ █ █ █ █▀▀ ▝▀▙ ",
|
||||
"▀▀▀▘▝▀▘ ▀ ▀ ▝▀▘ ▀▀▘ ",
|
||||
]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -775,14 +770,13 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 24, 4));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
"▟▀▙ ▟ ▝█ ▝█ ",
|
||||
"▜▙ ▝█▀ █ █ █ ▟▀▙ ▗▄█ ",
|
||||
"▄▝█ █▗ ▜▄█ █ █▀▀ █ █ ",
|
||||
"▝▀▘ ▝▘ ▄▄▛ ▝▀▘ ▝▀▘ ▝▀▝▘",
|
||||
let expected = Buffer::with_lines([
|
||||
"▟▀▙ ▟ ▝█ ▝█ ".bold(),
|
||||
"▜▙ ▝█▀ █ █ █ ▟▀▙ ▗▄█ ".bold(),
|
||||
"▄▝█ █▗ ▜▄█ █ █▀▀ █ █ ".bold(),
|
||||
"▝▀▘ ▝▘ ▄▄▛ ▝▀▘ ▝▀▘ ▝▀▝▘".bold(),
|
||||
]);
|
||||
expected.set_style(Rect::new(0, 0, 24, 4), Style::new().bold());
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -798,7 +792,7 @@ mod tests {
|
||||
.build()?;
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 12));
|
||||
big_text.render(buf.area, &mut buf);
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
"▜▛▜▖ ▝█ ",
|
||||
"▐▙▟▘▟▀▙ ▗▄█ ",
|
||||
"▐▌▜▖█▀▀ █ █ ",
|
||||
@@ -815,7 +809,7 @@ mod tests {
|
||||
expected.set_style(Rect::new(0, 0, 12, 4), Style::new().red());
|
||||
expected.set_style(Rect::new(0, 4, 20, 4), Style::new().green());
|
||||
expected.set_style(Rect::new(0, 8, 16, 4), Style::new().blue());
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ pub use theme::*;
|
||||
fn main() -> Result<()> {
|
||||
errors::init_hooks()?;
|
||||
let terminal = &mut term::init()?;
|
||||
App::new().run(terminal)?;
|
||||
App::default().run(terminal)?;
|
||||
term::restore()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -49,10 +49,10 @@ fn render_hops(selected_row: usize, area: Rect, buf: &mut Buffer) {
|
||||
.iter()
|
||||
.map(|hop| Row::new(vec![hop.host, hop.address]))
|
||||
.collect_vec();
|
||||
let block = Block::default()
|
||||
.title("Traceroute bad.horse".bold().white())
|
||||
let block = Block::new()
|
||||
.padding(Padding::new(1, 1, 1, 1))
|
||||
.title_alignment(Alignment::Center)
|
||||
.padding(Padding::new(1, 1, 1, 1));
|
||||
.title("Traceroute bad.horse".bold().white());
|
||||
StatefulWidget::render(
|
||||
Table::new(rows, [Constraint::Max(100), Constraint::Length(15)])
|
||||
.header(Row::new(vec!["Host", "Address"]).set_style(THEME.traceroute.header))
|
||||
|
||||
@@ -51,8 +51,7 @@ fn main() -> io::Result<()> {
|
||||
|
||||
fn hello_world(frame: &mut Frame) {
|
||||
frame.render_widget(
|
||||
Paragraph::new("Hello World!")
|
||||
.block(Block::default().title("Greeting").borders(Borders::ALL)),
|
||||
Paragraph::new("Hello World!").block(Block::bordered().title("Greeting")),
|
||||
frame.size(),
|
||||
);
|
||||
}
|
||||
@@ -87,8 +86,8 @@ fn layout(frame: &mut Frame) {
|
||||
Block::new().borders(Borders::TOP).title("Status Bar"),
|
||||
status_bar,
|
||||
);
|
||||
frame.render_widget(Block::default().borders(Borders::ALL).title("Left"), left);
|
||||
frame.render_widget(Block::default().borders(Borders::ALL).title("Right"), right);
|
||||
frame.render_widget(Block::bordered().title("Left"), left);
|
||||
frame.render_widget(Block::bordered().title("Right"), right);
|
||||
}
|
||||
|
||||
fn styling(frame: &mut Frame) {
|
||||
|
||||
@@ -206,11 +206,11 @@ impl App {
|
||||
|
||||
fn title_block(title: &str) -> Block {
|
||||
let title = Title::from(title).alignment(Alignment::Center);
|
||||
Block::default()
|
||||
.title(title)
|
||||
Block::new()
|
||||
.borders(Borders::NONE)
|
||||
.fg(CUSTOM_LABEL_COLOR)
|
||||
.padding(Padding::vertical(1))
|
||||
.title(title)
|
||||
.fg(CUSTOM_LABEL_COLOR)
|
||||
}
|
||||
|
||||
fn init_error_hooks() -> color_eyre::Result<()> {
|
||||
|
||||
@@ -236,7 +236,7 @@ fn run_app<B: Backend>(
|
||||
fn ui(f: &mut Frame, downloads: &Downloads) {
|
||||
let area = f.size();
|
||||
|
||||
let block = Block::default().title(block::Title::from("Progress").alignment(Alignment::Center));
|
||||
let block = Block::new().title(block::Title::from("Progress").alignment(Alignment::Center));
|
||||
f.render_widget(block, area);
|
||||
|
||||
let vertical = Layout::vertical([Constraint::Length(2), Constraint::Length(4)]).margin(1);
|
||||
|
||||
@@ -26,7 +26,7 @@ use itertools::Itertools;
|
||||
use ratatui::{
|
||||
layout::Constraint::*,
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
widgets::{Block, Paragraph},
|
||||
};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
@@ -190,10 +190,9 @@ fn render_example_combination(
|
||||
title: &str,
|
||||
constraints: Vec<(Constraint, Constraint)>,
|
||||
) {
|
||||
let block = Block::default()
|
||||
let block = Block::bordered()
|
||||
.title(title.gray())
|
||||
.style(Style::reset())
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(Color::DarkGray));
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(block, area);
|
||||
|
||||
@@ -189,13 +189,13 @@ impl Widget for &mut App<'_> {
|
||||
impl App<'_> {
|
||||
fn render_todo(&mut self, area: Rect, buf: &mut Buffer) {
|
||||
// We create two blocks, one is for the header (outer) and the other is for list (inner).
|
||||
let outer_block = Block::default()
|
||||
let outer_block = Block::new()
|
||||
.borders(Borders::NONE)
|
||||
.fg(TEXT_COLOR)
|
||||
.bg(TODO_HEADER_BG)
|
||||
.title_alignment(Alignment::Center)
|
||||
.title("TODO List")
|
||||
.title_alignment(Alignment::Center);
|
||||
let inner_block = Block::default()
|
||||
.fg(TEXT_COLOR)
|
||||
.bg(TODO_HEADER_BG);
|
||||
let inner_block = Block::new()
|
||||
.borders(Borders::NONE)
|
||||
.fg(TEXT_COLOR)
|
||||
.bg(NORMAL_ROW_COLOR);
|
||||
@@ -246,16 +246,16 @@ impl App<'_> {
|
||||
};
|
||||
|
||||
// We show the list item's info under the list in this paragraph
|
||||
let outer_info_block = Block::default()
|
||||
let outer_info_block = Block::new()
|
||||
.borders(Borders::NONE)
|
||||
.fg(TEXT_COLOR)
|
||||
.bg(TODO_HEADER_BG)
|
||||
.title_alignment(Alignment::Center)
|
||||
.title("TODO Info")
|
||||
.title_alignment(Alignment::Center);
|
||||
let inner_info_block = Block::default()
|
||||
.fg(TEXT_COLOR)
|
||||
.bg(TODO_HEADER_BG);
|
||||
let inner_info_block = Block::new()
|
||||
.borders(Borders::NONE)
|
||||
.bg(NORMAL_ROW_COLOR)
|
||||
.padding(Padding::horizontal(1));
|
||||
.padding(Padding::horizontal(1))
|
||||
.bg(NORMAL_ROW_COLOR);
|
||||
|
||||
// This is a similar process to what we did for list. outer_info_area will be used for
|
||||
// header inner_info_area will be used for the list info.
|
||||
|
||||
@@ -37,7 +37,7 @@ use crossterm::{
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
widgets::{Block, Paragraph},
|
||||
};
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
||||
@@ -142,11 +142,9 @@ fn ui(f: &mut Frame, app: &App) {
|
||||
Line::from("try first without the panic handler to see the difference"),
|
||||
];
|
||||
|
||||
let b = Block::default()
|
||||
.title("Panic Handler Demo")
|
||||
.borders(Borders::ALL);
|
||||
let paragraph = Paragraph::new(text)
|
||||
.block(Block::bordered().title("Panic Handler Demo"))
|
||||
.centered();
|
||||
|
||||
let p = Paragraph::new(text).block(b).centered();
|
||||
|
||||
f.render_widget(p, f.size());
|
||||
f.render_widget(paragraph, f.size());
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ use crossterm::{
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, Paragraph, Wrap},
|
||||
widgets::{Block, Paragraph, Wrap},
|
||||
};
|
||||
|
||||
struct App {
|
||||
@@ -105,7 +105,7 @@ fn ui(f: &mut Frame, app: &App) {
|
||||
let mut long_line = s.repeat(usize::from(size.width) / s.len() + 4);
|
||||
long_line.push('\n');
|
||||
|
||||
let block = Block::default().black();
|
||||
let block = Block::new().black();
|
||||
f.render_widget(block, size);
|
||||
|
||||
let layout = Layout::vertical([Constraint::Ratio(1, 4); 4]).split(size);
|
||||
@@ -127,8 +127,7 @@ fn ui(f: &mut Frame, app: &App) {
|
||||
];
|
||||
|
||||
let create_block = |title| {
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
Block::bordered()
|
||||
.style(Style::default().fg(Color::Gray))
|
||||
.title(Span::styled(
|
||||
title,
|
||||
|
||||
@@ -25,7 +25,7 @@ use crossterm::{
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, Clear, Paragraph, Wrap},
|
||||
widgets::{Block, Clear, Paragraph, Wrap},
|
||||
};
|
||||
|
||||
struct App {
|
||||
@@ -98,14 +98,11 @@ fn ui(f: &mut Frame, app: &App) {
|
||||
.wrap(Wrap { trim: true });
|
||||
f.render_widget(paragraph, instructions);
|
||||
|
||||
let block = Block::default()
|
||||
.title("Content")
|
||||
.borders(Borders::ALL)
|
||||
.on_blue();
|
||||
let block = Block::bordered().title("Content").on_blue();
|
||||
f.render_widget(block, content);
|
||||
|
||||
if app.show_popup {
|
||||
let block = Block::default().title("Popup").borders(Borders::ALL);
|
||||
let block = Block::bordered().title("Popup");
|
||||
let area = centered_rect(60, 20, area);
|
||||
f.render_widget(Clear, area); //this clears out the background
|
||||
f.render_widget(block, area);
|
||||
|
||||
@@ -151,18 +151,18 @@ fn ui(f: &mut Frame, app: &App) {
|
||||
.split(f.size());
|
||||
let sparkline = Sparkline::default()
|
||||
.block(
|
||||
Block::default()
|
||||
.title("Data1")
|
||||
.borders(Borders::LEFT | Borders::RIGHT),
|
||||
Block::new()
|
||||
.borders(Borders::LEFT | Borders::RIGHT)
|
||||
.title("Data1"),
|
||||
)
|
||||
.data(&app.data1)
|
||||
.style(Style::default().fg(Color::Yellow));
|
||||
f.render_widget(sparkline, chunks[0]);
|
||||
let sparkline = Sparkline::default()
|
||||
.block(
|
||||
Block::default()
|
||||
.title("Data2")
|
||||
.borders(Borders::LEFT | Borders::RIGHT),
|
||||
Block::new()
|
||||
.borders(Borders::LEFT | Borders::RIGHT)
|
||||
.title("Data2"),
|
||||
)
|
||||
.data(&app.data2)
|
||||
.style(Style::default().bg(Color::Green));
|
||||
@@ -170,9 +170,9 @@ fn ui(f: &mut Frame, app: &App) {
|
||||
// Multiline
|
||||
let sparkline = Sparkline::default()
|
||||
.block(
|
||||
Block::default()
|
||||
.title("Data3")
|
||||
.borders(Borders::LEFT | Borders::RIGHT),
|
||||
Block::new()
|
||||
.borders(Borders::LEFT | Borders::RIGHT)
|
||||
.title("Data3"),
|
||||
)
|
||||
.data(&app.data3)
|
||||
.style(Style::default().fg(Color::Red));
|
||||
|
||||
@@ -331,10 +331,9 @@ fn render_footer(f: &mut Frame, app: &App, area: Rect) {
|
||||
.style(Style::new().fg(app.colors.row_fg).bg(app.colors.buffer_bg))
|
||||
.centered()
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::new().fg(app.colors.footer_border_color))
|
||||
.border_type(BorderType::Double),
|
||||
Block::bordered()
|
||||
.border_type(BorderType::Double)
|
||||
.border_style(Style::new().fg(app.colors.footer_border_color)),
|
||||
);
|
||||
f.render_widget(info_footer, area);
|
||||
}
|
||||
|
||||
@@ -205,8 +205,7 @@ impl SelectedTab {
|
||||
|
||||
/// A block surrounding the tab's content
|
||||
fn block(self) -> Block<'static> {
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
Block::bordered()
|
||||
.border_set(symbols::border::PROPORTIONAL_TALL)
|
||||
.padding(Padding::horizontal(1))
|
||||
.border_style(self.palette().c700)
|
||||
|
||||
@@ -36,7 +36,7 @@ use crossterm::{
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, List, ListItem, Paragraph},
|
||||
widgets::{Block, List, ListItem, Paragraph},
|
||||
};
|
||||
|
||||
enum InputMode {
|
||||
@@ -49,7 +49,7 @@ struct App {
|
||||
/// Current value of the input box
|
||||
input: String,
|
||||
/// Position of cursor in the editor area.
|
||||
cursor_position: usize,
|
||||
character_index: usize,
|
||||
/// Current input mode
|
||||
input_mode: InputMode,
|
||||
/// History of recorded messages
|
||||
@@ -62,34 +62,46 @@ impl App {
|
||||
input: String::new(),
|
||||
input_mode: InputMode::Normal,
|
||||
messages: Vec::new(),
|
||||
cursor_position: 0,
|
||||
character_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn move_cursor_left(&mut self) {
|
||||
let cursor_moved_left = self.cursor_position.saturating_sub(1);
|
||||
self.cursor_position = self.clamp_cursor(cursor_moved_left);
|
||||
let cursor_moved_left = self.character_index.saturating_sub(1);
|
||||
self.character_index = self.clamp_cursor(cursor_moved_left);
|
||||
}
|
||||
|
||||
fn move_cursor_right(&mut self) {
|
||||
let cursor_moved_right = self.cursor_position.saturating_add(1);
|
||||
self.cursor_position = self.clamp_cursor(cursor_moved_right);
|
||||
let cursor_moved_right = self.character_index.saturating_add(1);
|
||||
self.character_index = self.clamp_cursor(cursor_moved_right);
|
||||
}
|
||||
|
||||
fn enter_char(&mut self, new_char: char) {
|
||||
self.input.insert(self.cursor_position, new_char);
|
||||
|
||||
let index = self.byte_index();
|
||||
self.input.insert(index, new_char);
|
||||
self.move_cursor_right();
|
||||
}
|
||||
|
||||
/// Returns the byte index based on the character position.
|
||||
///
|
||||
/// Since each character in a string can be contain multiple bytes, it's necessary to calculate
|
||||
/// the byte index based on the index of the character.
|
||||
fn byte_index(&mut self) -> usize {
|
||||
self.input
|
||||
.char_indices()
|
||||
.map(|(i, _)| i)
|
||||
.nth(self.character_index)
|
||||
.unwrap_or(self.input.len())
|
||||
}
|
||||
|
||||
fn delete_char(&mut self) {
|
||||
let is_not_cursor_leftmost = self.cursor_position != 0;
|
||||
let is_not_cursor_leftmost = self.character_index != 0;
|
||||
if is_not_cursor_leftmost {
|
||||
// Method "remove" is not used on the saved text for deleting the selected char.
|
||||
// Reason: Using remove on String works on bytes instead of the chars.
|
||||
// Using remove would require special care because of char boundaries.
|
||||
|
||||
let current_index = self.cursor_position;
|
||||
let current_index = self.character_index;
|
||||
let from_left_to_current_index = current_index - 1;
|
||||
|
||||
// Getting all characters before the selected character.
|
||||
@@ -105,11 +117,11 @@ impl App {
|
||||
}
|
||||
|
||||
fn clamp_cursor(&self, new_cursor_pos: usize) -> usize {
|
||||
new_cursor_pos.clamp(0, self.input.len())
|
||||
new_cursor_pos.clamp(0, self.input.chars().count())
|
||||
}
|
||||
|
||||
fn reset_cursor(&mut self) {
|
||||
self.cursor_position = 0;
|
||||
self.character_index = 0;
|
||||
}
|
||||
|
||||
fn submit_message(&mut self) {
|
||||
@@ -226,7 +238,7 @@ fn ui(f: &mut Frame, app: &App) {
|
||||
InputMode::Normal => Style::default(),
|
||||
InputMode::Editing => Style::default().fg(Color::Yellow),
|
||||
})
|
||||
.block(Block::default().borders(Borders::ALL).title("Input"));
|
||||
.block(Block::bordered().title("Input"));
|
||||
f.render_widget(input, input_area);
|
||||
match app.input_mode {
|
||||
InputMode::Normal =>
|
||||
@@ -240,7 +252,7 @@ fn ui(f: &mut Frame, app: &App) {
|
||||
f.set_cursor(
|
||||
// Draw the cursor at the current position in the input field.
|
||||
// This position is can be controlled via the left and right arrow key
|
||||
input_area.x + app.cursor_position as u16 + 1,
|
||||
input_area.x + app.character_index as u16 + 1,
|
||||
// Move one line down, from the border to the input line
|
||||
input_area.y + 1,
|
||||
);
|
||||
@@ -256,7 +268,6 @@ fn ui(f: &mut Frame, app: &App) {
|
||||
ListItem::new(content)
|
||||
})
|
||||
.collect();
|
||||
let messages =
|
||||
List::new(messages).block(Block::default().borders(Borders::ALL).title("Messages"));
|
||||
let messages = List::new(messages).block(Block::bordered().title("Messages"));
|
||||
f.render_widget(messages, messages_area);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ use crossterm::{
|
||||
cursor::{Hide, MoveTo, Show},
|
||||
execute, queue,
|
||||
style::{
|
||||
Attribute as CAttribute, Attributes as CAttributes, Color as CColor, ContentStyle, Print,
|
||||
SetAttribute, SetBackgroundColor, SetForegroundColor,
|
||||
Attribute as CAttribute, Attributes as CAttributes, Color as CColor, Colors, ContentStyle,
|
||||
Print, SetAttribute, SetBackgroundColor, SetColors, SetForegroundColor,
|
||||
},
|
||||
terminal::{self, Clear},
|
||||
};
|
||||
@@ -145,14 +145,12 @@ where
|
||||
diff.queue(&mut self.writer)?;
|
||||
modifier = cell.modifier;
|
||||
}
|
||||
if cell.fg != fg {
|
||||
let color = CColor::from(cell.fg);
|
||||
queue!(self.writer, SetForegroundColor(color))?;
|
||||
if cell.fg != fg || cell.bg != bg {
|
||||
queue!(
|
||||
self.writer,
|
||||
SetColors(Colors::new(cell.fg.into(), cell.bg.into()))
|
||||
)?;
|
||||
fg = cell.fg;
|
||||
}
|
||||
if cell.bg != bg {
|
||||
let color = CColor::from(cell.bg);
|
||||
queue!(self.writer, SetBackgroundColor(color))?;
|
||||
bg = cell.bg;
|
||||
}
|
||||
#[cfg(feature = "underline-color")]
|
||||
@@ -228,7 +226,7 @@ where
|
||||
Ok(Rect::new(0, 0, width, height))
|
||||
}
|
||||
|
||||
fn window_size(&mut self) -> Result<WindowSize, io::Error> {
|
||||
fn window_size(&mut self) -> io::Result<WindowSize> {
|
||||
let crossterm::terminal::WindowSize {
|
||||
columns,
|
||||
rows,
|
||||
|
||||
@@ -198,7 +198,7 @@ where
|
||||
Ok(Rect::new(0, 0, terminal.0, terminal.1))
|
||||
}
|
||||
|
||||
fn window_size(&mut self) -> Result<WindowSize, io::Error> {
|
||||
fn window_size(&mut self) -> io::Result<WindowSize> {
|
||||
Ok(WindowSize {
|
||||
columns_rows: termion::terminal_size()?.into(),
|
||||
pixels: termion::terminal_size_pixels()?.into(),
|
||||
|
||||
@@ -111,7 +111,7 @@ impl TermwizBackend {
|
||||
}
|
||||
|
||||
impl Backend for TermwizBackend {
|
||||
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
|
||||
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
||||
where
|
||||
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
||||
{
|
||||
@@ -181,13 +181,13 @@ impl Backend for TermwizBackend {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn hide_cursor(&mut self) -> Result<(), io::Error> {
|
||||
fn hide_cursor(&mut self) -> io::Result<()> {
|
||||
self.buffered_terminal
|
||||
.add_change(Change::CursorVisibility(CursorVisibility::Hidden));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn show_cursor(&mut self) -> Result<(), io::Error> {
|
||||
fn show_cursor(&mut self) -> io::Result<()> {
|
||||
self.buffered_terminal
|
||||
.add_change(Change::CursorVisibility(CursorVisibility::Visible));
|
||||
Ok(())
|
||||
@@ -207,18 +207,18 @@ impl Backend for TermwizBackend {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear(&mut self) -> Result<(), io::Error> {
|
||||
fn clear(&mut self) -> io::Result<()> {
|
||||
self.buffered_terminal
|
||||
.add_change(Change::ClearScreen(termwiz::color::ColorAttribute::Default));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn size(&self) -> Result<Rect, io::Error> {
|
||||
fn size(&self) -> io::Result<Rect> {
|
||||
let (cols, rows) = self.buffered_terminal.dimensions();
|
||||
Ok(Rect::new(0, 0, u16_max(cols), u16_max(rows)))
|
||||
}
|
||||
|
||||
fn window_size(&mut self) -> Result<WindowSize, io::Error> {
|
||||
fn window_size(&mut self) -> io::Result<WindowSize> {
|
||||
let ScreenSize {
|
||||
cols,
|
||||
rows,
|
||||
@@ -241,7 +241,7 @@ impl Backend for TermwizBackend {
|
||||
})
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), io::Error> {
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.buffered_terminal
|
||||
.flush()
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
@@ -271,7 +271,7 @@ impl From<CellAttributes> for Style {
|
||||
|
||||
style.fg = Some(value.foreground().into());
|
||||
style.bg = Some(value.background().into());
|
||||
#[cfg(feature = "underline_color")]
|
||||
#[cfg(feature = "underline-color")]
|
||||
{
|
||||
style.underline_color = Some(value.underline_color().into());
|
||||
}
|
||||
@@ -407,7 +407,6 @@ fn u16_max(i: usize) -> u16 {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::style::Stylize;
|
||||
|
||||
mod into_color {
|
||||
use Color as C;
|
||||
@@ -576,11 +575,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn from_cell_attribute_for_style() {
|
||||
use crate::style::Stylize;
|
||||
|
||||
#[cfg(feature = "underline-color")]
|
||||
const STYLE: Style = Style::new()
|
||||
.underline_color(Color::Reset)
|
||||
.fg(Color::Reset)
|
||||
.bg(Color::Reset);
|
||||
#[cfg(not(feature = "underline-color"))]
|
||||
const STYLE: Style = Style::new().fg(Color::Reset).bg(Color::Reset);
|
||||
|
||||
// default
|
||||
assert_eq!(
|
||||
Style::from(CellAttributes::default()),
|
||||
Style::new().fg(Color::Reset).bg(Color::Reset)
|
||||
);
|
||||
assert_eq!(Style::from(CellAttributes::default()), STYLE);
|
||||
|
||||
// foreground color
|
||||
assert_eq!(
|
||||
Style::from(
|
||||
@@ -588,7 +595,7 @@ mod tests {
|
||||
.set_foreground(ColorAttribute::PaletteIndex(31))
|
||||
.to_owned()
|
||||
),
|
||||
Style::new().fg(Color::Indexed(31)).bg(Color::Reset)
|
||||
STYLE.fg(Color::Indexed(31))
|
||||
);
|
||||
// background color
|
||||
assert_eq!(
|
||||
@@ -597,21 +604,7 @@ mod tests {
|
||||
.set_background(ColorAttribute::PaletteIndex(31))
|
||||
.to_owned()
|
||||
),
|
||||
Style::new().fg(Color::Reset).bg(Color::Indexed(31))
|
||||
);
|
||||
// underline color
|
||||
#[cfg(feature = "underline_color")]
|
||||
assert_eq!(
|
||||
Style::from(
|
||||
CellAttributes::default()
|
||||
.set_underline_color(AnsiColor::Red)
|
||||
.set
|
||||
.to_owned()
|
||||
),
|
||||
Style::new()
|
||||
.fg(Color::Reset)
|
||||
.bg(Color::Reset)
|
||||
.underline_color(Color::Red)
|
||||
STYLE.bg(Color::Indexed(31))
|
||||
);
|
||||
// underlined
|
||||
assert_eq!(
|
||||
@@ -620,12 +613,12 @@ mod tests {
|
||||
.set_underline(Underline::Single)
|
||||
.to_owned()
|
||||
),
|
||||
Style::new().fg(Color::Reset).bg(Color::Reset).underlined()
|
||||
STYLE.underlined()
|
||||
);
|
||||
// blink
|
||||
assert_eq!(
|
||||
Style::from(CellAttributes::default().set_blink(Blink::Slow).to_owned()),
|
||||
Style::new().fg(Color::Reset).bg(Color::Reset).slow_blink()
|
||||
STYLE.slow_blink()
|
||||
);
|
||||
// intensity
|
||||
assert_eq!(
|
||||
@@ -634,27 +627,38 @@ mod tests {
|
||||
.set_intensity(Intensity::Bold)
|
||||
.to_owned()
|
||||
),
|
||||
Style::new().fg(Color::Reset).bg(Color::Reset).bold()
|
||||
STYLE.bold()
|
||||
);
|
||||
// italic
|
||||
assert_eq!(
|
||||
Style::from(CellAttributes::default().set_italic(true).to_owned()),
|
||||
Style::new().fg(Color::Reset).bg(Color::Reset).italic()
|
||||
STYLE.italic()
|
||||
);
|
||||
// reversed
|
||||
assert_eq!(
|
||||
Style::from(CellAttributes::default().set_reverse(true).to_owned()),
|
||||
Style::new().fg(Color::Reset).bg(Color::Reset).reversed()
|
||||
STYLE.reversed()
|
||||
);
|
||||
// strikethrough
|
||||
assert_eq!(
|
||||
Style::from(CellAttributes::default().set_strikethrough(true).to_owned()),
|
||||
Style::new().fg(Color::Reset).bg(Color::Reset).crossed_out()
|
||||
STYLE.crossed_out()
|
||||
);
|
||||
// hidden
|
||||
assert_eq!(
|
||||
Style::from(CellAttributes::default().set_invisible(true).to_owned()),
|
||||
Style::new().fg(Color::Reset).bg(Color::Reset).hidden()
|
||||
STYLE.hidden()
|
||||
);
|
||||
|
||||
// underline color
|
||||
#[cfg(feature = "underline-color")]
|
||||
assert_eq!(
|
||||
Style::from(
|
||||
CellAttributes::default()
|
||||
.set_underline_color(AnsiColor::Red)
|
||||
.to_owned()
|
||||
),
|
||||
STYLE.underline_color(Color::Indexed(9))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,19 @@
|
||||
//! It is used in the integration tests to verify the correctness of the library.
|
||||
|
||||
use std::{
|
||||
fmt::{Display, Write},
|
||||
fmt::{self, Write},
|
||||
io,
|
||||
};
|
||||
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{
|
||||
assert_buffer_eq,
|
||||
backend::{Backend, ClearType, WindowSize},
|
||||
buffer::{Buffer, Cell},
|
||||
layout::{Rect, Size},
|
||||
};
|
||||
|
||||
/// A [`Backend`] implementation used for integration testing that that renders to an in memory
|
||||
/// buffer.
|
||||
/// A [`Backend`] implementation used for integration testing that renders to an memory buffer.
|
||||
///
|
||||
/// Note: that although many of the integration and unit tests in ratatui are written using this
|
||||
/// backend, it is preferable to write unit tests for widgets directly against the buffer rather
|
||||
@@ -30,7 +28,7 @@ use crate::{
|
||||
///
|
||||
/// let mut backend = TestBackend::new(10, 2);
|
||||
/// backend.clear()?;
|
||||
/// backend.assert_buffer(&Buffer::with_lines(vec![" "; 2]));
|
||||
/// backend.assert_buffer_lines([" "; 2]);
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
@@ -97,24 +95,46 @@ impl TestBackend {
|
||||
}
|
||||
|
||||
/// Asserts that the `TestBackend`'s buffer is equal to the expected buffer.
|
||||
/// If the buffers are not equal, a panic occurs with a detailed error message
|
||||
/// showing the differences between the expected and actual buffers.
|
||||
///
|
||||
/// This is a shortcut for `assert_eq!(self.buffer(), &expected)`.
|
||||
///
|
||||
/// # Panics
|
||||
/// When they are not equal, a panic occurs with a detailed error message showing the
|
||||
/// differences between the expected and actual buffers.
|
||||
#[allow(deprecated)]
|
||||
#[track_caller]
|
||||
pub fn assert_buffer(&self, expected: &Buffer) {
|
||||
assert_buffer_eq!(&self.buffer, expected);
|
||||
// TODO: use assert_eq!()
|
||||
crate::assert_buffer_eq!(&self.buffer, expected);
|
||||
}
|
||||
|
||||
/// Asserts that the `TestBackend`'s buffer is equal to the expected lines.
|
||||
///
|
||||
/// This is a shortcut for `assert_eq!(self.buffer(), &Buffer::with_lines(expected))`.
|
||||
///
|
||||
/// # Panics
|
||||
/// When they are not equal, a panic occurs with a detailed error message showing the
|
||||
/// differences between the expected and actual buffers.
|
||||
#[track_caller]
|
||||
pub fn assert_buffer_lines<'line, Lines>(&self, expected: Lines)
|
||||
where
|
||||
Lines: IntoIterator,
|
||||
Lines::Item: Into<crate::text::Line<'line>>,
|
||||
{
|
||||
self.assert_buffer(&Buffer::with_lines(expected));
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TestBackend {
|
||||
impl fmt::Display for TestBackend {
|
||||
/// Formats the `TestBackend` for display by calling the `buffer_view` function
|
||||
/// on its internal buffer.
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", buffer_view(&self.buffer))
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for TestBackend {
|
||||
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
|
||||
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
||||
where
|
||||
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
||||
{
|
||||
@@ -125,26 +145,26 @@ impl Backend for TestBackend {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn hide_cursor(&mut self) -> Result<(), io::Error> {
|
||||
fn hide_cursor(&mut self) -> io::Result<()> {
|
||||
self.cursor = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn show_cursor(&mut self) -> Result<(), io::Error> {
|
||||
fn show_cursor(&mut self) -> io::Result<()> {
|
||||
self.cursor = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_cursor(&mut self) -> Result<(u16, u16), io::Error> {
|
||||
fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
|
||||
Ok(self.pos)
|
||||
}
|
||||
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), io::Error> {
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
|
||||
self.pos = (x, y);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear(&mut self) -> Result<(), io::Error> {
|
||||
fn clear(&mut self) -> io::Result<()> {
|
||||
self.buffer.reset();
|
||||
Ok(())
|
||||
}
|
||||
@@ -214,11 +234,11 @@ impl Backend for TestBackend {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn size(&self) -> Result<Rect, io::Error> {
|
||||
fn size(&self) -> io::Result<Rect> {
|
||||
Ok(Rect::new(0, 0, self.width, self.height))
|
||||
}
|
||||
|
||||
fn window_size(&mut self) -> Result<WindowSize, io::Error> {
|
||||
fn window_size(&mut self) -> io::Result<WindowSize> {
|
||||
// Some arbitrary window pixel size, probably doesn't need much testing.
|
||||
static WINDOW_PIXEL_SIZE: Size = Size {
|
||||
width: 640,
|
||||
@@ -230,7 +250,7 @@ impl Backend for TestBackend {
|
||||
})
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), io::Error> {
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -246,7 +266,7 @@ mod tests {
|
||||
TestBackend {
|
||||
width: 10,
|
||||
height: 2,
|
||||
buffer: Buffer::with_lines(vec![" "; 2]),
|
||||
buffer: Buffer::with_lines([" "; 2]),
|
||||
cursor: false,
|
||||
pos: (0, 0),
|
||||
}
|
||||
@@ -254,14 +274,14 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn test_buffer_view() {
|
||||
let buffer = Buffer::with_lines(vec!["aaaa"; 2]);
|
||||
let buffer = Buffer::with_lines(["aaaa"; 2]);
|
||||
assert_eq!(buffer_view(&buffer), "\"aaaa\"\n\"aaaa\"\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn buffer_view_with_overwrites() {
|
||||
let multi_byte_char = "👨👩👧👦"; // renders 8 wide
|
||||
let buffer = Buffer::with_lines(vec![multi_byte_char]);
|
||||
let buffer = Buffer::with_lines([multi_byte_char]);
|
||||
assert_eq!(
|
||||
buffer_view(&buffer),
|
||||
format!(
|
||||
@@ -274,29 +294,27 @@ mod tests {
|
||||
#[test]
|
||||
fn buffer() {
|
||||
let backend = TestBackend::new(10, 2);
|
||||
assert_eq!(backend.buffer(), &Buffer::with_lines(vec![" "; 2]));
|
||||
backend.assert_buffer_lines([" "; 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resize() {
|
||||
let mut backend = TestBackend::new(10, 2);
|
||||
backend.resize(5, 5);
|
||||
assert_eq!(backend.buffer(), &Buffer::with_lines(vec![" "; 5]));
|
||||
backend.assert_buffer_lines([" "; 5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_buffer() {
|
||||
let backend = TestBackend::new(10, 2);
|
||||
let buffer = Buffer::with_lines(vec![" "; 2]);
|
||||
backend.assert_buffer(&buffer);
|
||||
backend.assert_buffer_lines([" "; 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "buffer contents not equal"]
|
||||
fn assert_buffer_panics() {
|
||||
let backend = TestBackend::new(10, 2);
|
||||
let buffer = Buffer::with_lines(vec!["aaaaaaaaaa"; 2]);
|
||||
backend.assert_buffer(&buffer);
|
||||
backend.assert_buffer_lines(["aaaaaaaaaa"; 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -312,7 +330,7 @@ mod tests {
|
||||
cell.set_symbol("a");
|
||||
backend.draw([(0, 0, &cell)].into_iter()).unwrap();
|
||||
backend.draw([(0, 1, &cell)].into_iter()).unwrap();
|
||||
backend.assert_buffer(&Buffer::with_lines(vec!["a "; 2]));
|
||||
backend.assert_buffer_lines(["a "; 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -344,24 +362,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn clear() {
|
||||
let mut backend = TestBackend::new(10, 4);
|
||||
let mut backend = TestBackend::new(4, 2);
|
||||
let mut cell = Cell::default();
|
||||
cell.set_symbol("a");
|
||||
backend.draw([(0, 0, &cell)].into_iter()).unwrap();
|
||||
backend.draw([(0, 1, &cell)].into_iter()).unwrap();
|
||||
backend.clear().unwrap();
|
||||
backend.assert_buffer(&Buffer::with_lines(vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
]));
|
||||
backend.assert_buffer_lines([" ", " "]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_region_all() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines(vec![
|
||||
backend.buffer = Buffer::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
@@ -370,19 +383,19 @@ mod tests {
|
||||
]);
|
||||
|
||||
backend.clear_region(ClearType::All).unwrap();
|
||||
backend.assert_buffer(&Buffer::with_lines(vec![
|
||||
backend.assert_buffer_lines([
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
]));
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_region_after_cursor() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines(vec![
|
||||
backend.buffer = Buffer::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
@@ -392,19 +405,19 @@ mod tests {
|
||||
|
||||
backend.set_cursor(3, 2).unwrap();
|
||||
backend.clear_region(ClearType::AfterCursor).unwrap();
|
||||
backend.assert_buffer(&Buffer::with_lines(vec![
|
||||
backend.assert_buffer_lines([
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
"aaaa ",
|
||||
" ",
|
||||
" ",
|
||||
]));
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_region_before_cursor() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines(vec![
|
||||
backend.buffer = Buffer::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
@@ -414,19 +427,19 @@ mod tests {
|
||||
|
||||
backend.set_cursor(5, 3).unwrap();
|
||||
backend.clear_region(ClearType::BeforeCursor).unwrap();
|
||||
backend.assert_buffer(&Buffer::with_lines(vec![
|
||||
backend.assert_buffer_lines([
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" aaaaa",
|
||||
"aaaaaaaaaa",
|
||||
]));
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_region_current_line() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines(vec![
|
||||
backend.buffer = Buffer::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
@@ -436,19 +449,19 @@ mod tests {
|
||||
|
||||
backend.set_cursor(3, 1).unwrap();
|
||||
backend.clear_region(ClearType::CurrentLine).unwrap();
|
||||
backend.assert_buffer(&Buffer::with_lines(vec![
|
||||
backend.assert_buffer_lines([
|
||||
"aaaaaaaaaa",
|
||||
" ",
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
]));
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_region_until_new_line() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines(vec![
|
||||
backend.buffer = Buffer::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
@@ -458,19 +471,19 @@ mod tests {
|
||||
|
||||
backend.set_cursor(3, 0).unwrap();
|
||||
backend.clear_region(ClearType::UntilNewLine).unwrap();
|
||||
backend.assert_buffer(&Buffer::with_lines(vec![
|
||||
backend.assert_buffer_lines([
|
||||
"aaa ",
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
"aaaaaaaaaa",
|
||||
]));
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_lines_not_at_last_line() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines(vec![
|
||||
backend.buffer = Buffer::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
@@ -496,19 +509,19 @@ mod tests {
|
||||
assert_eq!(backend.get_cursor().unwrap(), (4, 4));
|
||||
|
||||
// As such the buffer should remain unchanged
|
||||
backend.assert_buffer(&Buffer::with_lines(vec![
|
||||
backend.assert_buffer_lines([
|
||||
"aaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
"dddddddddd",
|
||||
"eeeeeeeeee",
|
||||
]));
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_lines_at_last_line() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines(vec![
|
||||
backend.buffer = Buffer::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
@@ -522,7 +535,7 @@ mod tests {
|
||||
|
||||
backend.append_lines(1).unwrap();
|
||||
|
||||
backend.buffer = Buffer::with_lines(vec![
|
||||
backend.buffer = Buffer::with_lines([
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
"dddddddddd",
|
||||
@@ -538,7 +551,7 @@ mod tests {
|
||||
#[test]
|
||||
fn append_multiple_lines_not_at_last_line() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines(vec![
|
||||
backend.buffer = Buffer::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
@@ -555,19 +568,19 @@ mod tests {
|
||||
assert_eq!(backend.get_cursor().unwrap(), (1, 4));
|
||||
|
||||
// As such the buffer should remain unchanged
|
||||
backend.assert_buffer(&Buffer::with_lines(vec![
|
||||
backend.assert_buffer_lines([
|
||||
"aaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
"dddddddddd",
|
||||
"eeeeeeeeee",
|
||||
]));
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_multiple_lines_past_last_line() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines(vec![
|
||||
backend.buffer = Buffer::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
@@ -580,19 +593,19 @@ mod tests {
|
||||
backend.append_lines(3).unwrap();
|
||||
assert_eq!(backend.get_cursor().unwrap(), (1, 4));
|
||||
|
||||
backend.assert_buffer(&Buffer::with_lines(vec![
|
||||
backend.assert_buffer_lines([
|
||||
"cccccccccc",
|
||||
"dddddddddd",
|
||||
"eeeeeeeeee",
|
||||
" ",
|
||||
" ",
|
||||
]));
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_multiple_lines_where_cursor_at_end_appends_height_lines() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines(vec![
|
||||
backend.buffer = Buffer::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
@@ -605,19 +618,19 @@ mod tests {
|
||||
backend.append_lines(5).unwrap();
|
||||
assert_eq!(backend.get_cursor().unwrap(), (1, 4));
|
||||
|
||||
backend.assert_buffer(&Buffer::with_lines(vec![
|
||||
backend.assert_buffer_lines([
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
]));
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_multiple_lines_where_cursor_appends_height_lines() {
|
||||
let mut backend = TestBackend::new(10, 5);
|
||||
backend.buffer = Buffer::with_lines(vec![
|
||||
backend.buffer = Buffer::with_lines([
|
||||
"aaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
@@ -630,13 +643,13 @@ mod tests {
|
||||
backend.append_lines(5).unwrap();
|
||||
assert_eq!(backend.get_cursor().unwrap(), (1, 4));
|
||||
|
||||
backend.assert_buffer(&Buffer::with_lines(vec![
|
||||
backend.assert_buffer_lines([
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccc",
|
||||
"dddddddddd",
|
||||
"eeeeeeeeee",
|
||||
" ",
|
||||
]));
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,57 +1,44 @@
|
||||
/// Assert that two buffers are equal by comparing their areas and content.
|
||||
///
|
||||
/// On panic, displays the areas or the content and a diff of the contents.
|
||||
/// # Panics
|
||||
/// When the buffers differ this method panics and displays the differences similar to
|
||||
/// `assert_eq!()`.
|
||||
#[deprecated = "use assert_eq!(&actual, &expected)"]
|
||||
#[macro_export]
|
||||
macro_rules! assert_buffer_eq {
|
||||
($actual_expr:expr, $expected_expr:expr) => {
|
||||
match (&$actual_expr, &$expected_expr) {
|
||||
(actual, expected) => {
|
||||
if actual.area != expected.area {
|
||||
panic!(
|
||||
indoc::indoc!(
|
||||
"
|
||||
buffer areas not equal
|
||||
expected: {:?}
|
||||
actual: {:?}"
|
||||
),
|
||||
expected, actual
|
||||
);
|
||||
}
|
||||
let diff = expected.diff(&actual);
|
||||
if !diff.is_empty() {
|
||||
let nice_diff = diff
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, (x, y, cell))| {
|
||||
let expected_cell = expected.get(*x, *y);
|
||||
indoc::formatdoc! {"
|
||||
{i}: at ({x}, {y})
|
||||
expected: {expected_cell:?}
|
||||
actual: {cell:?}
|
||||
"}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
panic!(
|
||||
indoc::indoc!(
|
||||
"
|
||||
buffer contents not equal
|
||||
expected: {:?}
|
||||
actual: {:?}
|
||||
diff:
|
||||
{}"
|
||||
),
|
||||
expected, actual, nice_diff
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
actual.area == expected.area,
|
||||
"buffer areas not equal\nexpected: {expected:?}\nactual: {actual:?}",
|
||||
);
|
||||
let nice_diff = expected
|
||||
.diff(actual)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, (x, y, cell))| {
|
||||
let expected_cell = expected.get(x, y);
|
||||
format!("{i}: at ({x}, {y})\n expected: {expected_cell:?}\n actual: {cell:?}")
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
assert!(
|
||||
nice_diff.is_empty(),
|
||||
"buffer contents not equal\nexpected: {expected:?}\nactual: {actual:?}\ndiff:\n{nice_diff}",
|
||||
);
|
||||
// shouldn't get here, but this guards against future behavior
|
||||
// that changes equality but not area or content
|
||||
assert_eq!(actual, expected, "buffers not equal");
|
||||
assert_eq!(
|
||||
actual, expected,
|
||||
"buffers are not equal in an unexpected way. Please open an issue about this."
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use std::{
|
||||
cmp::min,
|
||||
fmt::{Debug, Formatter, Result},
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
@@ -55,22 +52,21 @@ pub struct Buffer {
|
||||
|
||||
impl Buffer {
|
||||
/// Returns a Buffer with all cells set to the default one
|
||||
#[must_use]
|
||||
pub fn empty(area: Rect) -> Self {
|
||||
let cell = Cell::default();
|
||||
Self::filled(area, &cell)
|
||||
Self::filled(area, &Cell::default())
|
||||
}
|
||||
|
||||
/// Returns a Buffer with all cells initialized with the attributes of the given Cell
|
||||
#[must_use]
|
||||
pub fn filled(area: Rect, cell: &Cell) -> Self {
|
||||
let size = area.area() as usize;
|
||||
let mut content = Vec::with_capacity(size);
|
||||
for _ in 0..size {
|
||||
content.push(cell.clone());
|
||||
}
|
||||
let content = vec![cell.clone(); size];
|
||||
Self { area, content }
|
||||
}
|
||||
|
||||
/// Returns a Buffer containing the given lines
|
||||
#[must_use]
|
||||
pub fn with_lines<'a, Iter>(lines: Iter) -> Self
|
||||
where
|
||||
Iter: IntoIterator,
|
||||
@@ -97,12 +93,14 @@ impl Buffer {
|
||||
}
|
||||
|
||||
/// Returns a reference to Cell at the given coordinates
|
||||
#[track_caller]
|
||||
pub fn get(&self, x: u16, y: u16) -> &Cell {
|
||||
let i = self.index_of(x, y);
|
||||
&self.content[i]
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to Cell at the given coordinates
|
||||
#[track_caller]
|
||||
pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
|
||||
let i = self.index_of(x, y);
|
||||
&mut self.content[i]
|
||||
@@ -134,6 +132,7 @@ impl Buffer {
|
||||
/// // starts at (200, 100).
|
||||
/// buffer.index_of(0, 0); // Panics
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn index_of(&self, x: u16, y: u16) -> usize {
|
||||
debug_assert!(
|
||||
x >= self.area.left()
|
||||
@@ -184,65 +183,56 @@ impl Buffer {
|
||||
}
|
||||
|
||||
/// Print a string, starting at the position (x, y)
|
||||
///
|
||||
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
pub fn set_string<T, S>(&mut self, x: u16, y: u16, string: T, style: S)
|
||||
where
|
||||
T: AsRef<str>,
|
||||
S: Into<Style>,
|
||||
{
|
||||
self.set_stringn(x, y, string, usize::MAX, style.into());
|
||||
self.set_stringn(x, y, string, usize::MAX, style);
|
||||
}
|
||||
|
||||
/// Print at most the first n characters of a string if enough space is available
|
||||
/// until the end of the line
|
||||
/// until the end of the line.
|
||||
///
|
||||
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
|
||||
/// your own type that implements [`Into<Style>`]).
|
||||
/// Use [`Buffer::set_string`] when the maximum amount of characters can be printed.
|
||||
pub fn set_stringn<T, S>(
|
||||
&mut self,
|
||||
x: u16,
|
||||
mut x: u16,
|
||||
y: u16,
|
||||
string: T,
|
||||
width: usize,
|
||||
max_width: usize,
|
||||
style: S,
|
||||
) -> (u16, u16)
|
||||
where
|
||||
T: AsRef<str>,
|
||||
S: Into<Style>,
|
||||
{
|
||||
let max_width = max_width.try_into().unwrap_or(u16::MAX);
|
||||
let mut remaining_width = self.area.right().saturating_sub(x).min(max_width);
|
||||
let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true)
|
||||
.map(|symbol| (symbol, symbol.width() as u16))
|
||||
.filter(|(_symbol, width)| *width > 0)
|
||||
.map_while(|(symbol, width)| {
|
||||
remaining_width = remaining_width.checked_sub(width)?;
|
||||
Some((symbol, width))
|
||||
});
|
||||
let style = style.into();
|
||||
let mut index = self.index_of(x, y);
|
||||
let mut x_offset = x as usize;
|
||||
let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true);
|
||||
let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
|
||||
for s in graphemes {
|
||||
let width = s.width();
|
||||
if width == 0 {
|
||||
continue;
|
||||
}
|
||||
// `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we
|
||||
// change dimensions to usize or u32 and someone resizes the terminal to 1x2^32.
|
||||
if width > max_offset.saturating_sub(x_offset) {
|
||||
break;
|
||||
}
|
||||
|
||||
self.content[index].set_symbol(s);
|
||||
self.content[index].set_style(style);
|
||||
for (symbol, width) in graphemes {
|
||||
self.get_mut(x, y).set_symbol(symbol).set_style(style);
|
||||
let next_symbol = x + width;
|
||||
x += 1;
|
||||
// Reset following cells if multi-width (they would be hidden by the grapheme),
|
||||
for i in index + 1..index + width {
|
||||
self.content[i].reset();
|
||||
while x < next_symbol {
|
||||
self.get_mut(x, y).reset();
|
||||
x += 1;
|
||||
}
|
||||
index += width;
|
||||
x_offset += width;
|
||||
}
|
||||
(x_offset as u16, y)
|
||||
(x, y)
|
||||
}
|
||||
|
||||
/// Print a line, starting at the position (x, y)
|
||||
pub fn set_line(&mut self, x: u16, y: u16, line: &Line<'_>, width: u16) -> (u16, u16) {
|
||||
let mut remaining_width = width;
|
||||
pub fn set_line(&mut self, x: u16, y: u16, line: &Line<'_>, max_width: u16) -> (u16, u16) {
|
||||
let mut remaining_width = max_width;
|
||||
let mut x = x;
|
||||
for span in line {
|
||||
if remaining_width == 0 {
|
||||
@@ -263,8 +253,8 @@ impl Buffer {
|
||||
}
|
||||
|
||||
/// Print a span, starting at the position (x, y)
|
||||
pub fn set_span(&mut self, x: u16, y: u16, span: &Span<'_>, width: u16) -> (u16, u16) {
|
||||
self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style)
|
||||
pub fn set_span(&mut self, x: u16, y: u16, span: &Span<'_>, max_width: u16) -> (u16, u16) {
|
||||
self.set_stringn(x, y, &span.content, max_width as usize, span.style)
|
||||
}
|
||||
|
||||
/// Set the style of all cells in the given area.
|
||||
@@ -303,8 +293,7 @@ impl Buffer {
|
||||
/// Merge an other buffer into this one
|
||||
pub fn merge(&mut self, other: &Self) {
|
||||
let area = self.area.union(other.area);
|
||||
let cell = Cell::default();
|
||||
self.content.resize(area.area() as usize, cell.clone());
|
||||
self.content.resize(area.area() as usize, Cell::default());
|
||||
|
||||
// Move original content to the appropriate space
|
||||
let size = self.area.area() as usize;
|
||||
@@ -314,7 +303,7 @@ impl Buffer {
|
||||
let k = ((y - area.y) * area.width + x - area.x) as usize;
|
||||
if i != k {
|
||||
self.content[k] = self.content[i].clone();
|
||||
self.content[i] = cell.clone();
|
||||
self.content[i] = Cell::default();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,7 +372,7 @@ impl Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Buffer {
|
||||
impl fmt::Debug for Buffer {
|
||||
/// Writes a debug representation of the buffer to the given formatter.
|
||||
///
|
||||
/// The format is like a pretty printed struct, with the following fields:
|
||||
@@ -391,11 +380,14 @@ impl Debug for Buffer {
|
||||
/// * `content`: displayed as a list of strings representing the content of the buffer
|
||||
/// * `styles`: displayed as a list of: `{ x: 1, y: 2, fg: Color::Red, bg: Color::Blue,
|
||||
/// modifier: Modifier::BOLD }` only showing a value when there is a change in style.
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.write_fmt(format_args!(
|
||||
"Buffer {{\n area: {:?},\n content: [\n",
|
||||
&self.area
|
||||
))?;
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_fmt(format_args!("Buffer {{\n area: {:?}", &self.area))?;
|
||||
|
||||
if self.area.is_empty() {
|
||||
return f.write_str("\n}");
|
||||
}
|
||||
|
||||
f.write_str(",\n content: [\n")?;
|
||||
let mut last_style = None;
|
||||
let mut styles = vec![];
|
||||
for (y, line) in self.content.chunks(self.area.width as usize).enumerate() {
|
||||
@@ -426,12 +418,13 @@ impl Debug for Buffer {
|
||||
}
|
||||
}
|
||||
}
|
||||
f.write_str("\",")?;
|
||||
if !overwritten.is_empty() {
|
||||
f.write_fmt(format_args!(
|
||||
"// hidden by multi-width symbols: {overwritten:?}"
|
||||
" // hidden by multi-width symbols: {overwritten:?}"
|
||||
))?;
|
||||
}
|
||||
f.write_str("\",\n")?;
|
||||
f.write_str("\n")?;
|
||||
}
|
||||
f.write_str(" ],\n styles: [\n")?;
|
||||
for s in styles {
|
||||
@@ -459,7 +452,6 @@ mod tests {
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
use super::*;
|
||||
use crate::assert_buffer_eq;
|
||||
|
||||
fn cell(s: &str) -> Cell {
|
||||
let mut cell = Cell::default();
|
||||
@@ -468,10 +460,40 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug() {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 12, 2));
|
||||
buf.set_string(0, 0, "Hello World!", Style::default());
|
||||
buf.set_string(
|
||||
fn debug_empty_buffer() {
|
||||
let buffer = Buffer::empty(Rect::ZERO);
|
||||
let result = format!("{buffer:?}");
|
||||
println!("{result}");
|
||||
let expected = "Buffer {\n area: Rect { x: 0, y: 0, width: 0, height: 0 }\n}";
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[cfg(feature = "underline-color")]
|
||||
#[test]
|
||||
fn debug_grapheme_override() {
|
||||
let buffer = Buffer::with_lines(["a🦀b"]);
|
||||
let result = format!("{buffer:?}");
|
||||
println!("{result}");
|
||||
let expected = indoc::indoc!(
|
||||
r#"
|
||||
Buffer {
|
||||
area: Rect { x: 0, y: 0, width: 4, height: 1 },
|
||||
content: [
|
||||
"a🦀b", // hidden by multi-width symbols: [(2, " ")]
|
||||
],
|
||||
styles: [
|
||||
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
]
|
||||
}"#
|
||||
);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_some_example() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 12, 2));
|
||||
buffer.set_string(0, 0, "Hello World!", Style::default());
|
||||
buffer.set_string(
|
||||
0,
|
||||
1,
|
||||
"G'day World!",
|
||||
@@ -480,42 +502,40 @@ mod tests {
|
||||
.bg(Color::Yellow)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
);
|
||||
let result = format!("{buffer:?}");
|
||||
println!("{result}");
|
||||
#[cfg(feature = "underline-color")]
|
||||
assert_eq!(
|
||||
format!("{buf:?}"),
|
||||
indoc::indoc!(
|
||||
"
|
||||
Buffer {
|
||||
area: Rect { x: 0, y: 0, width: 12, height: 2 },
|
||||
content: [
|
||||
\"Hello World!\",
|
||||
\"G'day World!\",
|
||||
],
|
||||
styles: [
|
||||
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 0, y: 1, fg: Green, bg: Yellow, underline: Reset, modifier: BOLD,
|
||||
]
|
||||
}"
|
||||
)
|
||||
let expected = indoc::indoc!(
|
||||
r#"
|
||||
Buffer {
|
||||
area: Rect { x: 0, y: 0, width: 12, height: 2 },
|
||||
content: [
|
||||
"Hello World!",
|
||||
"G'day World!",
|
||||
],
|
||||
styles: [
|
||||
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 0, y: 1, fg: Green, bg: Yellow, underline: Reset, modifier: BOLD,
|
||||
]
|
||||
}"#
|
||||
);
|
||||
#[cfg(not(feature = "underline-color"))]
|
||||
assert_eq!(
|
||||
format!("{buf:?}"),
|
||||
indoc::indoc!(
|
||||
"
|
||||
Buffer {
|
||||
area: Rect { x: 0, y: 0, width: 12, height: 2 },
|
||||
content: [
|
||||
\"Hello World!\",
|
||||
\"G'day World!\",
|
||||
],
|
||||
styles: [
|
||||
x: 0, y: 0, fg: Reset, bg: Reset, modifier: NONE,
|
||||
x: 0, y: 1, fg: Green, bg: Yellow, modifier: BOLD,
|
||||
]
|
||||
}"
|
||||
)
|
||||
let expected = indoc::indoc!(
|
||||
r#"
|
||||
Buffer {
|
||||
area: Rect { x: 0, y: 0, width: 12, height: 2 },
|
||||
content: [
|
||||
"Hello World!",
|
||||
"G'day World!",
|
||||
],
|
||||
styles: [
|
||||
x: 0, y: 0, fg: Reset, bg: Reset, modifier: NONE,
|
||||
x: 0, y: 1, fg: Green, bg: Yellow, modifier: BOLD,
|
||||
]
|
||||
}"#
|
||||
);
|
||||
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -559,27 +579,27 @@ mod tests {
|
||||
|
||||
// Zero-width
|
||||
buffer.set_stringn(0, 0, "aaa", 0, Style::default());
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" "]));
|
||||
assert_eq!(buffer, Buffer::with_lines([" "]));
|
||||
|
||||
buffer.set_string(0, 0, "aaa", Style::default());
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["aaa "]));
|
||||
assert_eq!(buffer, Buffer::with_lines(["aaa "]));
|
||||
|
||||
// Width limit:
|
||||
buffer.set_stringn(0, 0, "bbbbbbbbbbbbbb", 4, Style::default());
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["bbbb "]));
|
||||
assert_eq!(buffer, Buffer::with_lines(["bbbb "]));
|
||||
|
||||
buffer.set_string(0, 0, "12345", Style::default());
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["12345"]));
|
||||
assert_eq!(buffer, Buffer::with_lines(["12345"]));
|
||||
|
||||
// Width truncation:
|
||||
buffer.set_string(0, 0, "123456", Style::default());
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["12345"]));
|
||||
assert_eq!(buffer, Buffer::with_lines(["12345"]));
|
||||
|
||||
// multi-line
|
||||
buffer = Buffer::empty(Rect::new(0, 0, 5, 2));
|
||||
buffer.set_string(0, 0, "12345", Style::default());
|
||||
buffer.set_string(0, 1, "67890", Style::default());
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["12345", "67890"]));
|
||||
assert_eq!(buffer, Buffer::with_lines(["12345", "67890"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -590,7 +610,7 @@ mod tests {
|
||||
// multi-width overwrite
|
||||
buffer.set_string(0, 0, "aaaaa", Style::default());
|
||||
buffer.set_string(0, 0, "称号", Style::default());
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["称号a"]));
|
||||
assert_eq!(buffer, Buffer::with_lines(["称号a"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -601,12 +621,12 @@ mod tests {
|
||||
// Leading grapheme with zero width
|
||||
let s = "\u{1}a";
|
||||
buffer.set_stringn(0, 0, s, 1, Style::default());
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["a"]));
|
||||
assert_eq!(buffer, Buffer::with_lines(["a"]));
|
||||
|
||||
// Trailing grapheme with zero with
|
||||
let s = "a\u{1}";
|
||||
buffer.set_stringn(0, 0, s, 1, Style::default());
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["a"]));
|
||||
assert_eq!(buffer, Buffer::with_lines(["a"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -614,11 +634,11 @@ mod tests {
|
||||
let area = Rect::new(0, 0, 5, 1);
|
||||
let mut buffer = Buffer::empty(area);
|
||||
buffer.set_string(0, 0, "コン", Style::default());
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["コン "]));
|
||||
assert_eq!(buffer, Buffer::with_lines(["コン "]));
|
||||
|
||||
// Only 1 space left.
|
||||
buffer.set_string(0, 0, "コンピ", Style::default());
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["コン "]));
|
||||
assert_eq!(buffer, Buffer::with_lines(["コン "]));
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
@@ -643,7 +663,7 @@ mod tests {
|
||||
// set_line
|
||||
let mut expected_buffer = Buffer::empty(small_one_line_buffer.area);
|
||||
expected_buffer.set_string(0, 0, expected, Style::default());
|
||||
assert_buffer_eq!(small_one_line_buffer, expected_buffer);
|
||||
assert_eq!(small_one_line_buffer, expected_buffer);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -684,28 +704,39 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn set_style() {
|
||||
let mut buffer = Buffer::with_lines(vec!["aaaaa", "bbbbb", "ccccc"]);
|
||||
let mut buffer = Buffer::with_lines(["aaaaa", "bbbbb", "ccccc"]);
|
||||
buffer.set_style(Rect::new(0, 1, 5, 1), Style::new().red());
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec!["aaaaa".into(), "bbbbb".red(), "ccccc".into(),])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
"aaaaa".into(),
|
||||
"bbbbb".red(),
|
||||
"ccccc".into(),
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_style_does_not_panic_when_out_of_area() {
|
||||
let mut buffer = Buffer::with_lines(vec!["aaaaa", "bbbbb", "ccccc"]);
|
||||
let mut buffer = Buffer::with_lines(["aaaaa", "bbbbb", "ccccc"]);
|
||||
buffer.set_style(Rect::new(0, 1, 10, 3), Style::new().red());
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec!["aaaaa".into(), "bbbbb".red(), "ccccc".red(),])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
"aaaaa".into(),
|
||||
"bbbbb".red(),
|
||||
"ccccc".red(),
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_lines() {
|
||||
let buffer =
|
||||
Buffer::with_lines(vec!["┌────────┐", "│コンピュ│", "│ーa 上で│", "└────────┘"]);
|
||||
#[rustfmt::skip]
|
||||
let buffer = Buffer::with_lines([
|
||||
"┌────────┐",
|
||||
"│コンピュ│",
|
||||
"│ーa 上で│",
|
||||
"└────────┘",
|
||||
]);
|
||||
assert_eq!(buffer.area.x, 0);
|
||||
assert_eq!(buffer.area.y, 0);
|
||||
assert_eq!(buffer.area.width, 10);
|
||||
@@ -741,14 +772,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn diff_single_width() {
|
||||
let prev = Buffer::with_lines(vec![
|
||||
let prev = Buffer::with_lines([
|
||||
" ",
|
||||
"┌Title─┐ ",
|
||||
"│ │ ",
|
||||
"│ │ ",
|
||||
"└──────┘ ",
|
||||
]);
|
||||
let next = Buffer::with_lines(vec![
|
||||
let next = Buffer::with_lines([
|
||||
" ",
|
||||
"┌TITLE─┐ ",
|
||||
"│ │ ",
|
||||
@@ -770,11 +801,11 @@ mod tests {
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn diff_multi_width() {
|
||||
let prev = Buffer::with_lines(vec![
|
||||
let prev = Buffer::with_lines([
|
||||
"┌Title─┐ ",
|
||||
"└──────┘ ",
|
||||
]);
|
||||
let next = Buffer::with_lines(vec![
|
||||
let next = Buffer::with_lines([
|
||||
"┌称号──┐ ",
|
||||
"└──────┘ ",
|
||||
]);
|
||||
@@ -793,8 +824,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn diff_multi_width_offset() {
|
||||
let prev = Buffer::with_lines(vec!["┌称号──┐"]);
|
||||
let next = Buffer::with_lines(vec!["┌─称号─┐"]);
|
||||
let prev = Buffer::with_lines(["┌称号──┐"]);
|
||||
let next = Buffer::with_lines(["┌─称号─┐"]);
|
||||
|
||||
let diff = prev.diff(&next);
|
||||
assert_eq!(
|
||||
@@ -805,8 +836,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn diff_skip() {
|
||||
let prev = Buffer::with_lines(vec!["123"]);
|
||||
let mut next = Buffer::with_lines(vec!["456"]);
|
||||
let prev = Buffer::with_lines(["123"]);
|
||||
let mut next = Buffer::with_lines(["456"]);
|
||||
for i in 1..3 {
|
||||
next.content[i].set_skip(true);
|
||||
}
|
||||
@@ -836,7 +867,7 @@ mod tests {
|
||||
Cell::default().set_symbol("2"),
|
||||
);
|
||||
one.merge(&two);
|
||||
assert_buffer_eq!(one, Buffer::with_lines(vec!["11", "11", "22", "22"]));
|
||||
assert_eq!(one, Buffer::with_lines(["11", "11", "22", "22"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -860,10 +891,7 @@ mod tests {
|
||||
Cell::default().set_symbol("2"),
|
||||
);
|
||||
one.merge(&two);
|
||||
assert_buffer_eq!(
|
||||
one,
|
||||
Buffer::with_lines(vec!["22 ", "22 ", " 11", " 11"])
|
||||
);
|
||||
assert_eq!(one, Buffer::with_lines(["22 ", "22 ", " 11", " 11"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -887,14 +915,14 @@ mod tests {
|
||||
Cell::default().set_symbol("2"),
|
||||
);
|
||||
one.merge(&two);
|
||||
let mut merged = Buffer::with_lines(vec!["222 ", "222 ", "2221", "2221"]);
|
||||
let mut merged = Buffer::with_lines(["222 ", "222 ", "2221", "2221"]);
|
||||
merged.area = Rect {
|
||||
x: 1,
|
||||
y: 1,
|
||||
width: 4,
|
||||
height: 4,
|
||||
};
|
||||
assert_buffer_eq!(one, merged);
|
||||
assert_eq!(one, merged);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -953,6 +981,6 @@ mod tests {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 3, 2));
|
||||
buf.set_string(0, 0, "foo", Style::new().red());
|
||||
buf.set_string(0, 1, "bar", Style::new().blue());
|
||||
assert_eq!(buf, Buffer::with_lines(vec!["foo".red(), "bar".blue()]));
|
||||
assert_eq!(buf, Buffer::with_lines(["foo".red(), "bar".blue()]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use compact_str::CompactString;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::fmt::{self, Display};
|
||||
use std::fmt;
|
||||
|
||||
use itertools::Itertools;
|
||||
use strum::EnumIs;
|
||||
@@ -362,7 +362,7 @@ impl Default for Constraint {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Constraint {
|
||||
impl fmt::Display for Constraint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Percentage(p) => write!(f, "Percentage({p})"),
|
||||
|
||||
@@ -448,7 +448,7 @@ impl Layout {
|
||||
/// # }
|
||||
pub fn areas<const N: usize>(&self, area: Rect) -> [Rect; N] {
|
||||
let (areas, _) = self.split_with_spacers(area);
|
||||
areas.to_vec().try_into().expect("invalid number of rects")
|
||||
areas.as_ref().try_into().expect("invalid number of rects")
|
||||
}
|
||||
|
||||
/// Split the rect into a number of sub-rects according to the given [`Layout`] and return just
|
||||
@@ -482,7 +482,7 @@ impl Layout {
|
||||
pub fn spacers<const N: usize>(&self, area: Rect) -> [Rect; N] {
|
||||
let (_, spacers) = self.split_with_spacers(area);
|
||||
spacers
|
||||
.to_vec()
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("invalid number of rects")
|
||||
}
|
||||
@@ -1334,7 +1334,6 @@ mod tests {
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::{
|
||||
assert_buffer_eq,
|
||||
layout::flex::Flex,
|
||||
prelude::{Constraint::*, *},
|
||||
widgets::Paragraph,
|
||||
@@ -1361,8 +1360,7 @@ mod tests {
|
||||
let s = c.to_string().repeat(area.width as usize);
|
||||
Paragraph::new(s).render(layout[i], &mut buffer);
|
||||
}
|
||||
let expected = Buffer::with_lines(vec![expected]);
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
assert_eq!(buffer, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::fmt::{self, Display};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub struct Margin {
|
||||
@@ -15,7 +15,7 @@ impl Margin {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Margin {
|
||||
impl fmt::Display for Margin {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}x{}", self.horizontal, self.vertical)
|
||||
}
|
||||
|
||||
@@ -47,6 +47,14 @@ impl fmt::Display for Rect {
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
/// A zero sized Rect at position 0,0
|
||||
pub const ZERO: Self = Self {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
|
||||
/// Creates a new `Rect`, with width and height limited to keep the area under max `u16`. If
|
||||
/// clipped, aspect ratio will be preserved.
|
||||
pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
|
||||
@@ -111,13 +119,14 @@ impl Rect {
|
||||
/// Returns a new `Rect` inside the current one, with the given margin on each side.
|
||||
///
|
||||
/// If the margin is larger than the `Rect`, the returned `Rect` will have no area.
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)] // See PR #1008
|
||||
#[must_use = "method returns the modified value"]
|
||||
pub fn inner(self, margin: &Margin) -> Self {
|
||||
pub const fn inner(self, margin: &Margin) -> Self {
|
||||
let doubled_margin_horizontal = margin.horizontal.saturating_mul(2);
|
||||
let doubled_margin_vertical = margin.vertical.saturating_mul(2);
|
||||
|
||||
if self.width < doubled_margin_horizontal || self.height < doubled_margin_vertical {
|
||||
Self::default()
|
||||
Self::ZERO
|
||||
} else {
|
||||
Self {
|
||||
x: self.x.saturating_add(margin.horizontal),
|
||||
@@ -313,6 +322,18 @@ impl Rect {
|
||||
height: self.height,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[must_use]
|
||||
pub(crate) const fn indent_x(self, offset: u16) -> Self {
|
||||
Self {
|
||||
x: self.x.saturating_add(offset),
|
||||
width: self.width.saturating_sub(offset),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Position, Size)> for Rect {
|
||||
|
||||
13
src/lib.rs
13
src/lib.rs
@@ -139,8 +139,7 @@
|
||||
//!
|
||||
//! fn ui(frame: &mut Frame) {
|
||||
//! frame.render_widget(
|
||||
//! Paragraph::new("Hello World!")
|
||||
//! .block(Block::default().title("Greeting").borders(Borders::ALL)),
|
||||
//! Paragraph::new("Hello World!").block(Block::bordered().title("Greeting")),
|
||||
//! frame.size(),
|
||||
//! );
|
||||
//! }
|
||||
@@ -184,14 +183,8 @@
|
||||
//! [Constraint::Percentage(50), Constraint::Percentage(50)],
|
||||
//! )
|
||||
//! .split(main_layout[1]);
|
||||
//! frame.render_widget(
|
||||
//! Block::default().borders(Borders::ALL).title("Left"),
|
||||
//! inner_layout[0],
|
||||
//! );
|
||||
//! frame.render_widget(
|
||||
//! Block::default().borders(Borders::ALL).title("Right"),
|
||||
//! inner_layout[1],
|
||||
//! );
|
||||
//! frame.render_widget(Block::bordered().title("Left"), inner_layout[0]);
|
||||
//! frame.render_widget(Block::bordered().title("Right"), inner_layout[1]);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
|
||||
87
src/style.rs
87
src/style.rs
@@ -68,14 +68,14 @@
|
||||
//! [`prelude`]: crate::prelude
|
||||
//! [`Span`]: crate::text::Span
|
||||
|
||||
use std::fmt::{self, Debug};
|
||||
use std::fmt;
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
mod color;
|
||||
mod stylize;
|
||||
|
||||
pub use color::Color;
|
||||
pub use color::{Color, ParseColorError};
|
||||
pub use stylize::{Styled, Stylize};
|
||||
pub mod palette;
|
||||
|
||||
@@ -119,7 +119,7 @@ impl fmt::Debug for Modifier {
|
||||
if self.is_empty() {
|
||||
return write!(f, "NONE");
|
||||
}
|
||||
fmt::Debug::fmt(&self.0, f)
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,34 +550,30 @@ impl From<(Color, Color, Modifier, Modifier)> for Style {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rstest::rstest;
|
||||
|
||||
fn styles() -> Vec<Style> {
|
||||
vec![
|
||||
Style::default(),
|
||||
Style::default().fg(Color::Yellow),
|
||||
Style::default().bg(Color::Yellow),
|
||||
Style::default().add_modifier(Modifier::BOLD),
|
||||
Style::default().remove_modifier(Modifier::BOLD),
|
||||
Style::default().add_modifier(Modifier::ITALIC),
|
||||
Style::default().remove_modifier(Modifier::ITALIC),
|
||||
Style::default().add_modifier(Modifier::ITALIC | Modifier::BOLD),
|
||||
Style::default().remove_modifier(Modifier::ITALIC | Modifier::BOLD),
|
||||
]
|
||||
}
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn combined_patch_gives_same_result_as_individual_patch() {
|
||||
let styles = styles();
|
||||
let styles = [
|
||||
Style::new(),
|
||||
Style::new().fg(Color::Yellow),
|
||||
Style::new().bg(Color::Yellow),
|
||||
Style::new().add_modifier(Modifier::BOLD),
|
||||
Style::new().remove_modifier(Modifier::BOLD),
|
||||
Style::new().add_modifier(Modifier::ITALIC),
|
||||
Style::new().remove_modifier(Modifier::ITALIC),
|
||||
Style::new().add_modifier(Modifier::ITALIC | Modifier::BOLD),
|
||||
Style::new().remove_modifier(Modifier::ITALIC | Modifier::BOLD),
|
||||
];
|
||||
for &a in &styles {
|
||||
for &b in &styles {
|
||||
for &c in &styles {
|
||||
for &d in &styles {
|
||||
let combined = a.patch(b.patch(c.patch(d)));
|
||||
|
||||
assert_eq!(
|
||||
Style::default().patch(a).patch(b).patch(c).patch(d),
|
||||
Style::default().patch(combined)
|
||||
Style::new().patch(a).patch(b).patch(c).patch(d),
|
||||
Style::new().patch(a.patch(b.patch(c.patch(d))))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -589,7 +585,7 @@ mod tests {
|
||||
fn combine_individual_modifiers() {
|
||||
use crate::{buffer::Buffer, layout::Rect};
|
||||
|
||||
let mods = vec![
|
||||
let mods = [
|
||||
Modifier::BOLD,
|
||||
Modifier::DIM,
|
||||
Modifier::ITALIC,
|
||||
@@ -603,37 +599,30 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
|
||||
for m in &mods {
|
||||
for m in mods {
|
||||
buffer.get_mut(0, 0).set_style(Style::reset());
|
||||
buffer
|
||||
.get_mut(0, 0)
|
||||
.set_style(Style::default().add_modifier(*m));
|
||||
buffer.get_mut(0, 0).set_style(Style::new().add_modifier(m));
|
||||
let style = buffer.get(0, 0).style();
|
||||
assert!(style.add_modifier.contains(*m));
|
||||
assert!(!style.sub_modifier.contains(*m));
|
||||
assert!(style.add_modifier.contains(m));
|
||||
assert!(!style.sub_modifier.contains(m));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modifier_debug() {
|
||||
assert_eq!(format!("{:?}", Modifier::empty()), "NONE");
|
||||
assert_eq!(format!("{:?}", Modifier::BOLD), "BOLD");
|
||||
assert_eq!(format!("{:?}", Modifier::DIM), "DIM");
|
||||
assert_eq!(format!("{:?}", Modifier::ITALIC), "ITALIC");
|
||||
assert_eq!(format!("{:?}", Modifier::UNDERLINED), "UNDERLINED");
|
||||
assert_eq!(format!("{:?}", Modifier::SLOW_BLINK), "SLOW_BLINK");
|
||||
assert_eq!(format!("{:?}", Modifier::RAPID_BLINK), "RAPID_BLINK");
|
||||
assert_eq!(format!("{:?}", Modifier::REVERSED), "REVERSED");
|
||||
assert_eq!(format!("{:?}", Modifier::HIDDEN), "HIDDEN");
|
||||
assert_eq!(format!("{:?}", Modifier::CROSSED_OUT), "CROSSED_OUT");
|
||||
assert_eq!(
|
||||
format!("{:?}", Modifier::BOLD | Modifier::DIM),
|
||||
"BOLD | DIM"
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{:?}", Modifier::all()),
|
||||
"BOLD | DIM | ITALIC | UNDERLINED | SLOW_BLINK | RAPID_BLINK | REVERSED | HIDDEN | CROSSED_OUT"
|
||||
);
|
||||
#[rstest]
|
||||
#[case(Modifier::empty(), "NONE")]
|
||||
#[case(Modifier::BOLD, "BOLD")]
|
||||
#[case(Modifier::DIM, "DIM")]
|
||||
#[case(Modifier::ITALIC, "ITALIC")]
|
||||
#[case(Modifier::UNDERLINED, "UNDERLINED")]
|
||||
#[case(Modifier::SLOW_BLINK, "SLOW_BLINK")]
|
||||
#[case(Modifier::RAPID_BLINK, "RAPID_BLINK")]
|
||||
#[case(Modifier::REVERSED, "REVERSED")]
|
||||
#[case(Modifier::HIDDEN, "HIDDEN")]
|
||||
#[case(Modifier::CROSSED_OUT, "CROSSED_OUT")]
|
||||
#[case(Modifier::BOLD | Modifier::DIM, "BOLD | DIM")]
|
||||
#[case(Modifier::all(), "BOLD | DIM | ITALIC | UNDERLINED | SLOW_BLINK | RAPID_BLINK | REVERSED | HIDDEN | CROSSED_OUT")]
|
||||
fn modifier_debug(#[case] modifier: Modifier, #[case] expected: &str) {
|
||||
assert_eq!(format!("{modifier:?}"), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#![allow(clippy::unreadable_literal)]
|
||||
|
||||
use std::{
|
||||
fmt::{self, Debug, Display},
|
||||
str::FromStr,
|
||||
};
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
/// ANSI Color
|
||||
///
|
||||
@@ -66,7 +63,6 @@ use std::{
|
||||
///
|
||||
/// [ANSI color table]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub enum Color {
|
||||
/// Resets the foreground or background color
|
||||
#[default]
|
||||
@@ -141,14 +137,102 @@ impl Color {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for Color {
|
||||
/// This utilises the [`fmt::Display`] implementation for serialization.
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for Color {
|
||||
/// This is used to deserialize a value into Color via serde.
|
||||
///
|
||||
/// This implementation uses the `FromStr` trait to deserialize strings, so named colours, RGB,
|
||||
/// and indexed values are able to be deserialized. In addition, values that were produced by
|
||||
/// the the older serialization implementation of Color are also able to be deserialized.
|
||||
///
|
||||
/// Prior to v0.26.0, Ratatui would be serialized using a map for indexed and RGB values, for
|
||||
/// examples in json `{"Indexed": 10}` and `{"Rgb": [255, 0, 255]}` respectively. Now they are
|
||||
/// serialized using the string representation of the index and the RGB hex value, for example
|
||||
/// in json it would now be `"10"` and `"#FF00FF"` respectively.
|
||||
///
|
||||
/// See the [`Color`] documentation for more information on color names.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::prelude::*;
|
||||
///
|
||||
/// #[derive(Debug, serde::Deserialize)]
|
||||
/// struct Theme {
|
||||
/// color: Color,
|
||||
/// }
|
||||
///
|
||||
/// # fn get_theme() -> Result<(), serde_json::Error> {
|
||||
/// let theme: Theme = serde_json::from_str(r#"{"color": "bright-white"}"#)?;
|
||||
/// assert_eq!(theme.color, Color::White);
|
||||
///
|
||||
/// let theme: Theme = serde_json::from_str(r##"{"color": "#00FF00"}"##)?;
|
||||
/// assert_eq!(theme.color, Color::Rgb(0, 255, 0));
|
||||
///
|
||||
/// let theme: Theme = serde_json::from_str(r#"{"color": "42"}"#)?;
|
||||
/// assert_eq!(theme.color, Color::Indexed(42));
|
||||
///
|
||||
/// let err = serde_json::from_str::<Theme>(r#"{"color": "invalid"}"#).unwrap_err();
|
||||
/// assert!(err.is_data());
|
||||
/// assert_eq!(
|
||||
/// err.to_string(),
|
||||
/// "Failed to parse Colors at line 1 column 20"
|
||||
/// );
|
||||
///
|
||||
/// // Deserializing from the previous serialization implementation
|
||||
/// let theme: Theme = serde_json::from_str(r#"{"color": {"Rgb":[255,0,255]}}"#)?;
|
||||
/// assert_eq!(theme.color, Color::Rgb(255, 0, 255));
|
||||
///
|
||||
/// let theme: Theme = serde_json::from_str(r#"{"color": {"Indexed":10}}"#)?;
|
||||
/// assert_eq!(theme.color, Color::Indexed(10));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
FromStr::from_str(&s).map_err(serde::de::Error::custom)
|
||||
/// Colors are currently serialized with the `Display` implementation, so
|
||||
/// RGB values are serialized via hex, for example "#FFFFFF".
|
||||
///
|
||||
/// Previously they were serialized using serde derive, which encoded
|
||||
/// RGB values as a map, for example { "rgb": [255, 255, 255] }.
|
||||
///
|
||||
/// The deserialization implementation utilises a `Helper` struct
|
||||
/// to be able to support both formats for backwards compatibility.
|
||||
#[derive(serde::Deserialize)]
|
||||
enum ColorWrapper {
|
||||
Rgb(u8, u8, u8),
|
||||
Indexed(u8),
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum ColorFormat {
|
||||
V2(String),
|
||||
V1(ColorWrapper),
|
||||
}
|
||||
|
||||
let multi_type = ColorFormat::deserialize(deserializer)
|
||||
.map_err(|err| serde::de::Error::custom(format!("Failed to parse Colors: {err}")))?;
|
||||
match multi_type {
|
||||
ColorFormat::V2(s) => FromStr::from_str(&s).map_err(serde::de::Error::custom),
|
||||
ColorFormat::V1(color_wrapper) => match color_wrapper {
|
||||
ColorWrapper::Rgb(red, green, blue) => Ok(Self::Rgb(red, green, blue)),
|
||||
ColorWrapper::Indexed(index) => Ok(Self::Indexed(index)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,8 +240,8 @@ impl<'de> serde::Deserialize<'de> for Color {
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub struct ParseColorError;
|
||||
|
||||
impl std::fmt::Display for ParseColorError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl fmt::Display for ParseColorError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Failed to parse Colors")
|
||||
}
|
||||
}
|
||||
@@ -249,7 +333,7 @@ impl FromStr for Color {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Color {
|
||||
impl fmt::Display for Color {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Reset => write!(f, "Reset"),
|
||||
@@ -579,4 +663,42 @@ mod tests {
|
||||
Color::deserialize("#00000000".into_deserializer());
|
||||
assert!(color.is_err());
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[test]
|
||||
fn serialize_then_deserialize() -> Result<(), serde_json::Error> {
|
||||
let json_rgb = serde_json::to_string(&Color::Rgb(255, 0, 255))?;
|
||||
assert_eq!(json_rgb, r##""#FF00FF""##);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Color>(&json_rgb)?,
|
||||
Color::Rgb(255, 0, 255)
|
||||
);
|
||||
|
||||
let json_white = serde_json::to_string(&Color::White)?;
|
||||
assert_eq!(json_white, r#""White""#);
|
||||
|
||||
let json_indexed = serde_json::to_string(&Color::Indexed(10))?;
|
||||
assert_eq!(json_indexed, r#""10""#);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Color>(&json_indexed)?,
|
||||
Color::Indexed(10)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[test]
|
||||
fn deserialize_with_previous_format() -> Result<(), serde_json::Error> {
|
||||
assert_eq!(Color::White, serde_json::from_str::<Color>("\"White\"")?);
|
||||
assert_eq!(
|
||||
Color::Rgb(255, 0, 255),
|
||||
serde_json::from_str::<Color>(r#"{"Rgb":[255,0,255]}"#)?
|
||||
);
|
||||
assert_eq!(
|
||||
Color::Indexed(10),
|
||||
serde_json::from_str::<Color>(r#"{"Indexed":10}"#)?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,11 +133,7 @@ macro_rules! modifier {
|
||||
/// "world".green().on_yellow().not_bold(),
|
||||
/// ]);
|
||||
/// let paragraph = Paragraph::new(line).italic().underlined();
|
||||
/// let block = Block::default()
|
||||
/// .title("Title")
|
||||
/// .borders(Borders::ALL)
|
||||
/// .on_white()
|
||||
/// .bold();
|
||||
/// let block = Block::bordered().title("Title").on_white().bold();
|
||||
/// ```
|
||||
pub trait Stylize<'a, T>: Sized {
|
||||
#[must_use = "`bg` returns the modified style without modifying the original"]
|
||||
|
||||
@@ -65,7 +65,7 @@ impl Frame<'_> {
|
||||
/// # let backend = TestBackend::new(5, 5);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
/// # let mut frame = terminal.get_frame();
|
||||
/// let block = Block::default();
|
||||
/// let block = Block::new();
|
||||
/// let area = Rect::new(0, 0, 5, 5);
|
||||
/// frame.render_widget(block, area);
|
||||
/// ```
|
||||
@@ -87,7 +87,7 @@ impl Frame<'_> {
|
||||
/// # let backend = TestBackend::new(5, 5);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
/// # let mut frame = terminal.get_frame();
|
||||
/// let block = Block::default();
|
||||
/// let block = Block::new();
|
||||
/// let area = Rect::new(0, 0, 5, 5);
|
||||
/// frame.render_widget_ref(block, area);
|
||||
/// ```
|
||||
|
||||
@@ -25,21 +25,20 @@
|
||||
//! // Converted to Line(vec![
|
||||
//! // Span { content: Cow::Borrowed("My title"), style: Style { .. } }
|
||||
//! // ])
|
||||
//! let block = Block::default().title("My title");
|
||||
//! let block = Block::new().title("My title");
|
||||
//!
|
||||
//! // A simple string with a unique style.
|
||||
//! // Converted to Line(vec![
|
||||
//! // Span { content: Cow::Borrowed("My title"), style: Style { fg: Some(Color::Yellow), .. }
|
||||
//! // ])
|
||||
//! let block =
|
||||
//! Block::default().title(Span::styled("My title", Style::default().fg(Color::Yellow)));
|
||||
//! let block = Block::new().title(Span::styled("My title", Style::default().fg(Color::Yellow)));
|
||||
//!
|
||||
//! // A string with multiple styles.
|
||||
//! // Converted to Line(vec![
|
||||
//! // Span { content: Cow::Borrowed("My"), style: Style { fg: Some(Color::Yellow), .. } },
|
||||
//! // Span { content: Cow::Borrowed(" title"), .. }
|
||||
//! // ])
|
||||
//! let block = Block::default().title(vec![
|
||||
//! let block = Block::new().title(vec![
|
||||
//! Span::styled("My", Style::default().fg(Color::Yellow)),
|
||||
//! Span::raw(" title"),
|
||||
//! ]);
|
||||
|
||||
388
src/text/line.rs
388
src/text/line.rs
@@ -1,5 +1,8 @@
|
||||
#![deny(missing_docs)]
|
||||
use std::borrow::Cow;
|
||||
#![warn(clippy::pedantic, clippy::nursery, clippy::arithmetic_side_effects)]
|
||||
use std::{borrow::Cow, fmt};
|
||||
|
||||
use unicode_truncate::UnicodeTruncateStr;
|
||||
|
||||
use super::StyledGrapheme;
|
||||
use crate::prelude::*;
|
||||
@@ -552,32 +555,100 @@ impl Widget for Line<'_> {
|
||||
impl WidgetRef for Line<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
let area = area.intersection(buf.area);
|
||||
buf.set_style(area, self.style);
|
||||
let width = self.width() as u16;
|
||||
let offset = match self.alignment {
|
||||
Some(Alignment::Center) => (area.width.saturating_sub(width)) / 2,
|
||||
Some(Alignment::Right) => area.width.saturating_sub(width),
|
||||
Some(Alignment::Left) | None => 0,
|
||||
};
|
||||
let mut x = area.left().saturating_add(offset);
|
||||
for span in &self.spans {
|
||||
let span_width = span.width() as u16;
|
||||
let span_area = Rect {
|
||||
x,
|
||||
width: span_width.min(area.right() - x),
|
||||
..area
|
||||
};
|
||||
span.render(span_area, buf);
|
||||
x = x.saturating_add(span_width);
|
||||
if x >= area.right() {
|
||||
break;
|
||||
}
|
||||
if area.is_empty() {
|
||||
return;
|
||||
}
|
||||
let line_width = self.width();
|
||||
if line_width == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
buf.set_style(area, self.style);
|
||||
|
||||
let area_width = usize::from(area.width);
|
||||
let can_render_complete_line = line_width <= area_width;
|
||||
if can_render_complete_line {
|
||||
let indent_width = match self.alignment {
|
||||
Some(Alignment::Center) => (area_width.saturating_sub(line_width)) / 2,
|
||||
Some(Alignment::Right) => area_width.saturating_sub(line_width),
|
||||
Some(Alignment::Left) | None => 0,
|
||||
};
|
||||
let indent_width = u16::try_from(indent_width).unwrap_or(u16::MAX);
|
||||
let area = area.indent_x(indent_width);
|
||||
render_spans(&self.spans, area, buf, 0);
|
||||
} else {
|
||||
// There is not enough space to render the whole line. As the right side is truncated by
|
||||
// the area width, only truncate the left.
|
||||
let skip_width = match self.alignment {
|
||||
Some(Alignment::Center) => (line_width.saturating_sub(area_width)) / 2,
|
||||
Some(Alignment::Right) => line_width.saturating_sub(area_width),
|
||||
Some(Alignment::Left) | None => 0,
|
||||
};
|
||||
render_spans(&self.spans, area, buf, skip_width);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Line<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
/// Renders all the spans of the line that should be visible.
|
||||
fn render_spans(spans: &[Span], mut area: Rect, buf: &mut Buffer, span_skip_width: usize) {
|
||||
for (span, span_width, offset) in spans_after_width(spans, span_skip_width) {
|
||||
area = area.indent_x(offset);
|
||||
if area.is_empty() {
|
||||
break;
|
||||
}
|
||||
span.render_ref(area, buf);
|
||||
let span_width = u16::try_from(span_width).unwrap_or(u16::MAX);
|
||||
area = area.indent_x(span_width);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over the spans that lie after a given skip widtch from the start of the
|
||||
/// `Line` (including a partially visible span if the `skip_width` lands within a span).
|
||||
fn spans_after_width<'a>(
|
||||
spans: &'a [Span],
|
||||
mut skip_width: usize,
|
||||
) -> impl Iterator<Item = (Span<'a>, usize, u16)> {
|
||||
spans
|
||||
.iter()
|
||||
.map(|span| (span, span.width()))
|
||||
// Filter non visible spans out.
|
||||
.filter_map(move |(span, span_width)| {
|
||||
// Ignore spans that are completely before the offset. Decrement `span_skip_width` by
|
||||
// the span width until we find a span that is partially or completely visible.
|
||||
if skip_width >= span_width {
|
||||
skip_width = skip_width.saturating_sub(span_width);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Apply the skip from the start of the span, not the end as the end will be trimmed
|
||||
// when rendering the span to the buffer.
|
||||
let available_width = span_width.saturating_sub(skip_width);
|
||||
skip_width = 0; // ensure the next span is rendered in full
|
||||
Some((span, span_width, available_width))
|
||||
})
|
||||
.map(|(span, span_width, available_width)| {
|
||||
if span_width <= available_width {
|
||||
// Span is fully visible. Clone here is fast as the underlying content is `Cow`.
|
||||
return (span.clone(), span_width, 0u16);
|
||||
}
|
||||
// Span is only partially visible. As the end is truncated by the area width, only
|
||||
// truncate the start of the span.
|
||||
let (content, actual_width) = span.content.unicode_truncate_start(available_width);
|
||||
|
||||
// When the first grapheme of the span was truncated, start rendering from a position
|
||||
// that takes that into account by indenting the start of the area
|
||||
let first_grapheme_offset = available_width.saturating_sub(actual_width);
|
||||
let first_grapheme_offset = u16::try_from(first_grapheme_offset).unwrap_or(u16::MAX);
|
||||
(
|
||||
Span::styled(content, span.style),
|
||||
actual_width,
|
||||
first_grapheme_offset,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
impl fmt::Display for Line<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for span in &self.spans {
|
||||
write!(f, "{span}")?;
|
||||
}
|
||||
@@ -878,8 +949,12 @@ mod tests {
|
||||
}
|
||||
|
||||
mod widget {
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use super::*;
|
||||
use crate::assert_buffer_eq;
|
||||
use crate::buffer::Cell;
|
||||
|
||||
const BLUE: Style = Style::new().fg(Color::Blue);
|
||||
const GREEN: Style = Style::new().fg(Color::Green);
|
||||
const ITALIC: Style = Style::new().add_modifier(Modifier::ITALIC);
|
||||
@@ -897,37 +972,36 @@ mod tests {
|
||||
fn render() {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
|
||||
hello_world().render(Rect::new(0, 0, 15, 1), &mut buf);
|
||||
let mut expected = Buffer::with_lines(vec!["Hello world! "]);
|
||||
let mut expected = Buffer::with_lines(["Hello world! "]);
|
||||
expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
|
||||
expected.set_style(Rect::new(0, 0, 6, 1), BLUE);
|
||||
expected.set_style(Rect::new(6, 0, 6, 1), GREEN);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_out_of_bounds(hello_world: Line<'static>, mut small_buf: Buffer) {
|
||||
let out_of_bounds = Rect::new(20, 20, 10, 1);
|
||||
hello_world.render(out_of_bounds, &mut small_buf);
|
||||
assert_buffer_eq!(small_buf, Buffer::empty(small_buf.area));
|
||||
assert_eq!(small_buf, Buffer::empty(small_buf.area));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_only_styles_line_area() {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 1));
|
||||
hello_world().render(Rect::new(0, 0, 15, 1), &mut buf);
|
||||
let mut expected = Buffer::with_lines(vec!["Hello world! "]);
|
||||
let mut expected = Buffer::with_lines(["Hello world! "]);
|
||||
expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
|
||||
expected.set_style(Rect::new(0, 0, 6, 1), BLUE);
|
||||
expected.set_style(Rect::new(6, 0, 6, 1), GREEN);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_truncates() {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 10, 1));
|
||||
Line::from("Hello world!").render(Rect::new(0, 0, 5, 1), &mut buf);
|
||||
let expected = Buffer::with_lines(vec!["Hello "]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -935,11 +1009,11 @@ mod tests {
|
||||
let line = hello_world().alignment(Alignment::Center);
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
|
||||
line.render(Rect::new(0, 0, 15, 1), &mut buf);
|
||||
let mut expected = Buffer::with_lines(vec![" Hello world! "]);
|
||||
let mut expected = Buffer::with_lines([" Hello world! "]);
|
||||
expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
|
||||
expected.set_style(Rect::new(1, 0, 6, 1), BLUE);
|
||||
expected.set_style(Rect::new(7, 0, 6, 1), GREEN);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -947,11 +1021,255 @@ mod tests {
|
||||
let line = hello_world().alignment(Alignment::Right);
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
|
||||
line.render(Rect::new(0, 0, 15, 1), &mut buf);
|
||||
let mut expected = Buffer::with_lines(vec![" Hello world!"]);
|
||||
let mut expected = Buffer::with_lines([" Hello world!"]);
|
||||
expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
|
||||
expected.set_style(Rect::new(3, 0, 6, 1), BLUE);
|
||||
expected.set_style(Rect::new(9, 0, 6, 1), GREEN);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_truncates_left() {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
|
||||
Line::from("Hello world")
|
||||
.left_aligned()
|
||||
.render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_truncates_right() {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
|
||||
Line::from("Hello world")
|
||||
.right_aligned()
|
||||
.render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["world"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_truncates_center() {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
|
||||
Line::from("Hello world")
|
||||
.centered()
|
||||
.render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["lo wo"]));
|
||||
}
|
||||
|
||||
/// Part of a regression test for <https://github.com/ratatui-org/ratatui/issues/1032> which
|
||||
/// found panics with truncating lines that contained multi-byte characters.
|
||||
#[test]
|
||||
fn regression_1032() {
|
||||
let line = Line::from(
|
||||
"🦀 RFC8628 OAuth 2.0 Device Authorization GrantでCLIからGithubのaccess tokenを取得する"
|
||||
);
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 83, 1));
|
||||
line.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines([
|
||||
"🦀 RFC8628 OAuth 2.0 Device Authorization GrantでCLIからGithubのaccess tokenを取得 "
|
||||
]));
|
||||
}
|
||||
|
||||
/// Documentary test to highlight the crab emoji width / length discrepancy
|
||||
///
|
||||
/// Part of a regression test for <https://github.com/ratatui-org/ratatui/issues/1032> which
|
||||
/// found panics with truncating lines that contained multi-byte characters.
|
||||
#[test]
|
||||
fn crab_emoji_width() {
|
||||
let crab = "🦀";
|
||||
assert_eq!(crab.len(), 4); // bytes
|
||||
assert_eq!(crab.chars().count(), 1);
|
||||
assert_eq!(crab.graphemes(true).count(), 1);
|
||||
assert_eq!(crab.width(), 2); // display width
|
||||
}
|
||||
|
||||
/// Part of a regression test for <https://github.com/ratatui-org/ratatui/issues/1032> which
|
||||
/// found panics with truncating lines that contained multi-byte characters.
|
||||
#[rstest]
|
||||
#[case::left_4(Alignment::Left, 4, "1234")]
|
||||
#[case::left_5(Alignment::Left, 5, "1234 ")]
|
||||
#[case::left_6(Alignment::Left, 6, "1234🦀")]
|
||||
#[case::left_7(Alignment::Left, 7, "1234🦀7")]
|
||||
#[case::right_4(Alignment::Right, 4, "7890")]
|
||||
#[case::right_5(Alignment::Right, 5, " 7890")]
|
||||
#[case::right_6(Alignment::Right, 6, "🦀7890")]
|
||||
#[case::right_7(Alignment::Right, 7, "4🦀7890")]
|
||||
fn render_truncates_emoji(
|
||||
#[case] alignment: Alignment,
|
||||
#[case] buf_width: u16,
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
let line = Line::from("1234🦀7890").alignment(alignment);
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
|
||||
line.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
/// Part of a regression test for <https://github.com/ratatui-org/ratatui/issues/1032> which
|
||||
/// found panics with truncating lines that contained multi-byte characters.
|
||||
///
|
||||
/// centering is tricky because there's an ambiguity about whether to take one more char
|
||||
/// from the left or the right when the line width is odd. This interacts with the width of
|
||||
/// the crab emoji, which is 2 characters wide by hitting the left or right side of the
|
||||
/// emoji.
|
||||
#[rstest]
|
||||
#[case::center_6_0(6, 0, "")]
|
||||
#[case::center_6_1(6, 1, " ")] // lef side of "🦀"
|
||||
#[case::center_6_2(6, 2, "🦀")]
|
||||
#[case::center_6_3(6, 3, "b🦀")]
|
||||
#[case::center_6_4(6, 4, "b🦀c")]
|
||||
#[case::center_7_0(7, 0, "")]
|
||||
#[case::center_7_1(7, 1, " ")] // right side of "🦀"
|
||||
#[case::center_7_2(7, 2, "🦀")]
|
||||
#[case::center_7_3(7, 3, "🦀c")]
|
||||
#[case::center_7_4(7, 4, "b🦀c")]
|
||||
#[case::center_8_0(8, 0, "")]
|
||||
#[case::center_8_1(8, 1, " ")] // right side of "🦀"
|
||||
#[case::center_8_2(8, 2, " c")] // right side of "🦀c"
|
||||
#[case::center_8_3(8, 3, "🦀c")]
|
||||
#[case::center_8_4(8, 4, "🦀cd")]
|
||||
#[case::center_8_5(8, 5, "b🦀cd")]
|
||||
#[case::center_9_0(9, 0, "")]
|
||||
#[case::center_9_1(9, 1, "c")]
|
||||
#[case::center_9_2(9, 2, " c")] // right side of "🦀c"
|
||||
#[case::center_9_3(9, 3, " cd")]
|
||||
#[case::center_9_4(9, 4, "🦀cd")]
|
||||
#[case::center_9_5(9, 5, "🦀cde")]
|
||||
#[case::center_9_6(9, 6, "b🦀cde")]
|
||||
fn render_truncates_emoji_center(
|
||||
#[case] line_width: u16,
|
||||
#[case] buf_width: u16,
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
// because the crab emoji is 2 characters wide, it will can cause the centering tests
|
||||
// intersect with either the left or right part of the emoji, which causes the emoji to
|
||||
// be not rendered. Checking for four different widths of the line is enough to cover
|
||||
// all the possible cases.
|
||||
let value = match line_width {
|
||||
6 => "ab🦀cd",
|
||||
7 => "ab🦀cde",
|
||||
8 => "ab🦀cdef",
|
||||
9 => "ab🦀cdefg",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let line = Line::from(value).centered();
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
|
||||
line.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
/// Ensures the rendering also works away from the 0x0 position.
|
||||
///
|
||||
/// Particularly of note is that an emoji that is truncated will not overwrite the
|
||||
/// characters that are already in the buffer. This is inentional (consider how a line
|
||||
/// that is rendered on a border should not overwrite the border with a partial emoji).
|
||||
#[rstest]
|
||||
#[case::left(Alignment::Left, "XXa🦀bcXXX")]
|
||||
#[case::center(Alignment::Center, "XX🦀bc🦀XX")]
|
||||
#[case::right(Alignment::Right, "XXXbc🦀dXX")]
|
||||
fn render_truncates_away_from_0x0(#[case] alignment: Alignment, #[case] expected: &str) {
|
||||
let line = Line::from(vec![Span::raw("a🦀b"), Span::raw("c🦀d")]).alignment(alignment);
|
||||
// Fill buffer with stuff to ensure the output is indeed padded
|
||||
let mut buf = Buffer::filled(Rect::new(0, 0, 10, 1), Cell::default().set_symbol("X"));
|
||||
let area = Rect::new(2, 0, 6, 1);
|
||||
line.render_ref(area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
/// When two spans are rendered after each other the first needs to be padded in accordance
|
||||
/// to the skipped unicode width. In this case the first crab does not fit at width 6 which
|
||||
/// takes a front white space.
|
||||
#[rstest]
|
||||
#[case::right_4(4, "c🦀d")]
|
||||
#[case::right_5(5, "bc🦀d")]
|
||||
#[case::right_6(6, "Xbc🦀d")]
|
||||
#[case::right_7(7, "🦀bc🦀d")]
|
||||
#[case::right_8(8, "a🦀bc🦀d")]
|
||||
fn render_right_aligned_multi_span(#[case] buf_width: u16, #[case] expected: &str) {
|
||||
let line = Line::from(vec![Span::raw("a🦀b"), Span::raw("c🦀d")]).right_aligned();
|
||||
let area = Rect::new(0, 0, buf_width, 1);
|
||||
// Fill buffer with stuff to ensure the output is indeed padded
|
||||
let mut buf = Buffer::filled(area, Cell::default().set_symbol("X"));
|
||||
line.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
/// Part of a regression test for <https://github.com/ratatui-org/ratatui/issues/1032> which
|
||||
/// found panics with truncating lines that contained multi-byte characters.
|
||||
///
|
||||
/// Flag emoji are actually two independent characters, so they can be truncated in the
|
||||
/// middle of the emoji. This test documents just the emoji part of the test.
|
||||
#[test]
|
||||
fn flag_emoji() {
|
||||
let str = "🇺🇸1234";
|
||||
assert_eq!(str.len(), 12); // flag is 4 bytes
|
||||
assert_eq!(str.chars().count(), 6); // flag is 2 chars
|
||||
assert_eq!(str.graphemes(true).count(), 5); // flag is 1 grapheme
|
||||
assert_eq!(str.width(), 6); // flag is 2 display width
|
||||
}
|
||||
|
||||
/// Part of a regression test for <https://github.com/ratatui-org/ratatui/issues/1032> which
|
||||
/// found panics with truncating lines that contained multi-byte characters.
|
||||
#[rstest]
|
||||
#[case::flag_1(1, " ")]
|
||||
#[case::flag_2(2, "🇺🇸")]
|
||||
#[case::flag_3(3, "🇺🇸1")]
|
||||
#[case::flag_4(4, "🇺🇸12")]
|
||||
#[case::flag_5(5, "🇺🇸123")]
|
||||
#[case::flag_6(6, "🇺🇸1234")]
|
||||
#[case::flag_7(7, "🇺🇸1234 ")]
|
||||
fn render_truncates_flag(#[case] buf_width: u16, #[case] expected: &str) {
|
||||
let line = Line::from("🇺🇸1234");
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
|
||||
line.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
// Buffer width is `u16`. A line can be longer.
|
||||
#[rstest]
|
||||
#[case::left(Alignment::Left, "This is some content with a some")]
|
||||
#[case::right(Alignment::Right, "horribly long Line over u16::MAX")]
|
||||
fn render_truncates_very_long_line_of_many_spans(
|
||||
#[case] alignment: Alignment,
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
let part = "This is some content with a somewhat long width to be repeated over and over again to create horribly long Line over u16::MAX";
|
||||
let min_width = usize::from(u16::MAX).saturating_add(1);
|
||||
|
||||
// width == len as only ASCII is used here
|
||||
let factor = min_width.div_ceil(part.len());
|
||||
|
||||
let line = Line::from(vec![Span::raw(part); factor]).alignment(alignment);
|
||||
|
||||
dbg!(line.width());
|
||||
assert!(line.width() >= min_width);
|
||||
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 32, 1));
|
||||
line.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
// Buffer width is `u16`. A single span inside a line can be longer.
|
||||
#[rstest]
|
||||
#[case::left(Alignment::Left, "This is some content with a some")]
|
||||
#[case::right(Alignment::Right, "horribly long Line over u16::MAX")]
|
||||
fn render_truncates_very_long_single_span_line(
|
||||
#[case] alignment: Alignment,
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
let part = "This is some content with a somewhat long width to be repeated over and over again to create horribly long Line over u16::MAX";
|
||||
let min_width = usize::from(u16::MAX).saturating_add(1);
|
||||
|
||||
// width == len as only ASCII is used here
|
||||
let factor = min_width.div_ceil(part.len());
|
||||
|
||||
let line = Line::from(vec![Span::raw(part.repeat(factor))]).alignment(alignment);
|
||||
|
||||
dbg!(line.width());
|
||||
assert!(line.width() >= min_width);
|
||||
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 32, 1));
|
||||
line.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines([expected]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::{self, Debug, Display},
|
||||
};
|
||||
use std::{borrow::Cow, fmt};
|
||||
|
||||
use super::Text;
|
||||
|
||||
@@ -19,7 +16,7 @@ use super::Text;
|
||||
/// let password = Masked::new("12345", 'x');
|
||||
///
|
||||
/// Paragraph::new(password).render(buffer.area, &mut buffer);
|
||||
/// assert_eq!(buffer, Buffer::with_lines(vec!["xxxxx"]));
|
||||
/// assert_eq!(buffer, Buffer::with_lines(["xxxxx"]));
|
||||
/// ```
|
||||
#[derive(Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Masked<'a> {
|
||||
@@ -46,17 +43,18 @@ impl<'a> Masked<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Masked<'_> {
|
||||
impl fmt::Debug for Masked<'_> {
|
||||
/// Debug representation of a masked string is the underlying string
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.inner, f)
|
||||
// note that calling display instead of Debug here is intentional
|
||||
fmt::Display::fmt(&self.inner, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Masked<'_> {
|
||||
impl fmt::Display for Masked<'_> {
|
||||
/// Display representation of a masked string is the masked string
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.value(), f)
|
||||
fmt::Display::fmt(&self.value(), f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,12 +110,14 @@ mod tests {
|
||||
fn debug() {
|
||||
let masked = Masked::new("12345", 'x');
|
||||
assert_eq!(format!("{masked:?}"), "12345");
|
||||
assert_eq!(format!("{masked:.3?}"), "123", "Debug truncates");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display() {
|
||||
let masked = Masked::new("12345", 'x');
|
||||
assert_eq!(format!("{masked}"), "xxxxx");
|
||||
assert_eq!(format!("{masked:.3}"), "xxx", "Display truncates");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
use std::{borrow::Cow, fmt};
|
||||
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
@@ -395,9 +395,9 @@ impl WidgetRef for Span<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Span<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.content)
|
||||
impl fmt::Display for Span<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.content, f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,15 +529,15 @@ mod tests {
|
||||
#[test]
|
||||
fn display_span() {
|
||||
let span = Span::raw("test content");
|
||||
|
||||
assert_eq!(format!("{span}"), "test content");
|
||||
assert_eq!(format!("{span:.4}"), "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_styled_span() {
|
||||
let stylized_span = Span::styled("stylized test content", Style::new().green());
|
||||
|
||||
assert_eq!(format!("{stylized_span}"), "stylized test content");
|
||||
assert_eq!(format!("{stylized_span:.8}"), "stylized");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -565,7 +565,6 @@ mod tests {
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
use crate::assert_buffer_eq;
|
||||
|
||||
#[test]
|
||||
fn render() {
|
||||
@@ -573,12 +572,11 @@ mod tests {
|
||||
let span = Span::styled("test content", style);
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
|
||||
span.render(buf.area, &mut buf);
|
||||
|
||||
let expected = Buffer::with_lines(vec![Line::from(vec![
|
||||
let expected = Buffer::with_lines([Line::from(vec![
|
||||
"test content".green().on_yellow(),
|
||||
" ".into(),
|
||||
])]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -598,10 +596,11 @@ mod tests {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 10, 1));
|
||||
span.render(Rect::new(0, 0, 5, 1), &mut buf);
|
||||
|
||||
let mut expected = Buffer::with_lines(vec![Line::from("test ")]);
|
||||
expected.set_style(Rect::new(0, 0, 5, 1), (Color::Green, Color::Yellow));
|
||||
|
||||
assert_buffer_eq!(buf, expected);
|
||||
let expected = Buffer::with_lines([Line::from(vec![
|
||||
"test ".green().on_yellow(),
|
||||
" ".into(),
|
||||
])]);
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
|
||||
/// When there is already a style set on the buffer, the style of the span should be
|
||||
@@ -613,12 +612,11 @@ mod tests {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
|
||||
buf.set_style(buf.area, Style::new().italic());
|
||||
span.render(buf.area, &mut buf);
|
||||
|
||||
let expected = Buffer::with_lines(vec![Line::from(vec![
|
||||
let expected = Buffer::with_lines([Line::from(vec![
|
||||
"test content".green().on_yellow().italic(),
|
||||
" ".italic(),
|
||||
])]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
|
||||
/// When the span contains a multi-width grapheme, the grapheme will ensure that the cells
|
||||
@@ -629,12 +627,11 @@ mod tests {
|
||||
let span = Span::styled("test 😃 content", style);
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
|
||||
span.render(buf.area, &mut buf);
|
||||
|
||||
// The existing code in buffer.set_line() handles multi-width graphemes by clearing the
|
||||
// cells of the hidden characters. This test ensures that the existing behavior is
|
||||
// preserved.
|
||||
let expected = Buffer::with_lines(vec!["test 😃 content".green().on_yellow()]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
let expected = Buffer::with_lines(["test 😃 content".green().on_yellow()]);
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
|
||||
/// When the span contains a multi-width grapheme that does not fit in the area passed to
|
||||
@@ -647,11 +644,9 @@ mod tests {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 6, 1));
|
||||
span.render(buf.area, &mut buf);
|
||||
|
||||
let expected = Buffer::with_lines(vec![Line::from(vec![
|
||||
"test ".green().on_yellow(),
|
||||
" ".into(),
|
||||
])]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
let expected =
|
||||
Buffer::with_lines([Line::from(vec!["test ".green().on_yellow(), " ".into()])]);
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
|
||||
/// When the area passed to render overflows the buffer, the content should be truncated
|
||||
@@ -663,11 +658,11 @@ mod tests {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
|
||||
span.render(Rect::new(10, 0, 20, 1), &mut buf);
|
||||
|
||||
let expected = Buffer::with_lines(vec![Line::from(vec![
|
||||
let expected = Buffer::with_lines([Line::from(vec![
|
||||
" ".into(),
|
||||
"test ".green().on_yellow(),
|
||||
])]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#![warn(missing_docs)]
|
||||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, fmt};
|
||||
|
||||
use itertools::{Itertools, Position};
|
||||
|
||||
@@ -580,8 +580,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Text<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl fmt::Display for Text<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (position, line) in self.iter().with_position() {
|
||||
if position == Position::Last {
|
||||
write!(f, "{line}")?;
|
||||
@@ -962,19 +962,14 @@ mod tests {
|
||||
|
||||
mod widget {
|
||||
use super::*;
|
||||
use crate::assert_buffer_eq;
|
||||
|
||||
#[test]
|
||||
fn render() {
|
||||
let text = Text::from("foo");
|
||||
|
||||
let area = Rect::new(0, 0, 5, 1);
|
||||
let mut buf = Buffer::empty(area);
|
||||
text.render(area, &mut buf);
|
||||
|
||||
let expected_buf = Buffer::with_lines(vec!["foo "]);
|
||||
|
||||
assert_buffer_eq!(buf, expected_buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["foo "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -987,40 +982,28 @@ mod tests {
|
||||
#[test]
|
||||
fn render_right_aligned() {
|
||||
let text = Text::from("foo").alignment(Alignment::Right);
|
||||
|
||||
let area = Rect::new(0, 0, 5, 1);
|
||||
let mut buf = Buffer::empty(area);
|
||||
text.render(area, &mut buf);
|
||||
|
||||
let expected_buf = Buffer::with_lines(vec![" foo"]);
|
||||
|
||||
assert_buffer_eq!(buf, expected_buf);
|
||||
assert_eq!(buf, Buffer::with_lines([" foo"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_centered_odd() {
|
||||
let text = Text::from("foo").alignment(Alignment::Center);
|
||||
|
||||
let area = Rect::new(0, 0, 5, 1);
|
||||
let mut buf = Buffer::empty(area);
|
||||
text.render(area, &mut buf);
|
||||
|
||||
let expected_buf = Buffer::with_lines(vec![" foo "]);
|
||||
|
||||
assert_buffer_eq!(buf, expected_buf);
|
||||
assert_eq!(buf, Buffer::with_lines([" foo "]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_centered_even() {
|
||||
let text = Text::from("foo").alignment(Alignment::Center);
|
||||
|
||||
let area = Rect::new(0, 0, 6, 1);
|
||||
let mut buf = Buffer::empty(area);
|
||||
text.render(area, &mut buf);
|
||||
|
||||
let expected_buf = Buffer::with_lines(vec![" foo "]);
|
||||
|
||||
assert_buffer_eq!(buf, expected_buf);
|
||||
assert_eq!(buf, Buffer::with_lines([" foo "]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1030,14 +1013,10 @@ mod tests {
|
||||
Line::from("bar").alignment(Alignment::Center),
|
||||
])
|
||||
.alignment(Alignment::Right);
|
||||
|
||||
let area = Rect::new(0, 0, 5, 2);
|
||||
let mut buf = Buffer::empty(area);
|
||||
text.render(area, &mut buf);
|
||||
|
||||
let expected_buf = Buffer::with_lines(vec![" foo", " bar "]);
|
||||
|
||||
assert_buffer_eq!(buf, expected_buf);
|
||||
assert_eq!(buf, Buffer::with_lines([" foo", " bar "]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1046,10 +1025,9 @@ mod tests {
|
||||
let mut buf = Buffer::empty(area);
|
||||
Text::from("foo".on_blue()).render(area, &mut buf);
|
||||
|
||||
let mut expected = Buffer::with_lines(vec!["foo "]);
|
||||
let mut expected = Buffer::with_lines(["foo "]);
|
||||
expected.set_style(Rect::new(0, 0, 3, 1), Style::new().bg(Color::Blue));
|
||||
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1057,10 +1035,9 @@ mod tests {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 6, 1));
|
||||
Text::from("foobar".on_blue()).render(Rect::new(0, 0, 3, 1), &mut buf);
|
||||
|
||||
let mut expected = Buffer::with_lines(vec!["foo "]);
|
||||
let mut expected = Buffer::with_lines(["foo "]);
|
||||
expected.set_style(Rect::new(0, 0, 3, 1), Style::new().bg(Color::Blue));
|
||||
|
||||
assert_buffer_eq!(buf, expected);
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -653,6 +653,6 @@ mod tests {
|
||||
#[rstest]
|
||||
fn string_option_render_ref(mut buf: Buffer) {
|
||||
Some(String::from("hello world")).render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]),);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ pub use bar_group::BarGroup;
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
///
|
||||
/// BarChart::default()
|
||||
/// .block(Block::default().title("BarChart").borders(Borders::ALL))
|
||||
/// .block(Block::bordered().title("BarChart"))
|
||||
/// .bar_width(3)
|
||||
/// .bar_gap(1)
|
||||
/// .group_gap(3)
|
||||
@@ -615,151 +615,140 @@ mod tests {
|
||||
use itertools::iproduct;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
assert_buffer_eq,
|
||||
widgets::{BorderType, Borders},
|
||||
};
|
||||
use crate::widgets::BorderType;
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
let widget = BarChart::default();
|
||||
widget.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" "; 3]));
|
||||
assert_eq!(buffer, Buffer::with_lines([" "; 3]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
let widget = BarChart::default().data(&[("foo", 1), ("bar", 2)]);
|
||||
widget.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" █ ",
|
||||
"1 2 ",
|
||||
"f b ",
|
||||
])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
" █ ",
|
||||
"1 2 ",
|
||||
"f b ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 5));
|
||||
let block = Block::default()
|
||||
.title("Block")
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 5));
|
||||
let block = Block::bordered()
|
||||
.border_type(BorderType::Double)
|
||||
.borders(Borders::ALL);
|
||||
.title("Block");
|
||||
let widget = BarChart::default()
|
||||
.data(&[("foo", 1), ("bar", 2)])
|
||||
.block(block);
|
||||
widget.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
"╔Block════════╗",
|
||||
"║ █ ║",
|
||||
"║1 2 ║",
|
||||
"║f b ║",
|
||||
"╚═════════════╝",
|
||||
])
|
||||
);
|
||||
let expected = Buffer::with_lines([
|
||||
"╔Block═══╗",
|
||||
"║ █ ║",
|
||||
"║1 2 ║",
|
||||
"║f b ║",
|
||||
"╚════════╝",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
let without_max = BarChart::default().data(&[("foo", 1), ("bar", 2), ("baz", 100)]);
|
||||
without_max.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" █ ",
|
||||
" █ ",
|
||||
"f b b ",
|
||||
])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
" █ ",
|
||||
" █ ",
|
||||
"f b b ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
let with_max = BarChart::default()
|
||||
.data(&[("foo", 1), ("bar", 2), ("baz", 100)])
|
||||
.max(2);
|
||||
with_max.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" █ █ ",
|
||||
"1 2 █ ",
|
||||
"f b b ",
|
||||
])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
" █ █ ",
|
||||
"1 2 █ ",
|
||||
"f b b ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bar_style() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
let widget = BarChart::default()
|
||||
.data(&[("foo", 1), ("bar", 2)])
|
||||
.bar_style(Style::new().red());
|
||||
widget.render(buffer.area, &mut buffer);
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
" █ ",
|
||||
"1 2 ",
|
||||
"f b ",
|
||||
#[rustfmt::skip]
|
||||
let mut expected = Buffer::with_lines([
|
||||
" █ ",
|
||||
"1 2 ",
|
||||
"f b ",
|
||||
]);
|
||||
for (x, y) in iproduct!([0, 2], [0, 1]) {
|
||||
expected.get_mut(x, y).set_fg(Color::Red);
|
||||
}
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bar_width() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
let widget = BarChart::default()
|
||||
.data(&[("foo", 1), ("bar", 2)])
|
||||
.bar_width(3);
|
||||
widget.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ███ ",
|
||||
"█1█ █2█ ",
|
||||
"foo bar ",
|
||||
])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
" ███ ",
|
||||
"█1█ █2█ ",
|
||||
"foo bar ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bar_gap() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
let widget = BarChart::default()
|
||||
.data(&[("foo", 1), ("bar", 2)])
|
||||
.bar_gap(2);
|
||||
widget.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" █ ",
|
||||
"1 2 ",
|
||||
"f b ",
|
||||
])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
" █ ",
|
||||
"1 2 ",
|
||||
"f b ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bar_set() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
let widget = BarChart::default()
|
||||
.data(&[("foo", 0), ("bar", 1), ("baz", 3)])
|
||||
.bar_set(symbols::bar::THREE_LEVELS);
|
||||
widget.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" █ ",
|
||||
" ▄ 3 ",
|
||||
"f b b ",
|
||||
])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
" █ ",
|
||||
" ▄ 3 ",
|
||||
"f b b ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -779,67 +768,68 @@ mod tests {
|
||||
])
|
||||
.bar_set(symbols::bar::NINE_LEVELS);
|
||||
widget.render(Rect::new(0, 1, 18, 2), &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ",
|
||||
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8 ",
|
||||
"a b c d e f g h i ",
|
||||
])
|
||||
);
|
||||
let expected = Buffer::with_lines([
|
||||
" ",
|
||||
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8 ",
|
||||
"a b c d e f g h i ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_style() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
let widget = BarChart::default()
|
||||
.data(&[("foo", 1), ("bar", 2)])
|
||||
.bar_width(3)
|
||||
.value_style(Style::new().red());
|
||||
widget.render(buffer.area, &mut buffer);
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
" ███ ",
|
||||
"█1█ █2█ ",
|
||||
"foo bar ",
|
||||
#[rustfmt::skip]
|
||||
let mut expected = Buffer::with_lines([
|
||||
" ███ ",
|
||||
"█1█ █2█ ",
|
||||
"foo bar ",
|
||||
]);
|
||||
expected.get_mut(1, 1).set_fg(Color::Red);
|
||||
expected.get_mut(5, 1).set_fg(Color::Red);
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn label_style() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
let widget = BarChart::default()
|
||||
.data(&[("foo", 1), ("bar", 2)])
|
||||
.label_style(Style::new().red());
|
||||
widget.render(buffer.area, &mut buffer);
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
" █ ",
|
||||
"1 2 ",
|
||||
"f b ",
|
||||
#[rustfmt::skip]
|
||||
let mut expected = Buffer::with_lines([
|
||||
" █ ",
|
||||
"1 2 ",
|
||||
"f b ",
|
||||
]);
|
||||
expected.get_mut(0, 2).set_fg(Color::Red);
|
||||
expected.get_mut(2, 2).set_fg(Color::Red);
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
let widget = BarChart::default()
|
||||
.data(&[("foo", 1), ("bar", 2)])
|
||||
.style(Style::new().red());
|
||||
widget.render(buffer.area, &mut buffer);
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
" █ ",
|
||||
"1 2 ",
|
||||
"f b ",
|
||||
#[rustfmt::skip]
|
||||
let mut expected = Buffer::with_lines([
|
||||
" █ ",
|
||||
"1 2 ",
|
||||
"f b ",
|
||||
]);
|
||||
for (x, y) in iproduct!(0..15, 0..3) {
|
||||
for (x, y) in iproduct!(0..10, 0..3) {
|
||||
expected.get_mut(x, y).set_fg(Color::Red);
|
||||
}
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -865,8 +855,13 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 3, 3));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
let expected = Buffer::with_lines(vec![" █", "1 2", "G "]);
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
" █",
|
||||
"1 2",
|
||||
"G ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
fn build_test_barchart<'a>() -> BarChart<'a> {
|
||||
@@ -892,7 +887,7 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 8));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
"2█ ",
|
||||
"3██ ",
|
||||
"4███ ",
|
||||
@@ -902,8 +897,7 @@ mod tests {
|
||||
"5████",
|
||||
"G2 ",
|
||||
]);
|
||||
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -912,7 +906,7 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 7));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
"2█ ",
|
||||
"3██ ",
|
||||
"4███ ",
|
||||
@@ -921,8 +915,7 @@ mod tests {
|
||||
"4███ ",
|
||||
"5████",
|
||||
]);
|
||||
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -931,9 +924,15 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 5));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
let expected = Buffer::with_lines(vec!["2█ ", "3██ ", "4███ ", "G1 ", "3██ "]);
|
||||
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
"2█ ",
|
||||
"3██ ",
|
||||
"4███ ",
|
||||
"G1 ",
|
||||
"3██ ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
fn test_horizontal_bars_label_width_greater_than_bar(bar_color: Option<Color>) {
|
||||
@@ -956,7 +955,7 @@ mod tests {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 2));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
|
||||
let mut expected = Buffer::with_lines(vec!["label", "5████"]);
|
||||
let mut expected = Buffer::with_lines(["label", "5████"]);
|
||||
|
||||
// first line has a yellow foreground. first cell contains italic "5"
|
||||
expected.get_mut(0, 1).modifier.insert(Modifier::ITALIC);
|
||||
@@ -981,7 +980,7 @@ mod tests {
|
||||
expected.get_mut(3, 0).set_fg(expected_color);
|
||||
expected.get_mut(4, 0).set_fg(expected_color);
|
||||
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1004,9 +1003,13 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
let expected = Buffer::with_lines(vec!["Jan 10█ ", "Feb 20████", "Mar 5 "]);
|
||||
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
"Jan 10█ ",
|
||||
"Feb 20████",
|
||||
"Mar 5 ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1027,13 +1030,13 @@ mod tests {
|
||||
// G1 should have the bold red style
|
||||
// bold: because of BarChart::label_style
|
||||
// red: is included with the label itself
|
||||
let mut expected = Buffer::with_lines(vec!["2████", "G1 "]);
|
||||
let mut expected = Buffer::with_lines(["2████", "G1 "]);
|
||||
let cell = expected.get_mut(0, 1).set_fg(Color::Red);
|
||||
cell.modifier.insert(Modifier::BOLD);
|
||||
let cell = expected.get_mut(1, 1).set_fg(Color::Red);
|
||||
cell.modifier.insert(Modifier::BOLD);
|
||||
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1050,17 +1053,14 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 13, 5));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ▂ █ ▂",
|
||||
" ▄ █ █ ▄ █",
|
||||
"▆ 2 3 4 ▆ 2 3",
|
||||
"a b c c a b c",
|
||||
" G1 G2 ",
|
||||
])
|
||||
);
|
||||
let expected = Buffer::with_lines([
|
||||
" ▂ █ ▂",
|
||||
" ▄ █ █ ▄ █",
|
||||
"▆ 2 3 4 ▆ 2 3",
|
||||
"a b c c a b c",
|
||||
" G1 G2 ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1073,9 +1073,13 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 3, 3));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
|
||||
let expected = Buffer::with_lines(vec![" █", "▆ 5", " G"]);
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
" █",
|
||||
"▆ 5",
|
||||
" G",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1098,15 +1102,14 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 5));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
" ▆▆▆ ███",
|
||||
" ███ ███",
|
||||
"▃▃▃ ███ ███",
|
||||
"写█ 写█ 写█",
|
||||
"B1 B2 B2 ",
|
||||
]);
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1118,7 +1121,7 @@ mod tests {
|
||||
.bar_gap(0);
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 0, 10));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(buffer, Buffer::empty(Rect::new(0, 0, 0, 10)));
|
||||
assert_eq!(buffer, Buffer::empty(Rect::new(0, 0, 0, 10)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1143,8 +1146,7 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 1));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8"]));
|
||||
assert_eq!(buffer, Buffer::with_lines([" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1169,15 +1171,12 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 3));
|
||||
chart.render(Rect::new(0, 1, buffer.area.width, 2), &mut buffer);
|
||||
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ",
|
||||
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
|
||||
"a b c d e f g h i",
|
||||
])
|
||||
);
|
||||
let expected = Buffer::with_lines([
|
||||
" ",
|
||||
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
|
||||
"a b c d e f g h i",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1202,15 +1201,12 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 3));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
|
||||
"a b c d e f g h i",
|
||||
" Group ",
|
||||
])
|
||||
);
|
||||
let expected = Buffer::with_lines([
|
||||
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
|
||||
"a b c d e f g h i",
|
||||
" Group ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1235,15 +1231,12 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 26, 3));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" 1▁ 2▂ 3▃ 4▄ 5▅ 6▆ 7▇ 8█",
|
||||
"a b c d e f g h i ",
|
||||
" Group ",
|
||||
])
|
||||
);
|
||||
let expected = Buffer::with_lines([
|
||||
" 1▁ 2▂ 3▃ 4▄ 5▅ 6▆ 7▇ 8█",
|
||||
"a b c d e f g h i ",
|
||||
" Group ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1268,16 +1261,13 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 4));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ▂ ▄ ▆ █",
|
||||
" ▂ ▄ ▆ 4 5 6 7 8",
|
||||
"a b c d e f g h i",
|
||||
" Group ",
|
||||
])
|
||||
);
|
||||
let expected = Buffer::with_lines([
|
||||
" ▂ ▄ ▆ █",
|
||||
" ▂ ▄ ▆ 4 5 6 7 8",
|
||||
"a b c d e f g h i",
|
||||
" Group ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1300,15 +1290,12 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 3));
|
||||
chart.render(Rect::new(0, 1, buffer.area.width, 2), &mut buffer);
|
||||
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ",
|
||||
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
|
||||
" Group ",
|
||||
])
|
||||
);
|
||||
let expected = Buffer::with_lines([
|
||||
" ",
|
||||
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
|
||||
" Group ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1319,13 +1306,9 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 59, 1));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ▁ ▁ ▁ ▁ ▂ ▂ ▂ ▃ ▃ ▃ ▃ ▄ ▄ ▄ ▄ ▅ ▅ ▅ ▆ ▆ ▆ ▆ ▇ ▇ ▇ █",
|
||||
])
|
||||
);
|
||||
let expected =
|
||||
Buffer::with_lines([" ▁ ▁ ▁ ▁ ▂ ▂ ▂ ▃ ▃ ▃ ▃ ▄ ▄ ▄ ▄ ▅ ▅ ▅ ▆ ▆ ▆ ▆ ▇ ▇ ▇ █"]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1337,17 +1320,14 @@ mod tests {
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 7, 6));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ██ ",
|
||||
" ██ ",
|
||||
"▄▄ ██ ",
|
||||
"██ ██ ",
|
||||
"1█ 2█ ",
|
||||
"a b ",
|
||||
])
|
||||
);
|
||||
let expected = Buffer::with_lines([
|
||||
" ██ ",
|
||||
" ██ ",
|
||||
"▄▄ ██ ",
|
||||
"██ ██ ",
|
||||
"1█ 2█ ",
|
||||
"a b ",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,12 +41,12 @@ pub use title::{Position, Title};
|
||||
/// ```
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
///
|
||||
/// Block::default()
|
||||
/// .title("Block")
|
||||
/// Block::new()
|
||||
/// .border_type(BorderType::Rounded)
|
||||
/// .borders(Borders::LEFT | Borders::RIGHT)
|
||||
/// .border_style(Style::default().fg(Color::White))
|
||||
/// .border_type(BorderType::Rounded)
|
||||
/// .style(Style::default().bg(Color::Black));
|
||||
/// .style(Style::default().bg(Color::Black))
|
||||
/// .title("Block");
|
||||
/// ```
|
||||
///
|
||||
/// You may also use multiple titles like in the following:
|
||||
@@ -56,7 +56,7 @@ pub use title::{Position, Title};
|
||||
/// widgets::{block::*, *},
|
||||
/// };
|
||||
///
|
||||
/// Block::default()
|
||||
/// Block::new()
|
||||
/// .title("Title 1")
|
||||
/// .title(Title::from("Title 2").position(Position::Bottom));
|
||||
/// ```
|
||||
@@ -173,6 +173,11 @@ impl<'a> Block<'a> {
|
||||
}
|
||||
|
||||
/// Create a new block with [all borders](Borders::ALL) shown
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::widgets::{Block, Borders};
|
||||
/// assert_eq!(Block::bordered(), Block::new().borders(Borders::ALL));
|
||||
/// ```
|
||||
pub const fn bordered() -> Self {
|
||||
let mut block = Self::new();
|
||||
block.borders = Borders::ALL;
|
||||
@@ -219,7 +224,7 @@ impl<'a> Block<'a> {
|
||||
/// widgets::{block::*, *},
|
||||
/// };
|
||||
///
|
||||
/// Block::default()
|
||||
/// Block::new()
|
||||
/// .title("Title") // By default in the top left corner
|
||||
/// .title(Title::from("Left").alignment(Alignment::Left)) // also on the left
|
||||
/// .title(Title::from("Right").alignment(Alignment::Right))
|
||||
@@ -325,12 +330,12 @@ impl<'a> Block<'a> {
|
||||
/// widgets::{block::*, *},
|
||||
/// };
|
||||
///
|
||||
/// Block::default()
|
||||
/// Block::new()
|
||||
/// .title_alignment(Alignment::Center)
|
||||
/// // This title won't be aligned in the center
|
||||
/// .title(Title::from("right").alignment(Alignment::Right))
|
||||
/// .title("foo")
|
||||
/// .title("bar")
|
||||
/// .title_alignment(Alignment::Center);
|
||||
/// .title("bar");
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn title_alignment(mut self, alignment: Alignment) -> Self {
|
||||
@@ -352,12 +357,12 @@ impl<'a> Block<'a> {
|
||||
/// widgets::{block::*, *},
|
||||
/// };
|
||||
///
|
||||
/// Block::default()
|
||||
/// Block::new()
|
||||
/// .title_position(Position::Bottom)
|
||||
/// // This title won't be aligned in the center
|
||||
/// .title(Title::from("top").position(Position::Top))
|
||||
/// .title("foo")
|
||||
/// .title("bar")
|
||||
/// .title_position(Position::Bottom);
|
||||
/// .title("bar");
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn title_position(mut self, position: Position) -> Self {
|
||||
@@ -377,9 +382,7 @@ impl<'a> Block<'a> {
|
||||
/// This example shows a `Block` with blue borders.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// Block::default()
|
||||
/// .borders(Borders::ALL)
|
||||
/// .border_style(Style::new().blue());
|
||||
/// Block::bordered().border_style(Style::new().blue());
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn border_style<S: Into<Style>>(mut self, style: S) -> Self {
|
||||
@@ -409,17 +412,13 @@ impl<'a> Block<'a> {
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Simply show all borders.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// Block::default().borders(Borders::ALL);
|
||||
/// ```
|
||||
///
|
||||
/// Display left and right borders.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// Block::default().borders(Borders::LEFT | Borders::RIGHT);
|
||||
/// Block::new().borders(Borders::LEFT | Borders::RIGHT);
|
||||
/// ```
|
||||
///
|
||||
/// To show all borders you can abbreviate this with [`Block::bordered`]
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn borders(mut self, flag: Borders) -> Self {
|
||||
self.borders = flag;
|
||||
@@ -437,10 +436,9 @@ impl<'a> Block<'a> {
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// Block::default()
|
||||
/// .title("Block")
|
||||
/// .borders(Borders::ALL)
|
||||
/// .border_type(BorderType::Rounded);
|
||||
/// Block::bordered()
|
||||
/// .border_type(BorderType::Rounded)
|
||||
/// .title("Block");
|
||||
/// // Renders
|
||||
/// // ╭Block╮
|
||||
/// // │ │
|
||||
@@ -460,7 +458,7 @@ impl<'a> Block<'a> {
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// Block::default().title("Block").borders(Borders::ALL).border_set(symbols::border::DOUBLE);
|
||||
/// Block::bordered().border_set(symbols::border::DOUBLE).title("Block");
|
||||
/// // Renders
|
||||
/// // ╔Block╗
|
||||
/// // ║ ║
|
||||
@@ -479,8 +477,8 @@ impl<'a> Block<'a> {
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # fn render_nested_block(frame: &mut Frame) {
|
||||
/// let outer_block = Block::default().title("Outer").borders(Borders::ALL);
|
||||
/// let inner_block = Block::default().title("Inner").borders(Borders::ALL);
|
||||
/// let outer_block = Block::bordered().title("Outer");
|
||||
/// let inner_block = Block::bordered().title("Inner");
|
||||
///
|
||||
/// let outer_area = frame.size();
|
||||
/// let inner_area = outer_block.inner(outer_area);
|
||||
@@ -501,14 +499,14 @@ impl<'a> Block<'a> {
|
||||
inner.x = inner.x.saturating_add(1).min(inner.right());
|
||||
inner.width = inner.width.saturating_sub(1);
|
||||
}
|
||||
if self.borders.intersects(Borders::TOP) || self.have_title_at_position(Position::Top) {
|
||||
if self.borders.intersects(Borders::TOP) || self.has_title_at_position(Position::Top) {
|
||||
inner.y = inner.y.saturating_add(1).min(inner.bottom());
|
||||
inner.height = inner.height.saturating_sub(1);
|
||||
}
|
||||
if self.borders.intersects(Borders::RIGHT) {
|
||||
inner.width = inner.width.saturating_sub(1);
|
||||
}
|
||||
if self.borders.intersects(Borders::BOTTOM) || self.have_title_at_position(Position::Bottom)
|
||||
if self.borders.intersects(Borders::BOTTOM) || self.has_title_at_position(Position::Bottom)
|
||||
{
|
||||
inner.height = inner.height.saturating_sub(1);
|
||||
}
|
||||
@@ -526,7 +524,7 @@ impl<'a> Block<'a> {
|
||||
inner
|
||||
}
|
||||
|
||||
fn have_title_at_position(&self, position: Position) -> bool {
|
||||
fn has_title_at_position(&self, position: Position) -> bool {
|
||||
self.titles
|
||||
.iter()
|
||||
.any(|title| title.position.unwrap_or(self.titles_position) == position)
|
||||
@@ -541,9 +539,7 @@ impl<'a> Block<'a> {
|
||||
/// This renders a `Block` with no padding (the default).
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// Block::default()
|
||||
/// .borders(Borders::ALL)
|
||||
/// .padding(Padding::zero());
|
||||
/// Block::bordered().padding(Padding::zero());
|
||||
/// // Renders
|
||||
/// // ┌───────┐
|
||||
/// // │content│
|
||||
@@ -554,9 +550,7 @@ impl<'a> Block<'a> {
|
||||
/// Notice the two spaces before and after the content.
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// Block::default()
|
||||
/// .borders(Borders::ALL)
|
||||
/// .padding(Padding::horizontal(2));
|
||||
/// Block::bordered().padding(Padding::horizontal(2));
|
||||
/// // Renders
|
||||
/// // ┌───────────┐
|
||||
/// // │ content │
|
||||
@@ -866,10 +860,10 @@ impl<'a> Styled for Block<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rstest::rstest;
|
||||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
use crate::assert_buffer_eq;
|
||||
|
||||
#[test]
|
||||
fn create_with_all_borders() {
|
||||
@@ -877,234 +871,112 @@ mod tests {
|
||||
assert_eq!(block.borders, Borders::all());
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[test]
|
||||
fn inner_takes_into_account_the_borders() {
|
||||
// No borders
|
||||
assert_eq!(
|
||||
Block::default().inner(Rect::default()),
|
||||
Rect::new(0, 0, 0, 0),
|
||||
"no borders, width=0, height=0"
|
||||
);
|
||||
assert_eq!(
|
||||
Block::default().inner(Rect::new(0, 0, 1, 1)),
|
||||
Rect::new(0, 0, 1, 1),
|
||||
"no borders, width=1, height=1"
|
||||
);
|
||||
#[rstest]
|
||||
#[case::none_0(Borders::NONE, Rect::ZERO, Rect::ZERO)]
|
||||
#[case::none_1(Borders::NONE, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 1))]
|
||||
#[case::left_0(Borders::LEFT, Rect::ZERO, Rect::ZERO)]
|
||||
#[case::left_w1(Borders::LEFT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
|
||||
#[case::left_w2(Borders::LEFT, Rect::new(0, 0, 1, 1), Rect::new(1, 0, 0, 1))]
|
||||
#[case::left_w3(Borders::LEFT, Rect::new(0, 0, 2, 1), Rect::new(1, 0, 1, 1))]
|
||||
#[case::top_0(Borders::TOP, Rect::ZERO, Rect::ZERO)]
|
||||
#[case::top_h1(Borders::TOP, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
|
||||
#[case::top_h2(Borders::TOP, Rect::new(0, 0, 1, 1), Rect::new(0, 1, 1, 0))]
|
||||
#[case::top_h3(Borders::TOP, Rect::new(0, 0, 1, 2), Rect::new(0, 1, 1, 1))]
|
||||
#[case::right_0(Borders::RIGHT, Rect::ZERO, Rect::ZERO)]
|
||||
#[case::right_w1(Borders::RIGHT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
|
||||
#[case::right_w2(Borders::RIGHT, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 0, 1))]
|
||||
#[case::right_w3(Borders::RIGHT, Rect::new(0, 0, 2, 1), Rect::new(0, 0, 1, 1))]
|
||||
#[case::bottom_0(Borders::BOTTOM, Rect::ZERO, Rect::ZERO)]
|
||||
#[case::bottom_h1(Borders::BOTTOM, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
|
||||
#[case::bottom_h2(Borders::BOTTOM, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 0))]
|
||||
#[case::bottom_h3(Borders::BOTTOM, Rect::new(0, 0, 1, 2), Rect::new(0, 0, 1, 1))]
|
||||
#[case::all_0(Borders::ALL, Rect::ZERO, Rect::ZERO)]
|
||||
#[case::all_1(Borders::ALL, Rect::new(0, 0, 1, 1), Rect::new(1, 1, 0, 0))]
|
||||
#[case::all_2(Borders::ALL, Rect::new(0, 0, 2, 2), Rect::new(1, 1, 0, 0))]
|
||||
#[case::all_3(Borders::ALL, Rect::new(0, 0, 3, 3), Rect::new(1, 1, 1, 1))]
|
||||
fn inner_takes_into_account_the_borders(
|
||||
#[case] borders: Borders,
|
||||
#[case] area: Rect,
|
||||
#[case] expected: Rect,
|
||||
) {
|
||||
let block = Block::new().borders(borders);
|
||||
assert_eq!(block.inner(area), expected);
|
||||
}
|
||||
|
||||
// Left border
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::LEFT)
|
||||
.inner(Rect::new(0, 0, 0, 1)),
|
||||
Rect::new(0, 0, 0, 1),
|
||||
"left, width=0"
|
||||
);
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::LEFT)
|
||||
.inner(Rect::new(0, 0, 1, 1)),
|
||||
Rect::new(1, 0, 0, 1),
|
||||
"left, width=1"
|
||||
);
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::LEFT)
|
||||
.inner(Rect::new(0, 0, 2, 1)),
|
||||
Rect::new(1, 0, 1, 1),
|
||||
"left, width=2"
|
||||
);
|
||||
#[rstest]
|
||||
#[case::left(Alignment::Left)]
|
||||
#[case::center(Alignment::Center)]
|
||||
#[case::right(Alignment::Right)]
|
||||
fn inner_takes_into_account_the_title(#[case] alignment: Alignment) {
|
||||
let area = Rect::new(0, 0, 0, 1);
|
||||
let expected = Rect::new(0, 1, 0, 0);
|
||||
|
||||
// Top border
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::TOP)
|
||||
.inner(Rect::new(0, 0, 1, 0)),
|
||||
Rect::new(0, 0, 1, 0),
|
||||
"top, height=0"
|
||||
);
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::TOP)
|
||||
.inner(Rect::new(0, 0, 1, 1)),
|
||||
Rect::new(0, 1, 1, 0),
|
||||
"top, height=1"
|
||||
);
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::TOP)
|
||||
.inner(Rect::new(0, 0, 1, 2)),
|
||||
Rect::new(0, 1, 1, 1),
|
||||
"top, height=2"
|
||||
);
|
||||
let block = Block::new().title(Title::from("Test").alignment(alignment));
|
||||
assert_eq!(block.inner(area), expected);
|
||||
}
|
||||
|
||||
// Right border
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::RIGHT)
|
||||
.inner(Rect::new(0, 0, 0, 1)),
|
||||
Rect::new(0, 0, 0, 1),
|
||||
"right, width=0"
|
||||
);
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::RIGHT)
|
||||
.inner(Rect::new(0, 0, 1, 1)),
|
||||
Rect::new(0, 0, 0, 1),
|
||||
"right, width=1"
|
||||
);
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::RIGHT)
|
||||
.inner(Rect::new(0, 0, 2, 1)),
|
||||
Rect::new(0, 0, 1, 1),
|
||||
"right, width=2"
|
||||
);
|
||||
|
||||
// Bottom border
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::BOTTOM)
|
||||
.inner(Rect::new(0, 0, 1, 0)),
|
||||
Rect::new(0, 0, 1, 0),
|
||||
"bottom, height=0"
|
||||
);
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::BOTTOM)
|
||||
.inner(Rect::new(0, 0, 1, 1)),
|
||||
Rect::new(0, 0, 1, 0),
|
||||
"bottom, height=1"
|
||||
);
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::BOTTOM)
|
||||
.inner(Rect::new(0, 0, 1, 2)),
|
||||
Rect::new(0, 0, 1, 1),
|
||||
"bottom, height=2"
|
||||
);
|
||||
|
||||
// All borders
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.inner(Rect::default()),
|
||||
Rect::new(0, 0, 0, 0),
|
||||
"all borders, width=0, height=0"
|
||||
);
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.inner(Rect::new(0, 0, 1, 1)),
|
||||
Rect::new(1, 1, 0, 0),
|
||||
"all borders, width=1, height=1"
|
||||
);
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.inner(Rect::new(0, 0, 2, 2)),
|
||||
Rect::new(1, 1, 0, 0),
|
||||
"all borders, width=2, height=2"
|
||||
);
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.inner(Rect::new(0, 0, 3, 3)),
|
||||
Rect::new(1, 1, 1, 1),
|
||||
"all borders, width=3, height=3"
|
||||
);
|
||||
#[rstest]
|
||||
#[case::top_top(Borders::TOP, Position::Top, Rect::new(0, 1, 0, 1))]
|
||||
#[case::top_bot(Borders::BOTTOM, Position::Top, Rect::new(0, 1, 0, 0))]
|
||||
#[case::bot_top(Borders::TOP, Position::Bottom, Rect::new(0, 1, 0, 0))]
|
||||
#[case::top_top(Borders::BOTTOM, Position::Bottom, Rect::new(0, 0, 0, 1))]
|
||||
fn inner_takes_into_account_border_and_title(
|
||||
#[case] borders: Borders,
|
||||
#[case] position: Position,
|
||||
#[case] expected: Rect,
|
||||
) {
|
||||
let area = Rect::new(0, 0, 0, 2);
|
||||
let block = Block::new()
|
||||
.borders(borders)
|
||||
.title(Title::from("Test").position(position));
|
||||
assert_eq!(block.inner(area), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inner_takes_into_account_the_title() {
|
||||
assert_eq!(
|
||||
Block::default().title("Test").inner(Rect::new(0, 0, 0, 1)),
|
||||
Rect::new(0, 1, 0, 0),
|
||||
);
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.title(Title::from("Test").alignment(Alignment::Center))
|
||||
.inner(Rect::new(0, 0, 0, 1)),
|
||||
Rect::new(0, 1, 0, 0),
|
||||
);
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.title(Title::from("Test").alignment(Alignment::Right))
|
||||
.inner(Rect::new(0, 0, 0, 1)),
|
||||
Rect::new(0, 1, 0, 0),
|
||||
);
|
||||
}
|
||||
fn has_title_at_position_takes_into_account_all_positioning_declarations() {
|
||||
let block = Block::new();
|
||||
assert!(!block.has_title_at_position(Position::Top));
|
||||
assert!(!block.has_title_at_position(Position::Bottom));
|
||||
|
||||
#[test]
|
||||
fn inner_takes_into_account_border_and_title() {
|
||||
let test_rect = Rect::new(0, 0, 0, 2);
|
||||
let block = Block::new().title(Title::from("Test").position(Position::Top));
|
||||
assert!(block.has_title_at_position(Position::Top));
|
||||
assert!(!block.has_title_at_position(Position::Bottom));
|
||||
|
||||
let top_top = Block::default()
|
||||
.title(Title::from("Test").position(Position::Top))
|
||||
.borders(Borders::TOP);
|
||||
assert_eq!(top_top.inner(test_rect), Rect::new(0, 1, 0, 1));
|
||||
let block = Block::new().title(Title::from("Test").position(Position::Bottom));
|
||||
assert!(!block.has_title_at_position(Position::Top));
|
||||
assert!(block.has_title_at_position(Position::Bottom));
|
||||
|
||||
let top_bot = Block::default()
|
||||
.title(Title::from("Test").position(Position::Top))
|
||||
.borders(Borders::BOTTOM);
|
||||
assert_eq!(top_bot.inner(test_rect), Rect::new(0, 1, 0, 0));
|
||||
|
||||
let bot_top = Block::default()
|
||||
.title(Title::from("Test").position(Position::Bottom))
|
||||
.borders(Borders::TOP);
|
||||
assert_eq!(bot_top.inner(test_rect), Rect::new(0, 1, 0, 0));
|
||||
|
||||
let bot_bot = Block::default()
|
||||
.title(Title::from("Test").position(Position::Bottom))
|
||||
.borders(Borders::BOTTOM);
|
||||
assert_eq!(bot_bot.inner(test_rect), Rect::new(0, 0, 0, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn have_title_at_position_takes_into_account_all_positioning_declarations() {
|
||||
let block = Block::default();
|
||||
assert!(!block.have_title_at_position(Position::Top));
|
||||
assert!(!block.have_title_at_position(Position::Bottom));
|
||||
|
||||
let block = Block::default().title(Title::from("Test").position(Position::Top));
|
||||
assert!(block.have_title_at_position(Position::Top));
|
||||
assert!(!block.have_title_at_position(Position::Bottom));
|
||||
|
||||
let block = Block::default().title(Title::from("Test").position(Position::Bottom));
|
||||
assert!(!block.have_title_at_position(Position::Top));
|
||||
assert!(block.have_title_at_position(Position::Bottom));
|
||||
|
||||
let block = Block::default()
|
||||
let block = Block::new()
|
||||
.title(Title::from("Test").position(Position::Top))
|
||||
.title_position(Position::Bottom);
|
||||
assert!(block.have_title_at_position(Position::Top));
|
||||
assert!(!block.have_title_at_position(Position::Bottom));
|
||||
assert!(block.has_title_at_position(Position::Top));
|
||||
assert!(!block.has_title_at_position(Position::Bottom));
|
||||
|
||||
let block = Block::default()
|
||||
let block = Block::new()
|
||||
.title(Title::from("Test").position(Position::Bottom))
|
||||
.title_position(Position::Top);
|
||||
assert!(!block.have_title_at_position(Position::Top));
|
||||
assert!(block.have_title_at_position(Position::Bottom));
|
||||
assert!(!block.has_title_at_position(Position::Top));
|
||||
assert!(block.has_title_at_position(Position::Bottom));
|
||||
|
||||
let block = Block::default()
|
||||
let block = Block::new()
|
||||
.title(Title::from("Test").position(Position::Top))
|
||||
.title(Title::from("Test").position(Position::Bottom));
|
||||
assert!(block.have_title_at_position(Position::Top));
|
||||
assert!(block.have_title_at_position(Position::Bottom));
|
||||
assert!(block.has_title_at_position(Position::Top));
|
||||
assert!(block.has_title_at_position(Position::Bottom));
|
||||
|
||||
let block = Block::default()
|
||||
let block = Block::new()
|
||||
.title(Title::from("Test").position(Position::Top))
|
||||
.title(Title::from("Test"))
|
||||
.title_position(Position::Bottom);
|
||||
assert!(block.have_title_at_position(Position::Top));
|
||||
assert!(block.have_title_at_position(Position::Bottom));
|
||||
assert!(block.has_title_at_position(Position::Top));
|
||||
assert!(block.has_title_at_position(Position::Bottom));
|
||||
|
||||
let block = Block::default()
|
||||
let block = Block::new()
|
||||
.title(Title::from("Test"))
|
||||
.title(Title::from("Test").position(Position::Bottom))
|
||||
.title_position(Position::Top);
|
||||
assert!(block.have_title_at_position(Position::Top));
|
||||
assert!(block.have_title_at_position(Position::Bottom));
|
||||
assert!(block.has_title_at_position(Position::Top));
|
||||
assert!(block.has_title_at_position(Position::Bottom));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1134,50 +1006,49 @@ mod tests {
|
||||
const fn block_can_be_const() {
|
||||
const _DEFAULT_STYLE: Style = Style::new();
|
||||
const _DEFAULT_PADDING: Padding = Padding::uniform(1);
|
||||
const _DEFAULT_BLOCK: Block = Block::new()
|
||||
const _DEFAULT_BLOCK: Block = Block::bordered()
|
||||
// the following methods are no longer const because they use Into<Style>
|
||||
// .style(_DEFAULT_STYLE) // no longer const
|
||||
// .border_style(_DEFAULT_STYLE) // no longer const
|
||||
// .title_style(_DEFAULT_STYLE) // no longer const
|
||||
.title_alignment(Alignment::Left)
|
||||
.title_position(Position::Top)
|
||||
.borders(Borders::ALL)
|
||||
.padding(_DEFAULT_PADDING);
|
||||
}
|
||||
|
||||
/// This test ensures that we have some coverage on the [`Style::from()`] implementations
|
||||
/// Ensure Style from/into works the way a user would use it.
|
||||
#[test]
|
||||
fn block_style() {
|
||||
fn style_into_works_from_user_view() {
|
||||
// nominal style
|
||||
let block = Block::default().style(Style::new().red());
|
||||
let block = Block::new().style(Style::new().red());
|
||||
assert_eq!(block.style, Style::new().red());
|
||||
|
||||
// auto-convert from Color
|
||||
let block = Block::default().style(Color::Red);
|
||||
let block = Block::new().style(Color::Red);
|
||||
assert_eq!(block.style, Style::new().red());
|
||||
|
||||
// auto-convert from (Color, Color)
|
||||
let block = Block::default().style((Color::Red, Color::Blue));
|
||||
let block = Block::new().style((Color::Red, Color::Blue));
|
||||
assert_eq!(block.style, Style::new().red().on_blue());
|
||||
|
||||
// auto-convert from Modifier
|
||||
let block = Block::default().style(Modifier::BOLD | Modifier::ITALIC);
|
||||
let block = Block::new().style(Modifier::BOLD | Modifier::ITALIC);
|
||||
assert_eq!(block.style, Style::new().bold().italic());
|
||||
|
||||
// auto-convert from (Modifier, Modifier)
|
||||
let block = Block::default().style((Modifier::BOLD | Modifier::ITALIC, Modifier::DIM));
|
||||
let block = Block::new().style((Modifier::BOLD | Modifier::ITALIC, Modifier::DIM));
|
||||
assert_eq!(block.style, Style::new().bold().italic().not_dim());
|
||||
|
||||
// auto-convert from (Color, Modifier)
|
||||
let block = Block::default().style((Color::Red, Modifier::BOLD));
|
||||
let block = Block::new().style((Color::Red, Modifier::BOLD));
|
||||
assert_eq!(block.style, Style::new().red().bold());
|
||||
|
||||
// auto-convert from (Color, Color, Modifier)
|
||||
let block = Block::default().style((Color::Red, Color::Blue, Modifier::BOLD));
|
||||
let block = Block::new().style((Color::Red, Color::Blue, Modifier::BOLD));
|
||||
assert_eq!(block.style, Style::new().red().on_blue().bold());
|
||||
|
||||
// auto-convert from (Color, Color, Modifier, Modifier)
|
||||
let block = Block::default().style((
|
||||
let block = Block::new().style((
|
||||
Color::Red,
|
||||
Color::Blue,
|
||||
Modifier::BOLD | Modifier::ITALIC,
|
||||
@@ -1191,7 +1062,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn can_be_stylized() {
|
||||
let block = Block::default().black().on_white().bold().not_dim();
|
||||
let block = Block::new().black().on_white().bold().not_dim();
|
||||
assert_eq!(
|
||||
block.style,
|
||||
Style::default()
|
||||
@@ -1206,7 +1077,7 @@ mod tests {
|
||||
fn title() {
|
||||
use Alignment::*;
|
||||
use Position::*;
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 3));
|
||||
Block::bordered()
|
||||
.title(Title::from("A").position(Top).alignment(Left))
|
||||
.title(Title::from("B").position(Top).alignment(Center))
|
||||
@@ -1215,19 +1086,18 @@ mod tests {
|
||||
.title(Title::from("E").position(Bottom).alignment(Center))
|
||||
.title(Title::from("F").position(Bottom).alignment(Right))
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
"┌A─────B─────C┐",
|
||||
"│ │",
|
||||
"└D─────E─────F┘",
|
||||
])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
"┌A───B───C┐",
|
||||
"│ │",
|
||||
"└D───E───F┘",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn title_top_bottom() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 3));
|
||||
Block::bordered()
|
||||
.title_top(Line::raw("A").left_aligned())
|
||||
.title_top(Line::raw("B").centered())
|
||||
@@ -1236,14 +1106,13 @@ mod tests {
|
||||
.title_bottom(Line::raw("E").centered())
|
||||
.title_bottom(Line::raw("F").right_aligned())
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
"┌A─────B─────C┐",
|
||||
"│ │",
|
||||
"└D─────E─────F┘",
|
||||
])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
"┌A───B───C┐",
|
||||
"│ │",
|
||||
"└D───E───F┘",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1255,11 +1124,11 @@ mod tests {
|
||||
];
|
||||
for (alignment, expected) in tests {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 1));
|
||||
Block::default()
|
||||
.title("test")
|
||||
Block::new()
|
||||
.title_alignment(alignment)
|
||||
.title("test")
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec![expected]));
|
||||
assert_eq!(buffer, Buffer::with_lines([expected]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1272,11 +1141,11 @@ mod tests {
|
||||
];
|
||||
for (block_title_alignment, alignment, expected) in tests {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 1));
|
||||
Block::default()
|
||||
.title(Title::from("test").alignment(alignment))
|
||||
Block::new()
|
||||
.title_alignment(block_title_alignment)
|
||||
.title(Title::from("test").alignment(alignment))
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec![expected]));
|
||||
assert_eq!(buffer, Buffer::with_lines([expected]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1284,43 +1153,32 @@ mod tests {
|
||||
#[test]
|
||||
fn render_right_aligned_empty_title() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
Block::default()
|
||||
.title("")
|
||||
Block::new()
|
||||
.title_alignment(Alignment::Right)
|
||||
.title("")
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
])
|
||||
);
|
||||
assert_eq!(buffer, Buffer::with_lines([" "; 3]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn title_position() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 2));
|
||||
Block::default()
|
||||
.title("test")
|
||||
Block::new()
|
||||
.title_position(Position::Bottom)
|
||||
.title("test")
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" ", "test"]));
|
||||
assert_eq!(buffer, Buffer::with_lines([" ", "test"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn title_content_style() {
|
||||
for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
|
||||
Block::default()
|
||||
.title("test".yellow())
|
||||
Block::new()
|
||||
.title_alignment(alignment)
|
||||
.title("test".yellow())
|
||||
.render(buffer.area, &mut buffer);
|
||||
|
||||
let mut expected_buffer = Buffer::with_lines(vec!["test"]);
|
||||
expected_buffer.set_style(Rect::new(0, 0, 4, 1), Style::new().yellow());
|
||||
|
||||
assert_buffer_eq!(buffer, expected_buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1328,16 +1186,12 @@ mod tests {
|
||||
fn block_title_style() {
|
||||
for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
|
||||
Block::default()
|
||||
.title("test")
|
||||
.title_style(Style::new().yellow())
|
||||
Block::new()
|
||||
.title_alignment(alignment)
|
||||
.title_style(Style::new().yellow())
|
||||
.title("test")
|
||||
.render(buffer.area, &mut buffer);
|
||||
|
||||
let mut expected_buffer = Buffer::with_lines(vec!["test"]);
|
||||
expected_buffer.set_style(Rect::new(0, 0, 4, 1), Style::new().yellow());
|
||||
|
||||
assert_buffer_eq!(buffer, expected_buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1345,37 +1199,31 @@ mod tests {
|
||||
fn title_style_overrides_block_title_style() {
|
||||
for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
|
||||
Block::default()
|
||||
.title("test".yellow())
|
||||
.title_style(Style::new().green().on_red())
|
||||
Block::new()
|
||||
.title_alignment(alignment)
|
||||
.title_style(Style::new().green().on_red())
|
||||
.title("test".yellow())
|
||||
.render(buffer.area, &mut buffer);
|
||||
|
||||
let mut expected_buffer = Buffer::with_lines(vec!["test"]);
|
||||
expected_buffer.set_style(Rect::new(0, 0, 4, 1), Style::new().yellow().on_red());
|
||||
|
||||
assert_buffer_eq!(buffer, expected_buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines(["test".yellow().on_red()]));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn title_border_style() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
Block::default()
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
Block::bordered()
|
||||
.title("test")
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::new().yellow())
|
||||
.render(buffer.area, &mut buffer);
|
||||
|
||||
let mut expected_buffer = Buffer::with_lines(vec![
|
||||
"┌test─────────┐",
|
||||
"│ │",
|
||||
"└─────────────┘",
|
||||
#[rustfmt::skip]
|
||||
let mut expected = Buffer::with_lines([
|
||||
"┌test────┐",
|
||||
"│ │",
|
||||
"└────────┘",
|
||||
]);
|
||||
expected_buffer.set_style(Rect::new(0, 0, 15, 3), Style::new().yellow());
|
||||
expected_buffer.set_style(Rect::new(1, 1, 13, 1), Style::reset());
|
||||
|
||||
assert_buffer_eq!(buffer, expected_buffer);
|
||||
expected.set_style(Rect::new(0, 0, 10, 3), Style::new().yellow());
|
||||
expected.set_style(Rect::new(1, 1, 8, 1), Style::reset());
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1397,111 +1245,98 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn render_plain_border() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
Block::bordered()
|
||||
.border_type(BorderType::Plain)
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
"┌─────────────┐",
|
||||
"│ │",
|
||||
"└─────────────┘"
|
||||
])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
"┌────────┐",
|
||||
"│ │",
|
||||
"└────────┘",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_rounded_border() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
Block::bordered()
|
||||
.border_type(BorderType::Rounded)
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
"╭─────────────╮",
|
||||
"│ │",
|
||||
"╰─────────────╯"
|
||||
])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
"╭────────╮",
|
||||
"│ │",
|
||||
"╰────────╯",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_double_border() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
Block::bordered()
|
||||
.border_type(BorderType::Double)
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
"╔═════════════╗",
|
||||
"║ ║",
|
||||
"╚═════════════╝"
|
||||
])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
"╔════════╗",
|
||||
"║ ║",
|
||||
"╚════════╝",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_quadrant_inside() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
Block::bordered()
|
||||
.border_type(BorderType::QuadrantInside)
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
"▗▄▄▄▄▄▄▄▄▄▄▄▄▄▖",
|
||||
"▐ ▌",
|
||||
"▝▀▀▀▀▀▀▀▀▀▀▀▀▀▘",
|
||||
])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
"▗▄▄▄▄▄▄▄▄▖",
|
||||
"▐ ▌",
|
||||
"▝▀▀▀▀▀▀▀▀▘",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_border_quadrant_outside() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
Block::bordered()
|
||||
.border_type(BorderType::QuadrantOutside)
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
"▛▀▀▀▀▀▀▀▀▀▀▀▀▀▜",
|
||||
"▌ ▐",
|
||||
"▙▄▄▄▄▄▄▄▄▄▄▄▄▄▟",
|
||||
])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
"▛▀▀▀▀▀▀▀▀▜",
|
||||
"▌ ▐",
|
||||
"▙▄▄▄▄▄▄▄▄▟",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_solid_border() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
Block::bordered()
|
||||
.border_type(BorderType::Thick)
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
"┏━━━━━━━━━━━━━┓",
|
||||
"┃ ┃",
|
||||
"┗━━━━━━━━━━━━━┛"
|
||||
])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
"┏━━━━━━━━┓",
|
||||
"┃ ┃",
|
||||
"┗━━━━━━━━┛",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_custom_border_set() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
Block::bordered()
|
||||
.border_set(border::Set {
|
||||
top_left: "1",
|
||||
top_right: "2",
|
||||
@@ -1513,13 +1348,12 @@ mod tests {
|
||||
horizontal_bottom: "B",
|
||||
})
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_buffer_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
"1TTTTTTTTTTTTT2",
|
||||
"L R",
|
||||
"3BBBBBBBBBBBBB4",
|
||||
])
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
"1TTTTTTTT2",
|
||||
"L R",
|
||||
"3BBBBBBBB4",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::fmt::{self, Debug};
|
||||
use std::fmt;
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
@@ -24,7 +24,7 @@ bitflags! {
|
||||
/// Implement the `Debug` trait for the `Borders` bitflags. This is a manual implementation to
|
||||
/// display the flags in a more readable way. The default implementation would display the
|
||||
/// flags as 'Border(0x0)' for `Borders::NONE` for example.
|
||||
impl Debug for Borders {
|
||||
impl fmt::Debug for Borders {
|
||||
/// Display the Borders bitflags as a list of names. For example, `Borders::NONE` will be
|
||||
/// displayed as `NONE` and `Borders::ALL` will be displayed as `ALL`. If multiple flags are
|
||||
/// set, they will be displayed separated by a pipe character.
|
||||
|
||||
@@ -28,14 +28,14 @@ pub struct Monthly<'a, DS: DateStyler> {
|
||||
|
||||
impl<'a, DS: DateStyler> Monthly<'a, DS> {
|
||||
/// Construct a calendar for the `display_date` and highlight the `events`
|
||||
pub fn new(display_date: Date, events: DS) -> Self {
|
||||
pub const fn new(display_date: Date, events: DS) -> Self {
|
||||
Self {
|
||||
display_date,
|
||||
events,
|
||||
show_surrounding: None,
|
||||
show_weekday: None,
|
||||
show_month: None,
|
||||
default_style: Style::default(),
|
||||
default_style: Style::new(),
|
||||
block: None,
|
||||
}
|
||||
}
|
||||
@@ -90,10 +90,10 @@ impl<'a, DS: DateStyler> Monthly<'a, DS> {
|
||||
}
|
||||
|
||||
/// Return a style with only the background from the default style
|
||||
fn default_bg(&self) -> Style {
|
||||
const fn default_bg(&self) -> Style {
|
||||
match self.default_style.bg {
|
||||
None => Style::default(),
|
||||
Some(c) => Style::default().bg(c),
|
||||
None => Style::new(),
|
||||
Some(c) => Style::new().bg(c),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ impl<DS: DateStyler> Monthly<'_, DS> {
|
||||
|
||||
let mut y = days_area.y;
|
||||
// go through all the weeks containing a day in the target month.
|
||||
while curr_day.month() as u8 != self.display_date.month().next() as u8 {
|
||||
while curr_day.month() != self.display_date.month().next() {
|
||||
let mut spans = Vec::with_capacity(14);
|
||||
for i in 0..7 {
|
||||
// Draw the gutter. Do it here so we can avoid worrying about
|
||||
|
||||
@@ -19,7 +19,7 @@ mod points;
|
||||
mod rectangle;
|
||||
mod world;
|
||||
|
||||
use std::{fmt::Debug, iter::zip};
|
||||
use std::{fmt, iter::zip};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
@@ -70,7 +70,7 @@ struct Layer {
|
||||
/// resolution of the grid might exceed the number of rows and columns. For example, a grid of
|
||||
/// Braille patterns will have a resolution of 2x4 dots per cell. This means that a grid of 10x10
|
||||
/// cells will have a resolution of 20x40 dots.
|
||||
trait Grid: Debug {
|
||||
trait Grid: fmt::Debug {
|
||||
/// Get the resolution of the grid in number of dots.
|
||||
///
|
||||
/// This doesn't have to be the same as the number of rows and columns of the grid. For example,
|
||||
@@ -567,7 +567,7 @@ impl<'a> Context<'a> {
|
||||
/// };
|
||||
///
|
||||
/// Canvas::default()
|
||||
/// .block(Block::default().title("Canvas").borders(Borders::ALL))
|
||||
/// .block(Block::bordered().title("Canvas"))
|
||||
/// .x_bounds([-180.0, 180.0])
|
||||
/// .y_bounds([-90.0, 90.0])
|
||||
/// .paint(|ctx| {
|
||||
|
||||
@@ -58,7 +58,7 @@ mod tests {
|
||||
.x_bounds([-10.0, 10.0])
|
||||
.y_bounds([-10.0, 10.0]);
|
||||
canvas.render(buffer.area, &mut buffer);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
" ⢀⣠⢤⣀ ",
|
||||
" ⢰⠋ ⠈⣇",
|
||||
" ⠘⣆⡀ ⣠⠇",
|
||||
|
||||
@@ -112,154 +112,111 @@ fn draw_line_high(painter: &mut Painter, x1: usize, y1: usize, x2: usize, y2: us
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Line;
|
||||
use crate::{assert_buffer_eq, prelude::*, widgets::canvas::Canvas};
|
||||
use rstest::rstest;
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[track_caller]
|
||||
fn test(line: Line, expected_lines: Vec<&str>) {
|
||||
use super::{super::*, *};
|
||||
use crate::{buffer::Buffer, layout::Rect};
|
||||
|
||||
#[rstest]
|
||||
#[case::off_grid(&Line::new(-1.0, -1.0, 10.0, 10.0, Color::Red), [" "; 10])]
|
||||
#[case::off_grid(&Line::new(0.0, 0.0, 11.0, 11.0, Color::Red), [" "; 10])]
|
||||
#[case::horizontal(&Line::new(0.0, 0.0, 10.0, 0.0, Color::Red), [
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
"••••••••••",
|
||||
])]
|
||||
#[case::horizontal(&Line::new(10.0, 10.0, 0.0, 10.0, Color::Red), [
|
||||
"••••••••••",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
])]
|
||||
#[case::vertical(&Line::new(0.0, 0.0, 0.0, 10.0, Color::Red), ["• "; 10])]
|
||||
#[case::vertical(&Line::new(10.0, 10.0, 10.0, 0.0, Color::Red), [" •"; 10])]
|
||||
// dy < dx, x1 < x2
|
||||
#[case::diagonal(&Line::new(0.0, 0.0, 10.0, 5.0, Color::Red), [
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" •",
|
||||
" •• ",
|
||||
" •• ",
|
||||
" •• ",
|
||||
" •• ",
|
||||
"• ",
|
||||
])]
|
||||
// dy < dx, x1 > x2
|
||||
#[case::diagonal(&Line::new(10.0, 0.0, 0.0, 5.0, Color::Red), [
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
"• ",
|
||||
" •• ",
|
||||
" •• ",
|
||||
" •• ",
|
||||
" •• ",
|
||||
" •",
|
||||
])]
|
||||
// dy > dx, y1 < y2
|
||||
#[case::diagonal(&Line::new(0.0, 0.0, 5.0, 10.0, Color::Red), [
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
"• ",
|
||||
"• ",
|
||||
])]
|
||||
// dy > dx, y1 > y2
|
||||
#[case::diagonal(&Line::new(0.0, 10.0, 5.0, 0.0, Color::Red), [
|
||||
"• ",
|
||||
"• ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
])]
|
||||
fn tests<'expected_line, ExpectedLines>(#[case] line: &Line, #[case] expected: ExpectedLines)
|
||||
where
|
||||
ExpectedLines: IntoIterator,
|
||||
ExpectedLines::Item: Into<crate::text::Line<'expected_line>>,
|
||||
{
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
|
||||
let canvas = Canvas::default()
|
||||
.marker(Marker::Dot)
|
||||
.x_bounds([0.0, 10.0])
|
||||
.y_bounds([0.0, 10.0])
|
||||
.paint(|context| {
|
||||
context.draw(&line);
|
||||
});
|
||||
.paint(|context| context.draw(line));
|
||||
canvas.render(buffer.area, &mut buffer);
|
||||
|
||||
let mut expected = Buffer::with_lines(expected_lines);
|
||||
let mut expected = Buffer::with_lines(expected);
|
||||
for cell in &mut expected.content {
|
||||
if cell.symbol() == "•" {
|
||||
cell.set_style(Style::new().red());
|
||||
}
|
||||
}
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn off_grid() {
|
||||
test(
|
||||
Line::new(-1.0, -1.0, 10.0, 10.0, Color::Red),
|
||||
vec![" "; 10],
|
||||
);
|
||||
test(
|
||||
Line::new(0.0, 0.0, 11.0, 11.0, Color::Red),
|
||||
vec![" "; 10],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn horizontal() {
|
||||
test(
|
||||
Line::new(0.0, 0.0, 10.0, 0.0, Color::Red),
|
||||
vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
"••••••••••",
|
||||
],
|
||||
);
|
||||
test(
|
||||
Line::new(10.0, 10.0, 0.0, 10.0, Color::Red),
|
||||
vec![
|
||||
"••••••••••",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vertical() {
|
||||
test(
|
||||
Line::new(0.0, 0.0, 0.0, 10.0, Color::Red),
|
||||
vec!["• "; 10],
|
||||
);
|
||||
test(
|
||||
Line::new(10.0, 10.0, 10.0, 0.0, Color::Red),
|
||||
vec![" •"; 10],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diagonal() {
|
||||
// dy < dx, x1 < x2
|
||||
test(
|
||||
Line::new(0.0, 0.0, 10.0, 5.0, Color::Red),
|
||||
vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" •",
|
||||
" •• ",
|
||||
" •• ",
|
||||
" •• ",
|
||||
" •• ",
|
||||
"• ",
|
||||
],
|
||||
);
|
||||
// dy < dx, x1 > x2
|
||||
test(
|
||||
Line::new(10.0, 0.0, 0.0, 5.0, Color::Red),
|
||||
vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
"• ",
|
||||
" •• ",
|
||||
" •• ",
|
||||
" •• ",
|
||||
" •• ",
|
||||
" •",
|
||||
],
|
||||
);
|
||||
// dy > dx, y1 < y2
|
||||
test(
|
||||
Line::new(0.0, 0.0, 5.0, 10.0, Color::Red),
|
||||
vec![
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
"• ",
|
||||
"• ",
|
||||
],
|
||||
);
|
||||
// dy > dx, y1 > y2
|
||||
test(
|
||||
Line::new(0.0, 10.0, 5.0, 0.0, Color::Red),
|
||||
vec![
|
||||
"• ",
|
||||
"• ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
" • ",
|
||||
],
|
||||
);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ pub enum MapResolution {
|
||||
}
|
||||
|
||||
impl MapResolution {
|
||||
fn data(self) -> &'static [(f64, f64)] {
|
||||
const fn data(self) -> &'static [(f64, f64)] {
|
||||
match self {
|
||||
Self::Low => &WORLD_LOW_RESOLUTION,
|
||||
Self::High => &WORLD_HIGH_RESOLUTION,
|
||||
@@ -65,7 +65,7 @@ mod tests {
|
||||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
use crate::{assert_buffer_eq, prelude::*, widgets::canvas::Canvas};
|
||||
use crate::{prelude::*, widgets::canvas::Canvas};
|
||||
|
||||
#[test]
|
||||
fn map_resolution_to_string() {
|
||||
@@ -101,7 +101,7 @@ mod tests {
|
||||
context.draw(&Map::default());
|
||||
});
|
||||
canvas.render(buffer.area, &mut buffer);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
" ",
|
||||
" ••••••• •• •• •• • ",
|
||||
" •••••••••••••• ••• •••• ••• •• •••• ",
|
||||
@@ -143,7 +143,7 @@ mod tests {
|
||||
" • ",
|
||||
" ",
|
||||
]);
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -160,7 +160,7 @@ mod tests {
|
||||
});
|
||||
});
|
||||
canvas.render(buffer.area, &mut buffer);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
" ",
|
||||
" ⢀⣠⠤⠤⠤⠔⢤⣤⡄⠤⡠⣄⠢⠂⢢⠰⣠⡄⣀⡀ ⣀ ",
|
||||
" ⢀⣀⡤⣦⠲⢶⣿⣮⣿⡉⣰⢶⢏⡂ ⢀⣟⠁ ⢺⣻⢿⠏ ⠈⠉⠁ ⢀⣀ ⠈⠓⢳⣢⣂⡀ ",
|
||||
@@ -202,6 +202,6 @@ mod tests {
|
||||
"⠶⠔⠲⠤⠠⠜⢗⠤⠄ ⠘⠉ ⠁ ⠈⠉⠒⠔⠤",
|
||||
" ",
|
||||
]);
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ impl Shape for Rectangle {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{assert_buffer_eq, prelude::*, widgets::canvas::Canvas};
|
||||
use crate::{prelude::*, widgets::canvas::Canvas};
|
||||
|
||||
#[test]
|
||||
fn draw_block_lines() {
|
||||
@@ -85,7 +85,7 @@ mod tests {
|
||||
});
|
||||
});
|
||||
canvas.render(buffer.area, &mut buffer);
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
"██████████",
|
||||
"█ █",
|
||||
"█ █",
|
||||
@@ -99,7 +99,7 @@ mod tests {
|
||||
]);
|
||||
expected.set_style(buffer.area, Style::new().red());
|
||||
expected.set_style(buffer.area.inner(&Margin::new(1, 1)), Style::reset());
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -119,7 +119,7 @@ mod tests {
|
||||
});
|
||||
});
|
||||
canvas.render(buffer.area, &mut buffer);
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
"█▀▀▀▀▀▀▀▀█",
|
||||
"█ █",
|
||||
"█ █",
|
||||
@@ -134,7 +134,7 @@ mod tests {
|
||||
expected.set_style(buffer.area, Style::new().red().on_red());
|
||||
expected.set_style(buffer.area.inner(&Margin::new(1, 0)), Style::reset().red());
|
||||
expected.set_style(buffer.area.inner(&Margin::new(1, 1)), Style::reset());
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -163,7 +163,7 @@ mod tests {
|
||||
});
|
||||
});
|
||||
canvas.render(buffer.area, &mut buffer);
|
||||
let mut expected = Buffer::with_lines(vec
|
||||
pub static WORLD_HIGH_RESOLUTION: [(f64, f64); 5125] = [
|
||||
pub const WORLD_HIGH_RESOLUTION: [(f64, f64); 5125] = [
|
||||
(-163.7128, -78.5956),
|
||||
(-163.1058, -78.2233),
|
||||
(-161.2451, -78.3801),
|
||||
@@ -5127,7 +5127,7 @@ pub static WORLD_HIGH_RESOLUTION: [(f64, f64); 5125] = [
|
||||
(180.0, -84.71338),
|
||||
];
|
||||
|
||||
pub static WORLD_LOW_RESOLUTION: [(f64, f64); 1166] = [
|
||||
pub const WORLD_LOW_RESOLUTION: [(f64, f64); 1166] = [
|
||||
(-92.32, 48.24),
|
||||
(-88.13, 48.92),
|
||||
(-83.11, 46.27),
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
prelude::*,
|
||||
widgets::{
|
||||
canvas::{Canvas, Line as CanvasLine, Points},
|
||||
Block, Borders,
|
||||
Block,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -475,7 +475,7 @@ struct ChartLayout {
|
||||
///
|
||||
/// // Create the chart and link all the parts together
|
||||
/// let chart = Chart::new(datasets)
|
||||
/// .block(Block::default().title("Chart"))
|
||||
/// .block(Block::new().title("Chart"))
|
||||
/// .x_axis(x_axis)
|
||||
/// .y_axis(y_axis);
|
||||
/// ```
|
||||
@@ -1050,9 +1050,7 @@ impl WidgetRef for Chart<'_> {
|
||||
|
||||
if let Some(legend_area) = layout.legend_area {
|
||||
buf.set_style(legend_area, original_style);
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.render(legend_area, buf);
|
||||
Block::bordered().render(legend_area, buf);
|
||||
|
||||
for (i, (dataset_name, dataset_style)) in self
|
||||
.datasets
|
||||
@@ -1113,10 +1111,10 @@ impl<'a> Styled for Chart<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rstest::rstest;
|
||||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
use crate::assert_buffer_eq;
|
||||
|
||||
struct LegendTestCase {
|
||||
chart_area: Rect,
|
||||
@@ -1211,7 +1209,6 @@ mod tests {
|
||||
.x_axis(Axis::default().title("xxxxxxxxxxxxxxxx"));
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 4));
|
||||
widget.render(buffer.area, &mut buffer);
|
||||
|
||||
assert_eq!(buffer, Buffer::with_lines(vec![" ".repeat(8); 4]));
|
||||
}
|
||||
|
||||
@@ -1246,30 +1243,25 @@ mod tests {
|
||||
let widget = Chart::new(vec![long_dataset_name, short_dataset])
|
||||
.hidden_legend_constraints((100.into(), 100.into()));
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 20, 5));
|
||||
|
||||
widget.render(buffer.area, &mut buffer);
|
||||
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
" ┌──────────────┐",
|
||||
" │Very long name│",
|
||||
" │ Short name│",
|
||||
" └──────────────┘",
|
||||
" ",
|
||||
]);
|
||||
assert_buffer_eq!(buffer, expected);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chart_have_a_topleft_legend() {
|
||||
let chart = Chart::new(vec![Dataset::default().name("Ds1")])
|
||||
.legend_position(Some(LegendPosition::TopLeft));
|
||||
|
||||
let area = Rect::new(0, 0, 30, 20);
|
||||
let mut buffer = Buffer::empty(area);
|
||||
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
"┌───┐ ",
|
||||
"│Ds1│ ",
|
||||
"└───┘ ",
|
||||
@@ -1291,7 +1283,6 @@ mod tests {
|
||||
" ",
|
||||
" ",
|
||||
]);
|
||||
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
@@ -1299,13 +1290,10 @@ mod tests {
|
||||
fn test_chart_have_a_long_y_axis_title_overlapping_legend() {
|
||||
let chart = Chart::new(vec![Dataset::default().name("Ds1")])
|
||||
.y_axis(Axis::default().title("The title overlap a legend."));
|
||||
|
||||
let area = Rect::new(0, 0, 30, 20);
|
||||
let mut buffer = Buffer::empty(area);
|
||||
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
"The title overlap a legend. ",
|
||||
" ┌───┐",
|
||||
" │Ds1│",
|
||||
@@ -1327,7 +1315,6 @@ mod tests {
|
||||
" ",
|
||||
" ",
|
||||
]);
|
||||
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
@@ -1335,13 +1322,10 @@ mod tests {
|
||||
fn test_chart_have_overflowed_y_axis() {
|
||||
let chart = Chart::new(vec![Dataset::default().name("Ds1")])
|
||||
.y_axis(Axis::default().title("The title overlap a legend."));
|
||||
|
||||
let area = Rect::new(0, 0, 10, 10);
|
||||
let mut buffer = Buffer::empty(area);
|
||||
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
@@ -1353,7 +1337,6 @@ mod tests {
|
||||
" ",
|
||||
" ",
|
||||
]);
|
||||
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
@@ -1362,12 +1345,8 @@ mod tests {
|
||||
let name = "Data";
|
||||
let chart = Chart::new(vec![Dataset::default().name(name)])
|
||||
.hidden_legend_constraints((Constraint::Percentage(100), Constraint::Percentage(100)));
|
||||
|
||||
let area = Rect::new(0, 0, name.len() as u16 + 2, 3);
|
||||
let mut buffer = Buffer::empty(area);
|
||||
|
||||
let expected = Buffer::with_lines(vec!["┌────┐", "│Data│", "└────┘"]);
|
||||
|
||||
for position in [
|
||||
LegendPosition::TopLeft,
|
||||
LegendPosition::Top,
|
||||
@@ -1381,171 +1360,103 @@ mod tests {
|
||||
let chart = chart.clone().legend_position(Some(position));
|
||||
buffer.reset();
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
#[rustfmt::skip]
|
||||
let expected = Buffer::with_lines([
|
||||
"┌────┐",
|
||||
"│Data│",
|
||||
"└────┘",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[test]
|
||||
fn test_legend_of_chart_have_odd_margin_size() {
|
||||
#[rstest]
|
||||
#[case(Some(LegendPosition::TopLeft), [
|
||||
"┌────┐ ",
|
||||
"│Data│ ",
|
||||
"└────┘ ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
])]
|
||||
#[case(Some(LegendPosition::Top), [
|
||||
" ┌────┐ ",
|
||||
" │Data│ ",
|
||||
" └────┘ ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
])]
|
||||
#[case(Some(LegendPosition::TopRight), [
|
||||
" ┌────┐",
|
||||
" │Data│",
|
||||
" └────┘",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
])]
|
||||
#[case(Some(LegendPosition::Left), [
|
||||
" ",
|
||||
"┌────┐ ",
|
||||
"│Data│ ",
|
||||
"└────┘ ",
|
||||
" ",
|
||||
" ",
|
||||
])]
|
||||
#[case(Some(LegendPosition::Right), [
|
||||
" ",
|
||||
" ┌────┐",
|
||||
" │Data│",
|
||||
" └────┘",
|
||||
" ",
|
||||
" ",
|
||||
])]
|
||||
#[case(Some(LegendPosition::BottomLeft), [
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
"┌────┐ ",
|
||||
"│Data│ ",
|
||||
"└────┘ ",
|
||||
])]
|
||||
#[case(Some(LegendPosition::Bottom), [
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ┌────┐ ",
|
||||
" │Data│ ",
|
||||
" └────┘ ",
|
||||
])]
|
||||
#[case(Some(LegendPosition::BottomRight), [
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ┌────┐",
|
||||
" │Data│",
|
||||
" └────┘",
|
||||
])]
|
||||
#[case(None, [
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
])]
|
||||
fn test_legend_of_chart_have_odd_margin_size<'line, Lines>(
|
||||
#[case] legend_position: Option<LegendPosition>,
|
||||
#[case] expected: Lines,
|
||||
) where
|
||||
Lines: IntoIterator,
|
||||
Lines::Item: Into<Line<'line>>,
|
||||
{
|
||||
let name = "Data";
|
||||
let base_chart = Chart::new(vec![Dataset::default().name(name)])
|
||||
.hidden_legend_constraints((Constraint::Percentage(100), Constraint::Percentage(100)));
|
||||
|
||||
let area = Rect::new(0, 0, name.len() as u16 + 2 + 3, 3 + 3);
|
||||
let mut buffer = Buffer::empty(area);
|
||||
|
||||
let chart = base_chart
|
||||
.clone()
|
||||
.legend_position(Some(LegendPosition::TopLeft));
|
||||
buffer.reset();
|
||||
let chart = Chart::new(vec![Dataset::default().name(name)])
|
||||
.legend_position(legend_position)
|
||||
.hidden_legend_constraints((Constraint::Percentage(100), Constraint::Percentage(100)));
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
"┌────┐ ",
|
||||
"│Data│ ",
|
||||
"└────┘ ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
])
|
||||
);
|
||||
buffer.reset();
|
||||
|
||||
let chart = base_chart
|
||||
.clone()
|
||||
.legend_position(Some(LegendPosition::Top));
|
||||
buffer.reset();
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ┌────┐ ",
|
||||
" │Data│ ",
|
||||
" └────┘ ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
])
|
||||
);
|
||||
|
||||
let chart = base_chart
|
||||
.clone()
|
||||
.legend_position(Some(LegendPosition::TopRight));
|
||||
buffer.reset();
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ┌────┐",
|
||||
" │Data│",
|
||||
" └────┘",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
])
|
||||
);
|
||||
|
||||
let chart = base_chart
|
||||
.clone()
|
||||
.legend_position(Some(LegendPosition::Left));
|
||||
buffer.reset();
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ",
|
||||
"┌────┐ ",
|
||||
"│Data│ ",
|
||||
"└────┘ ",
|
||||
" ",
|
||||
" ",
|
||||
])
|
||||
);
|
||||
buffer.reset();
|
||||
|
||||
let chart = base_chart
|
||||
.clone()
|
||||
.legend_position(Some(LegendPosition::Right));
|
||||
buffer.reset();
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ",
|
||||
" ┌────┐",
|
||||
" │Data│",
|
||||
" └────┘",
|
||||
" ",
|
||||
" ",
|
||||
])
|
||||
);
|
||||
|
||||
let chart = base_chart
|
||||
.clone()
|
||||
.legend_position(Some(LegendPosition::BottomLeft));
|
||||
buffer.reset();
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
"┌────┐ ",
|
||||
"│Data│ ",
|
||||
"└────┘ ",
|
||||
])
|
||||
);
|
||||
|
||||
let chart = base_chart
|
||||
.clone()
|
||||
.legend_position(Some(LegendPosition::Bottom));
|
||||
buffer.reset();
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ┌────┐ ",
|
||||
" │Data│ ",
|
||||
" └────┘ ",
|
||||
])
|
||||
);
|
||||
|
||||
let chart = base_chart
|
||||
.clone()
|
||||
.legend_position(Some(LegendPosition::BottomRight));
|
||||
buffer.reset();
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ┌────┐",
|
||||
" │Data│",
|
||||
" └────┘",
|
||||
])
|
||||
);
|
||||
|
||||
let chart = base_chart.clone().legend_position(None);
|
||||
buffer.reset();
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
])
|
||||
);
|
||||
assert_eq!(buffer, Buffer::with_lines(expected));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::prelude::*;
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
///
|
||||
/// fn draw_on_clear(f: &mut Frame, area: Rect) {
|
||||
/// let block = Block::default().title("Block").borders(Borders::ALL);
|
||||
/// let block = Block::bordered().title("Block");
|
||||
/// f.render_widget(Clear, area); // <- this will clear/reset the area first
|
||||
/// f.render_widget(block, area); // now render the block widget
|
||||
/// }
|
||||
@@ -43,24 +43,21 @@ impl WidgetRef for Clear {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::assert_buffer_eq;
|
||||
|
||||
#[test]
|
||||
fn render() {
|
||||
let mut buf = Buffer::with_lines(vec!["xxxxxxxxxxxxxxx"; 7]);
|
||||
let mut buffer = Buffer::with_lines(["xxxxxxxxxxxxxxx"; 7]);
|
||||
let clear = Clear;
|
||||
clear.render(Rect::new(1, 2, 3, 4), &mut buf);
|
||||
assert_buffer_eq!(
|
||||
buf,
|
||||
Buffer::with_lines(vec![
|
||||
"xxxxxxxxxxxxxxx",
|
||||
"xxxxxxxxxxxxxxx",
|
||||
"x xxxxxxxxxxx",
|
||||
"x xxxxxxxxxxx",
|
||||
"x xxxxxxxxxxx",
|
||||
"x xxxxxxxxxxx",
|
||||
"xxxxxxxxxxxxxxx",
|
||||
])
|
||||
);
|
||||
clear.render(Rect::new(1, 2, 3, 4), &mut buffer);
|
||||
let expected = Buffer::with_lines([
|
||||
"xxxxxxxxxxxxxxx",
|
||||
"xxxxxxxxxxxxxxx",
|
||||
"x xxxxxxxxxxx",
|
||||
"x xxxxxxxxxxx",
|
||||
"x xxxxxxxxxxx",
|
||||
"x xxxxxxxxxxx",
|
||||
"xxxxxxxxxxxxxxx",
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::{prelude::*, widgets::Block};
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
///
|
||||
/// Gauge::default()
|
||||
/// .block(Block::default().borders(Borders::ALL).title("Progress"))
|
||||
/// .block(Block::bordered().title("Progress"))
|
||||
/// .gauge_style(
|
||||
/// Style::default()
|
||||
/// .fg(Color::White)
|
||||
@@ -30,6 +30,7 @@ use crate::{prelude::*, widgets::Block};
|
||||
/// # See also
|
||||
///
|
||||
/// - [`LineGauge`] for a thin progress bar
|
||||
#[allow(clippy::struct_field_names)] // gauge_style needs to be differentiated to style
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Gauge<'a> {
|
||||
block: Option<Block<'a>>,
|
||||
@@ -164,12 +165,12 @@ impl WidgetRef for Gauge<'_> {
|
||||
buf.set_style(area, self.style);
|
||||
self.block.render_ref(area, buf);
|
||||
let inner = self.block.inner_if_some(area);
|
||||
self.render_gague(inner, buf);
|
||||
self.render_gauge(inner, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Gauge<'_> {
|
||||
fn render_gague(&self, gauge_area: Rect, buf: &mut Buffer) {
|
||||
fn render_gauge(&self, gauge_area: Rect, buf: &mut Buffer) {
|
||||
if gauge_area.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -249,7 +250,7 @@ fn get_unicode_block<'a>(frac: f64) -> &'a str {
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
///
|
||||
/// LineGauge::default()
|
||||
/// .block(Block::default().borders(Borders::ALL).title("Progress"))
|
||||
/// .block(Block::bordered().title("Progress"))
|
||||
/// .gauge_style(
|
||||
/// Style::default()
|
||||
/// .fg(Color::White)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,7 @@ const fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Align
|
||||
/// "Third line".into(),
|
||||
/// ];
|
||||
/// Paragraph::new(text)
|
||||
/// .block(Block::new().title("Paragraph").borders(Borders::ALL))
|
||||
/// .block(Block::bordered().title("Paragraph"))
|
||||
/// .style(Style::new().white().on_black())
|
||||
/// .alignment(Alignment::Center)
|
||||
/// .wrap(Wrap { trim: true });
|
||||
@@ -126,8 +126,7 @@ impl<'a> Paragraph<'a> {
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let paragraph = Paragraph::new("Hello, world!")
|
||||
/// .block(Block::default().title("Paragraph").borders(Borders::ALL));
|
||||
/// let paragraph = Paragraph::new("Hello, world!").block(Block::bordered().title("Paragraph"));
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn block(mut self, block: Block<'a>) -> Self {
|
||||
@@ -256,6 +255,8 @@ impl<'a> Paragraph<'a> {
|
||||
/// need in order to be fully rendered. For paragraphs that do not use wrapping, this count is
|
||||
/// simply the number of lines present in the paragraph.
|
||||
///
|
||||
/// Note: The design for text wrapping is not stable and might affect this API.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
@@ -267,7 +268,6 @@ impl<'a> Paragraph<'a> {
|
||||
/// ```
|
||||
#[stability::unstable(
|
||||
feature = "rendered-line-info",
|
||||
reason = "The design for text wrapping is not stable and might affect this API.",
|
||||
issue = "https://github.com/ratatui-org/ratatui/issues/293"
|
||||
)]
|
||||
pub fn line_count(&self, width: u16) -> usize {
|
||||
@@ -297,6 +297,8 @@ impl<'a> Paragraph<'a> {
|
||||
|
||||
/// Calculates the shortest line width needed to avoid any word being wrapped or truncated.
|
||||
///
|
||||
/// Note: The design for text wrapping is not stable and might affect this API.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
@@ -309,7 +311,6 @@ impl<'a> Paragraph<'a> {
|
||||
/// ```
|
||||
#[stability::unstable(
|
||||
feature = "rendered-line-info",
|
||||
reason = "The design for text wrapping is not stable and might affect this API.",
|
||||
issue = "https://github.com/ratatui-org/ratatui/issues/293"
|
||||
)]
|
||||
pub fn line_width(&self) -> usize {
|
||||
@@ -413,73 +414,65 @@ mod test {
|
||||
/// area and comparing the rendered and expected content.
|
||||
/// This can be used for easy testing of varying configured paragraphs with the same expected
|
||||
/// buffer or any other test case really.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn test_case(paragraph: &Paragraph, expected: Buffer) {
|
||||
#[track_caller]
|
||||
fn test_case(paragraph: &Paragraph, expected: &Buffer) {
|
||||
let backend = TestBackend::new(expected.area.width, expected.area.height);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
f.render_widget(paragraph.clone(), size);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
terminal.backend().assert_buffer(expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_width_char_at_end_of_line() {
|
||||
let line = "foo\0";
|
||||
let paragraphs = vec![
|
||||
for paragraph in [
|
||||
Paragraph::new(line),
|
||||
Paragraph::new(line).wrap(Wrap { trim: false }),
|
||||
Paragraph::new(line).wrap(Wrap { trim: true }),
|
||||
];
|
||||
|
||||
for paragraph in paragraphs {
|
||||
test_case(¶graph, Buffer::with_lines(vec!["foo"]));
|
||||
test_case(¶graph, Buffer::with_lines(vec!["foo "]));
|
||||
test_case(¶graph, Buffer::with_lines(vec!["foo ", " "]));
|
||||
test_case(¶graph, Buffer::with_lines(vec!["foo", " "]));
|
||||
] {
|
||||
test_case(¶graph, &Buffer::with_lines(["foo"]));
|
||||
test_case(¶graph, &Buffer::with_lines(["foo "]));
|
||||
test_case(¶graph, &Buffer::with_lines(["foo ", " "]));
|
||||
test_case(¶graph, &Buffer::with_lines(["foo", " "]));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_empty_paragraph() {
|
||||
let paragraphs = vec![
|
||||
for paragraph in [
|
||||
Paragraph::new(""),
|
||||
Paragraph::new("").wrap(Wrap { trim: false }),
|
||||
Paragraph::new("").wrap(Wrap { trim: true }),
|
||||
];
|
||||
|
||||
for paragraph in paragraphs {
|
||||
test_case(¶graph, Buffer::with_lines(vec![" "]));
|
||||
test_case(¶graph, Buffer::with_lines(vec![" "]));
|
||||
test_case(¶graph, Buffer::with_lines(vec![" "; 10]));
|
||||
test_case(¶graph, Buffer::with_lines(vec![" ", " "]));
|
||||
] {
|
||||
test_case(¶graph, &Buffer::with_lines([" "]));
|
||||
test_case(¶graph, &Buffer::with_lines([" "]));
|
||||
test_case(¶graph, &Buffer::with_lines([" "; 10]));
|
||||
test_case(¶graph, &Buffer::with_lines([" ", " "]));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_single_line_paragraph() {
|
||||
let text = "Hello, world!";
|
||||
let truncated_paragraph = Paragraph::new(text);
|
||||
let wrapped_paragraph = Paragraph::new(text).wrap(Wrap { trim: false });
|
||||
let trimmed_paragraph = Paragraph::new(text).wrap(Wrap { trim: true });
|
||||
|
||||
let paragraphs = vec![&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph];
|
||||
|
||||
for paragraph in paragraphs {
|
||||
test_case(paragraph, Buffer::with_lines(vec!["Hello, world! "]));
|
||||
test_case(paragraph, Buffer::with_lines(vec!["Hello, world!"]));
|
||||
for paragraph in [
|
||||
Paragraph::new(text),
|
||||
Paragraph::new(text).wrap(Wrap { trim: false }),
|
||||
Paragraph::new(text).wrap(Wrap { trim: true }),
|
||||
] {
|
||||
test_case(¶graph, &Buffer::with_lines(["Hello, world! "]));
|
||||
test_case(¶graph, &Buffer::with_lines(["Hello, world!"]));
|
||||
test_case(
|
||||
paragraph,
|
||||
Buffer::with_lines(vec!["Hello, world! ", " "]),
|
||||
¶graph,
|
||||
&Buffer::with_lines(["Hello, world! ", " "]),
|
||||
);
|
||||
test_case(
|
||||
paragraph,
|
||||
Buffer::with_lines(vec!["Hello, world!", " "]),
|
||||
¶graph,
|
||||
&Buffer::with_lines(["Hello, world!", " "]),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -487,29 +480,22 @@ mod test {
|
||||
#[test]
|
||||
fn test_render_multi_line_paragraph() {
|
||||
let text = "This is a\nmultiline\nparagraph.";
|
||||
|
||||
let paragraphs = vec![
|
||||
for paragraph in [
|
||||
Paragraph::new(text),
|
||||
Paragraph::new(text).wrap(Wrap { trim: false }),
|
||||
Paragraph::new(text).wrap(Wrap { trim: true }),
|
||||
];
|
||||
|
||||
for paragraph in paragraphs {
|
||||
] {
|
||||
test_case(
|
||||
¶graph,
|
||||
Buffer::with_lines(vec!["This is a ", "multiline ", "paragraph."]),
|
||||
&Buffer::with_lines(["This is a ", "multiline ", "paragraph."]),
|
||||
);
|
||||
test_case(
|
||||
¶graph,
|
||||
Buffer::with_lines(vec![
|
||||
"This is a ",
|
||||
"multiline ",
|
||||
"paragraph. ",
|
||||
]),
|
||||
&Buffer::with_lines(["This is a ", "multiline ", "paragraph. "]),
|
||||
);
|
||||
test_case(
|
||||
¶graph,
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"This is a ",
|
||||
"multiline ",
|
||||
"paragraph. ",
|
||||
@@ -525,17 +511,15 @@ mod test {
|
||||
// We use the slightly unconventional "worlds" instead of "world" here to make sure when we
|
||||
// can truncate this without triggering the typos linter.
|
||||
let text = "Hello, worlds!";
|
||||
let truncated_paragraph =
|
||||
Paragraph::new(text).block(Block::default().title("Title").borders(Borders::ALL));
|
||||
let truncated_paragraph = Paragraph::new(text).block(Block::bordered().title("Title"));
|
||||
let wrapped_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: false });
|
||||
let trimmed_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: true });
|
||||
|
||||
let paragraphs = vec![&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph];
|
||||
|
||||
for paragraph in paragraphs {
|
||||
for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
|
||||
#[rustfmt::skip]
|
||||
test_case(
|
||||
paragraph,
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌Title─────────┐",
|
||||
"│Hello, worlds!│",
|
||||
"└──────────────┘",
|
||||
@@ -543,7 +527,7 @@ mod test {
|
||||
);
|
||||
test_case(
|
||||
paragraph,
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌Title───────────┐",
|
||||
"│Hello, worlds! │",
|
||||
"└────────────────┘",
|
||||
@@ -551,7 +535,7 @@ mod test {
|
||||
);
|
||||
test_case(
|
||||
paragraph,
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌Title────────────┐",
|
||||
"│Hello, worlds! │",
|
||||
"│ │",
|
||||
@@ -562,7 +546,7 @@ mod test {
|
||||
|
||||
test_case(
|
||||
&truncated_paragraph,
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌Title───────┐",
|
||||
"│Hello, world│",
|
||||
"│ │",
|
||||
@@ -571,7 +555,7 @@ mod test {
|
||||
);
|
||||
test_case(
|
||||
&wrapped_paragraph,
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌Title──────┐",
|
||||
"│Hello, │",
|
||||
"│worlds! │",
|
||||
@@ -580,7 +564,7 @@ mod test {
|
||||
);
|
||||
test_case(
|
||||
&trimmed_paragraph,
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌Title──────┐",
|
||||
"│Hello, │",
|
||||
"│worlds! │",
|
||||
@@ -598,42 +582,41 @@ mod test {
|
||||
let paragraph = Paragraph::new(vec![l0, l1, l2, l3]);
|
||||
|
||||
let mut expected =
|
||||
Buffer::with_lines(vec!["unformatted", "bold text", "cyan text", "dim text"]);
|
||||
Buffer::with_lines(["unformatted", "bold text", "cyan text", "dim text"]);
|
||||
expected.set_style(Rect::new(0, 1, 9, 1), Style::new().bold());
|
||||
expected.set_style(Rect::new(0, 2, 9, 1), Style::new().cyan());
|
||||
expected.set_style(Rect::new(0, 3, 8, 1), Style::new().dim());
|
||||
|
||||
test_case(¶graph, expected);
|
||||
test_case(¶graph, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_line_spans_styled() {
|
||||
let l0 = Line::default().spans(vec![
|
||||
let l0 = Line::default().spans([
|
||||
Span::styled("bold", Style::new().bold()),
|
||||
Span::raw(" and "),
|
||||
Span::styled("cyan", Style::new().cyan()),
|
||||
]);
|
||||
let l1 = Line::default().spans(vec![Span::raw("unformatted")]);
|
||||
let l1 = Line::default().spans([Span::raw("unformatted")]);
|
||||
let paragraph = Paragraph::new(vec![l0, l1]);
|
||||
|
||||
let mut expected = Buffer::with_lines(vec!["bold and cyan", "unformatted"]);
|
||||
let mut expected = Buffer::with_lines(["bold and cyan", "unformatted"]);
|
||||
expected.set_style(Rect::new(0, 0, 4, 1), Style::new().bold());
|
||||
expected.set_style(Rect::new(9, 0, 4, 1), Style::new().cyan());
|
||||
|
||||
test_case(¶graph, expected);
|
||||
test_case(¶graph, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_paragraph_with_block_with_bottom_title_and_border() {
|
||||
let block = Block::default()
|
||||
.title("Title")
|
||||
let block = Block::new()
|
||||
.borders(Borders::BOTTOM)
|
||||
.title_position(Position::Bottom)
|
||||
.borders(Borders::BOTTOM);
|
||||
.title("Title");
|
||||
let paragraph = Paragraph::new("Hello, world!").block(block);
|
||||
|
||||
test_case(
|
||||
¶graph,
|
||||
Buffer::with_lines(vec!["Hello, world! ", "Title──────────"]),
|
||||
&Buffer::with_lines(["Hello, world! ", "Title──────────"]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -645,7 +628,7 @@ mod test {
|
||||
|
||||
test_case(
|
||||
&wrapped_paragraph,
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"This is a long line",
|
||||
"of text that should",
|
||||
"wrap and ",
|
||||
@@ -656,7 +639,7 @@ mod test {
|
||||
);
|
||||
test_case(
|
||||
&wrapped_paragraph,
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"This is a ",
|
||||
"long line of",
|
||||
"text that ",
|
||||
@@ -671,7 +654,7 @@ mod test {
|
||||
|
||||
test_case(
|
||||
&trimmed_paragraph,
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"This is a long line",
|
||||
"of text that should",
|
||||
"wrap and ",
|
||||
@@ -682,7 +665,7 @@ mod test {
|
||||
);
|
||||
test_case(
|
||||
&trimmed_paragraph,
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"This is a ",
|
||||
"long line of",
|
||||
"text that ",
|
||||
@@ -703,19 +686,19 @@ mod test {
|
||||
|
||||
test_case(
|
||||
&truncated_paragraph,
|
||||
Buffer::with_lines(vec!["This is a long line of"]),
|
||||
&Buffer::with_lines(["This is a long line of"]),
|
||||
);
|
||||
test_case(
|
||||
&truncated_paragraph,
|
||||
Buffer::with_lines(vec!["This is a long line of te"]),
|
||||
&Buffer::with_lines(["This is a long line of te"]),
|
||||
);
|
||||
test_case(
|
||||
&truncated_paragraph,
|
||||
Buffer::with_lines(vec!["This is a long line of "]),
|
||||
&Buffer::with_lines(["This is a long line of "]),
|
||||
);
|
||||
test_case(
|
||||
&truncated_paragraph.clone().scroll((0, 2)),
|
||||
Buffer::with_lines(vec!["is is a long line of te"]),
|
||||
&Buffer::with_lines(["is is a long line of te"]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -726,21 +709,19 @@ mod test {
|
||||
let wrapped_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: false });
|
||||
let trimmed_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: true });
|
||||
|
||||
let paragraphs = vec![&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph];
|
||||
|
||||
for paragraph in paragraphs {
|
||||
test_case(paragraph, Buffer::with_lines(vec!["Hello, world! "]));
|
||||
test_case(paragraph, Buffer::with_lines(vec!["Hello, world!"]));
|
||||
for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
|
||||
test_case(paragraph, &Buffer::with_lines(["Hello, world! "]));
|
||||
test_case(paragraph, &Buffer::with_lines(["Hello, world!"]));
|
||||
}
|
||||
|
||||
test_case(&truncated_paragraph, Buffer::with_lines(vec!["Hello, wor"]));
|
||||
test_case(&truncated_paragraph, &Buffer::with_lines(["Hello, wor"]));
|
||||
test_case(
|
||||
&wrapped_paragraph,
|
||||
Buffer::with_lines(vec!["Hello, ", "world! "]),
|
||||
&Buffer::with_lines(["Hello, ", "world! "]),
|
||||
);
|
||||
test_case(
|
||||
&trimmed_paragraph,
|
||||
Buffer::with_lines(vec!["Hello, ", "world! "]),
|
||||
&Buffer::with_lines(["Hello, ", "world! "]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -751,23 +732,21 @@ mod test {
|
||||
let wrapped_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: false });
|
||||
let trimmed_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: true });
|
||||
|
||||
let paragraphs = vec![&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph];
|
||||
|
||||
for paragraph in paragraphs {
|
||||
test_case(paragraph, Buffer::with_lines(vec![" Hello, world! "]));
|
||||
test_case(paragraph, Buffer::with_lines(vec![" Hello, world! "]));
|
||||
test_case(paragraph, Buffer::with_lines(vec![" Hello, world! "]));
|
||||
test_case(paragraph, Buffer::with_lines(vec!["Hello, world!"]));
|
||||
for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
|
||||
test_case(paragraph, &Buffer::with_lines([" Hello, world! "]));
|
||||
test_case(paragraph, &Buffer::with_lines([" Hello, world! "]));
|
||||
test_case(paragraph, &Buffer::with_lines([" Hello, world! "]));
|
||||
test_case(paragraph, &Buffer::with_lines(["Hello, world!"]));
|
||||
}
|
||||
|
||||
test_case(&truncated_paragraph, Buffer::with_lines(vec!["Hello, wor"]));
|
||||
test_case(&truncated_paragraph, &Buffer::with_lines(["Hello, wor"]));
|
||||
test_case(
|
||||
&wrapped_paragraph,
|
||||
Buffer::with_lines(vec![" Hello, ", " world! "]),
|
||||
&Buffer::with_lines([" Hello, ", " world! "]),
|
||||
);
|
||||
test_case(
|
||||
&trimmed_paragraph,
|
||||
Buffer::with_lines(vec![" Hello, ", " world! "]),
|
||||
&Buffer::with_lines([" Hello, ", " world! "]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -778,21 +757,19 @@ mod test {
|
||||
let wrapped_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: false });
|
||||
let trimmed_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: true });
|
||||
|
||||
let paragraphs = vec![&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph];
|
||||
|
||||
for paragraph in paragraphs {
|
||||
test_case(paragraph, Buffer::with_lines(vec![" Hello, world!"]));
|
||||
test_case(paragraph, Buffer::with_lines(vec!["Hello, world!"]));
|
||||
for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
|
||||
test_case(paragraph, &Buffer::with_lines([" Hello, world!"]));
|
||||
test_case(paragraph, &Buffer::with_lines(["Hello, world!"]));
|
||||
}
|
||||
|
||||
test_case(&truncated_paragraph, Buffer::with_lines(vec!["Hello, wor"]));
|
||||
test_case(&truncated_paragraph, &Buffer::with_lines(["Hello, wor"]));
|
||||
test_case(
|
||||
&wrapped_paragraph,
|
||||
Buffer::with_lines(vec![" Hello,", " world!"]),
|
||||
&Buffer::with_lines([" Hello,", " world!"]),
|
||||
);
|
||||
test_case(
|
||||
&trimmed_paragraph,
|
||||
Buffer::with_lines(vec![" Hello,", " world!"]),
|
||||
&Buffer::with_lines([" Hello,", " world!"]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -803,57 +780,51 @@ mod test {
|
||||
let wrapped_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: false });
|
||||
let trimmed_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: true });
|
||||
|
||||
let paragraphs = vec![&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph];
|
||||
|
||||
for paragraph in paragraphs {
|
||||
for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
|
||||
test_case(
|
||||
paragraph,
|
||||
Buffer::with_lines(vec!["multiline ", "paragraph. ", " "]),
|
||||
&Buffer::with_lines(["multiline ", "paragraph. ", " "]),
|
||||
);
|
||||
test_case(paragraph, Buffer::with_lines(vec!["multiline "]));
|
||||
test_case(paragraph, &Buffer::with_lines(["multiline "]));
|
||||
}
|
||||
|
||||
test_case(
|
||||
&truncated_paragraph.clone().scroll((2, 4)),
|
||||
Buffer::with_lines(vec!["iline ", "graph. "]),
|
||||
&Buffer::with_lines(["iline ", "graph. "]),
|
||||
);
|
||||
test_case(
|
||||
&wrapped_paragraph,
|
||||
Buffer::with_lines(vec!["cool ", "multili", "ne "]),
|
||||
&Buffer::with_lines(["cool ", "multili", "ne "]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_paragraph_with_zero_width_area() {
|
||||
let text = "Hello, world!";
|
||||
let area = Rect::new(0, 0, 0, 3);
|
||||
|
||||
let paragraphs = vec![
|
||||
for paragraph in [
|
||||
Paragraph::new(text),
|
||||
Paragraph::new(text).wrap(Wrap { trim: false }),
|
||||
Paragraph::new(text).wrap(Wrap { trim: true }),
|
||||
];
|
||||
|
||||
let area = Rect::new(0, 0, 0, 3);
|
||||
for paragraph in paragraphs {
|
||||
test_case(¶graph, Buffer::empty(area));
|
||||
test_case(¶graph.clone().scroll((2, 4)), Buffer::empty(area));
|
||||
] {
|
||||
test_case(¶graph, &Buffer::empty(area));
|
||||
test_case(¶graph.clone().scroll((2, 4)), &Buffer::empty(area));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_paragraph_with_zero_height_area() {
|
||||
let text = "Hello, world!";
|
||||
let area = Rect::new(0, 0, 10, 0);
|
||||
|
||||
let paragraphs = vec![
|
||||
for paragraph in [
|
||||
Paragraph::new(text),
|
||||
Paragraph::new(text).wrap(Wrap { trim: false }),
|
||||
Paragraph::new(text).wrap(Wrap { trim: true }),
|
||||
];
|
||||
|
||||
let area = Rect::new(0, 0, 10, 0);
|
||||
for paragraph in paragraphs {
|
||||
test_case(¶graph, Buffer::empty(area));
|
||||
test_case(¶graph.clone().scroll((2, 4)), Buffer::empty(area));
|
||||
] {
|
||||
test_case(¶graph, &Buffer::empty(area));
|
||||
test_case(¶graph.clone().scroll((2, 4)), &Buffer::empty(area));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -864,13 +835,7 @@ mod test {
|
||||
Span::styled("world!", Style::default().fg(Color::Blue)),
|
||||
]);
|
||||
|
||||
let paragraphs = vec![
|
||||
Paragraph::new(text.clone()),
|
||||
Paragraph::new(text.clone()).wrap(Wrap { trim: false }),
|
||||
Paragraph::new(text.clone()).wrap(Wrap { trim: true }),
|
||||
];
|
||||
|
||||
let mut expected_buffer = Buffer::with_lines(vec!["Hello, world!"]);
|
||||
let mut expected_buffer = Buffer::with_lines(["Hello, world!"]);
|
||||
expected_buffer.set_style(
|
||||
Rect::new(0, 0, 7, 1),
|
||||
Style::default().fg(Color::Red).bg(Color::Green),
|
||||
@@ -879,10 +844,15 @@ mod test {
|
||||
Rect::new(7, 0, 6, 1),
|
||||
Style::default().fg(Color::Blue).bg(Color::Green),
|
||||
);
|
||||
for paragraph in paragraphs {
|
||||
|
||||
for paragraph in [
|
||||
Paragraph::new(text.clone()),
|
||||
Paragraph::new(text.clone()).wrap(Wrap { trim: false }),
|
||||
Paragraph::new(text.clone()).wrap(Wrap { trim: true }),
|
||||
] {
|
||||
test_case(
|
||||
¶graph.style(Style::default().bg(Color::Green)),
|
||||
expected_buffer.clone(),
|
||||
&expected_buffer,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -890,22 +860,20 @@ mod test {
|
||||
#[test]
|
||||
fn test_render_paragraph_with_special_characters() {
|
||||
let text = "Hello, <world>!";
|
||||
let paragraphs = vec![
|
||||
for paragraph in [
|
||||
Paragraph::new(text),
|
||||
Paragraph::new(text).wrap(Wrap { trim: false }),
|
||||
Paragraph::new(text).wrap(Wrap { trim: true }),
|
||||
];
|
||||
|
||||
for paragraph in paragraphs {
|
||||
test_case(¶graph, Buffer::with_lines(vec!["Hello, <world>!"]));
|
||||
test_case(¶graph, Buffer::with_lines(vec!["Hello, <world>! "]));
|
||||
] {
|
||||
test_case(¶graph, &Buffer::with_lines(["Hello, <world>!"]));
|
||||
test_case(¶graph, &Buffer::with_lines(["Hello, <world>! "]));
|
||||
test_case(
|
||||
¶graph,
|
||||
Buffer::with_lines(vec!["Hello, <world>! ", " "]),
|
||||
&Buffer::with_lines(["Hello, <world>! ", " "]),
|
||||
);
|
||||
test_case(
|
||||
¶graph,
|
||||
Buffer::with_lines(vec!["Hello, <world>!", " "]),
|
||||
&Buffer::with_lines(["Hello, <world>!", " "]),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -917,27 +885,25 @@ mod test {
|
||||
let wrapped_paragraph = Paragraph::new(text).wrap(Wrap { trim: false });
|
||||
let trimmed_paragraph = Paragraph::new(text).wrap(Wrap { trim: true });
|
||||
|
||||
let paragraphs = vec![&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph];
|
||||
|
||||
for paragraph in paragraphs {
|
||||
test_case(paragraph, Buffer::with_lines(vec!["こんにちは, 世界! 😃"]));
|
||||
for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
|
||||
test_case(paragraph, &Buffer::with_lines(["こんにちは, 世界! 😃"]));
|
||||
test_case(
|
||||
paragraph,
|
||||
Buffer::with_lines(vec!["こんにちは, 世界! 😃 "]),
|
||||
&Buffer::with_lines(["こんにちは, 世界! 😃 "]),
|
||||
);
|
||||
}
|
||||
|
||||
test_case(
|
||||
&truncated_paragraph,
|
||||
Buffer::with_lines(vec!["こんにちは, 世 "]),
|
||||
&Buffer::with_lines(["こんにちは, 世 "]),
|
||||
);
|
||||
test_case(
|
||||
&wrapped_paragraph,
|
||||
Buffer::with_lines(vec!["こんにちは, ", "世界! 😃 "]),
|
||||
&Buffer::with_lines(["こんにちは, ", "世界! 😃 "]),
|
||||
);
|
||||
test_case(
|
||||
&trimmed_paragraph,
|
||||
Buffer::with_lines(vec!["こんにちは, ", "世界! 😃 "]),
|
||||
&Buffer::with_lines(["こんにちは, ", "世界! 😃 "]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1025,13 +991,12 @@ mod test {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 3));
|
||||
paragraph.render(Rect::new(0, 0, 20, 3), &mut buf);
|
||||
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
"┌──────────────────┐",
|
||||
"│Styled text │",
|
||||
"└──────────────────┘",
|
||||
]);
|
||||
expected.set_style(Rect::new(1, 1, 11, 1), Style::default().fg(Color::Green));
|
||||
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use unicode_width::UnicodeWidthStr;
|
||||
use crate::{layout::Alignment, text::StyledGrapheme};
|
||||
|
||||
const NBSP: &str = "\u{00a0}";
|
||||
const ZWSP: &str = "\u{200b}";
|
||||
|
||||
/// A state machine to pack styled symbols into lines.
|
||||
/// Cannot implement it as Iterator since it yields slices of the internal buffer (need streaming
|
||||
@@ -104,8 +105,8 @@ where
|
||||
|
||||
let mut has_seen_non_whitespace = false;
|
||||
for StyledGrapheme { symbol, style } in line_symbols {
|
||||
let symbol_whitespace =
|
||||
symbol.chars().all(&char::is_whitespace) && symbol != NBSP;
|
||||
let symbol_whitespace = symbol == ZWSP
|
||||
|| (symbol.chars().all(&char::is_whitespace) && symbol != NBSP);
|
||||
let symbol_width = symbol.width() as u16;
|
||||
// Ignore characters wider than the total max width
|
||||
if symbol_width > self.max_line_width {
|
||||
@@ -706,4 +707,12 @@ mod test {
|
||||
vec![Alignment::Left, Alignment::Right, Alignment::Center]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_composer_zero_width_white_space() {
|
||||
let width = 3;
|
||||
let line = "foo\u{200b}bar";
|
||||
let (word_wrapper, _, _) = run_composer(Composer::WordWrapper { trim: true }, line, width);
|
||||
assert_eq!(word_wrapper, vec!["foo", "bar"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@ use crate::{prelude::*, symbols::scrollbar::*};
|
||||
/// └─────────── begin
|
||||
/// ```
|
||||
///
|
||||
/// # Important
|
||||
///
|
||||
/// You must specify the [`ScrollbarState::content_length`] before rendering the `Scrollbar`, or
|
||||
/// else the `Scrollbar` will render blank.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
@@ -669,113 +674,107 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("#-", 0, 2, "area 2, position_0")]
|
||||
#[case("-#", 1, 2, "area 2, position_1")]
|
||||
#[case::area_2_position_0("#-", 0, 2)]
|
||||
#[case::area_2_position_1("-#", 1, 2)]
|
||||
fn render_scrollbar_simplest(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
#[case] description: &str,
|
||||
scrollbar_no_arrows: Scrollbar,
|
||||
) {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, expected.width() as u16, 1));
|
||||
let mut state = ScrollbarState::new(content_length).position(position);
|
||||
scrollbar_no_arrows.render(buffer.area, &mut buffer, &mut state);
|
||||
assert_eq!(buffer, Buffer::with_lines(vec![expected]), "{description}",);
|
||||
assert_eq!(buffer, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("#####-----", 0, 10, "position_0")]
|
||||
#[case("-#####----", 1, 10, "position_1")]
|
||||
#[case("-#####----", 2, 10, "position_2")]
|
||||
#[case("--#####---", 3, 10, "position_3")]
|
||||
#[case("--#####---", 4, 10, "position_4")]
|
||||
#[case("---#####--", 5, 10, "position_5")]
|
||||
#[case("---#####--", 6, 10, "position_6")]
|
||||
#[case("----#####-", 7, 10, "position_7")]
|
||||
#[case("----#####-", 8, 10, "position_8")]
|
||||
#[case("-----#####", 9, 10, "position_9")]
|
||||
#[case::position_0("#####-----", 0, 10)]
|
||||
#[case::position_1("-#####----", 1, 10)]
|
||||
#[case::position_2("-#####----", 2, 10)]
|
||||
#[case::position_3("--#####---", 3, 10)]
|
||||
#[case::position_4("--#####---", 4, 10)]
|
||||
#[case::position_5("---#####--", 5, 10)]
|
||||
#[case::position_6("---#####--", 6, 10)]
|
||||
#[case::position_7("----#####-", 7, 10)]
|
||||
#[case::position_8("----#####-", 8, 10)]
|
||||
#[case::position_9("-----#####", 9, 10)]
|
||||
fn render_scrollbar_simple(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
#[case] description: &str,
|
||||
scrollbar_no_arrows: Scrollbar,
|
||||
) {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, expected.width() as u16, 1));
|
||||
let mut state = ScrollbarState::new(content_length).position(position);
|
||||
scrollbar_no_arrows.render(buffer.area, &mut buffer, &mut state);
|
||||
assert_eq!(buffer, Buffer::with_lines(vec![expected]), "{description}",);
|
||||
assert_eq!(buffer, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(" ", 0, 0, "position_0")]
|
||||
#[case::position_0(" ", 0, 0)]
|
||||
fn render_scrollbar_nobar(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
#[case] description: &str,
|
||||
scrollbar_no_arrows: Scrollbar,
|
||||
) {
|
||||
let size = expected.width();
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, size as u16, 1));
|
||||
let mut state = ScrollbarState::new(content_length).position(position);
|
||||
scrollbar_no_arrows.render(buffer.area, &mut buffer, &mut state);
|
||||
assert_eq!(buffer, Buffer::with_lines(vec![expected]), "{description}",);
|
||||
assert_eq!(buffer, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("##########", 0, 1, "fullbar position 0")]
|
||||
#[case("#########-", 0, 2, "almost fullbar position 0")]
|
||||
#[case("-#########", 1, 2, "almost fullbar position 1")]
|
||||
#[case::fullbar_position_0("##########", 0, 1)]
|
||||
#[case::almost_fullbar_position_0("#########-", 0, 2)]
|
||||
#[case::almost_fullbar_position_1("-#########", 1, 2)]
|
||||
fn render_scrollbar_fullbar(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
#[case] description: &str,
|
||||
scrollbar_no_arrows: Scrollbar,
|
||||
) {
|
||||
let size = expected.width();
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, size as u16, 1));
|
||||
let mut state = ScrollbarState::new(content_length).position(position);
|
||||
scrollbar_no_arrows.render(buffer.area, &mut buffer, &mut state);
|
||||
assert_eq!(buffer, Buffer::with_lines(vec![expected]), "{description}",);
|
||||
assert_eq!(buffer, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("#########-", 0, 2, "position_0")]
|
||||
#[case("-#########", 1, 2, "position_1")]
|
||||
#[case::position_0("#########-", 0, 2)]
|
||||
#[case::position_1("-#########", 1, 2)]
|
||||
fn render_scrollbar_almost_fullbar(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
#[case] description: &str,
|
||||
scrollbar_no_arrows: Scrollbar,
|
||||
) {
|
||||
let size = expected.width();
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, size as u16, 1));
|
||||
let mut state = ScrollbarState::new(content_length).position(position);
|
||||
scrollbar_no_arrows.render(buffer.area, &mut buffer, &mut state);
|
||||
assert_eq!(buffer, Buffer::with_lines(vec![expected]), "{description}",);
|
||||
assert_eq!(buffer, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("█████═════", 0, 10, "position_0")]
|
||||
#[case("═█████════", 1, 10, "position_1")]
|
||||
#[case("═█████════", 2, 10, "position_2")]
|
||||
#[case("══█████═══", 3, 10, "position_3")]
|
||||
#[case("══█████═══", 4, 10, "position_4")]
|
||||
#[case("═══█████══", 5, 10, "position_5")]
|
||||
#[case("═══█████══", 6, 10, "position_6")]
|
||||
#[case("════█████═", 7, 10, "position_7")]
|
||||
#[case("════█████═", 8, 10, "position_8")]
|
||||
#[case("═════█████", 9, 10, "position_9")]
|
||||
#[case("═════█████", 100, 10, "position_out_of_bounds")]
|
||||
#[case::position_0("█████═════", 0, 10)]
|
||||
#[case::position_1("═█████════", 1, 10)]
|
||||
#[case::position_2("═█████════", 2, 10)]
|
||||
#[case::position_3("══█████═══", 3, 10)]
|
||||
#[case::position_4("══█████═══", 4, 10)]
|
||||
#[case::position_5("═══█████══", 5, 10)]
|
||||
#[case::position_6("═══█████══", 6, 10)]
|
||||
#[case::position_7("════█████═", 7, 10)]
|
||||
#[case::position_8("════█████═", 8, 10)]
|
||||
#[case::position_9("═════█████", 9, 10)]
|
||||
#[case::position_out_of_bounds("═════█████", 100, 10)]
|
||||
fn render_scrollbar_without_symbols(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
#[case] assertion_message: &str,
|
||||
) {
|
||||
let size = expected.width() as u16;
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, size, 1));
|
||||
@@ -784,30 +783,25 @@ mod tests {
|
||||
.begin_symbol(None)
|
||||
.end_symbol(None)
|
||||
.render(buffer.area, &mut buffer, &mut state);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![expected]),
|
||||
"{assertion_message}",
|
||||
);
|
||||
assert_eq!(buffer, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("█████ ", 0, 10, "position_0")]
|
||||
#[case(" █████ ", 1, 10, "position_1")]
|
||||
#[case(" █████ ", 2, 10, "position_2")]
|
||||
#[case(" █████ ", 3, 10, "position_3")]
|
||||
#[case(" █████ ", 4, 10, "position_4")]
|
||||
#[case(" █████ ", 5, 10, "position_5")]
|
||||
#[case(" █████ ", 6, 10, "position_6")]
|
||||
#[case(" █████ ", 7, 10, "position_7")]
|
||||
#[case(" █████ ", 8, 10, "position_8")]
|
||||
#[case(" █████", 9, 10, "position_9")]
|
||||
#[case(" █████", 100, 10, "position_out_of_bounds")]
|
||||
#[case::position_0("█████ ", 0, 10)]
|
||||
#[case::position_1(" █████ ", 1, 10)]
|
||||
#[case::position_2(" █████ ", 2, 10)]
|
||||
#[case::position_3(" █████ ", 3, 10)]
|
||||
#[case::position_4(" █████ ", 4, 10)]
|
||||
#[case::position_5(" █████ ", 5, 10)]
|
||||
#[case::position_6(" █████ ", 6, 10)]
|
||||
#[case::position_7(" █████ ", 7, 10)]
|
||||
#[case::position_8(" █████ ", 8, 10)]
|
||||
#[case::position_9(" █████", 9, 10)]
|
||||
#[case::position_out_of_bounds(" █████", 100, 10)]
|
||||
fn render_scrollbar_without_track_symbols(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
#[case] assertion_message: &str,
|
||||
) {
|
||||
let size = expected.width() as u16;
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, size, 1));
|
||||
@@ -817,30 +811,25 @@ mod tests {
|
||||
.begin_symbol(None)
|
||||
.end_symbol(None)
|
||||
.render(buffer.area, &mut buffer, &mut state);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![expected]),
|
||||
"{assertion_message}",
|
||||
);
|
||||
assert_eq!(buffer, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("█████-----", 0, 10, "position_0")]
|
||||
#[case("-█████----", 1, 10, "position_1")]
|
||||
#[case("-█████----", 2, 10, "position_2")]
|
||||
#[case("--█████---", 3, 10, "position_3")]
|
||||
#[case("--█████---", 4, 10, "position_4")]
|
||||
#[case("---█████--", 5, 10, "position_5")]
|
||||
#[case("---█████--", 6, 10, "position_6")]
|
||||
#[case("----█████-", 7, 10, "position_7")]
|
||||
#[case("----█████-", 8, 10, "position_8")]
|
||||
#[case("-----█████", 9, 10, "position_9")]
|
||||
#[case("-----█████", 100, 10, "position_out_of_bounds")]
|
||||
#[case::position_0("█████-----", 0, 10)]
|
||||
#[case::position_1("-█████----", 1, 10)]
|
||||
#[case::position_2("-█████----", 2, 10)]
|
||||
#[case::position_3("--█████---", 3, 10)]
|
||||
#[case::position_4("--█████---", 4, 10)]
|
||||
#[case::position_5("---█████--", 5, 10)]
|
||||
#[case::position_6("---█████--", 6, 10)]
|
||||
#[case::position_7("----█████-", 7, 10)]
|
||||
#[case::position_8("----█████-", 8, 10)]
|
||||
#[case::position_9("-----█████", 9, 10)]
|
||||
#[case::position_out_of_bounds("-----█████", 100, 10)]
|
||||
fn render_scrollbar_without_track_symbols_over_content(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
#[case] assertion_message: &str,
|
||||
) {
|
||||
let size = expected.width() as u16;
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, size, 1));
|
||||
@@ -853,32 +842,27 @@ mod tests {
|
||||
.begin_symbol(None)
|
||||
.end_symbol(None)
|
||||
.render(buffer.area, &mut buffer, &mut state);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![expected]),
|
||||
"{assertion_message}",
|
||||
);
|
||||
assert_eq!(buffer, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("<####---->", 0, 10, "position_0")]
|
||||
#[case("<#####--->", 1, 10, "position_1")]
|
||||
#[case("<-####--->", 2, 10, "position_2")]
|
||||
#[case("<-####--->", 3, 10, "position_3")]
|
||||
#[case("<--####-->", 4, 10, "position_4")]
|
||||
#[case("<--####-->", 5, 10, "position_5")]
|
||||
#[case("<---####->", 6, 10, "position_6")]
|
||||
#[case("<---####->", 7, 10, "position_7")]
|
||||
#[case("<---#####>", 8, 10, "position_8")]
|
||||
#[case("<----####>", 9, 10, "position_9")]
|
||||
#[case("<----####>", 10, 10, "position_one_out_of_bounds")]
|
||||
#[case("<----####>", 15, 10, "position_few_out_of_bounds")]
|
||||
#[case("<----####>", 500, 10, "position_very_many_out_of_bounds")]
|
||||
#[case::position_0("<####---->", 0, 10)]
|
||||
#[case::position_1("<#####--->", 1, 10)]
|
||||
#[case::position_2("<-####--->", 2, 10)]
|
||||
#[case::position_3("<-####--->", 3, 10)]
|
||||
#[case::position_4("<--####-->", 4, 10)]
|
||||
#[case::position_5("<--####-->", 5, 10)]
|
||||
#[case::position_6("<---####->", 6, 10)]
|
||||
#[case::position_7("<---####->", 7, 10)]
|
||||
#[case::position_8("<---#####>", 8, 10)]
|
||||
#[case::position_9("<----####>", 9, 10)]
|
||||
#[case::position_one_out_of_bounds("<----####>", 10, 10)]
|
||||
#[case::position_few_out_of_bounds("<----####>", 15, 10)]
|
||||
#[case::position_very_many_out_of_bounds("<----####>", 500, 10)]
|
||||
fn render_scrollbar_with_symbols(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
#[case] assertion_message: &str,
|
||||
) {
|
||||
let size = expected.width() as u16;
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, size, 1));
|
||||
@@ -889,30 +873,25 @@ mod tests {
|
||||
.track_symbol(Some("-"))
|
||||
.thumb_symbol("#")
|
||||
.render(buffer.area, &mut buffer, &mut state);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![expected]),
|
||||
"{assertion_message}",
|
||||
);
|
||||
assert_eq!(buffer, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("█████═════", 0, 10, "position_0")]
|
||||
#[case("═█████════", 1, 10, "position_1")]
|
||||
#[case("═█████════", 2, 10, "position_2")]
|
||||
#[case("══█████═══", 3, 10, "position_3")]
|
||||
#[case("══█████═══", 4, 10, "position_4")]
|
||||
#[case("═══█████══", 5, 10, "position_5")]
|
||||
#[case("═══█████══", 6, 10, "position_6")]
|
||||
#[case("════█████═", 7, 10, "position_7")]
|
||||
#[case("════█████═", 8, 10, "position_8")]
|
||||
#[case("═════█████", 9, 10, "position_9")]
|
||||
#[case("═════█████", 100, 10, "position_out_of_bounds")]
|
||||
#[case::position_0("█████═════", 0, 10)]
|
||||
#[case::position_1("═█████════", 1, 10)]
|
||||
#[case::position_2("═█████════", 2, 10)]
|
||||
#[case::position_3("══█████═══", 3, 10)]
|
||||
#[case::position_4("══█████═══", 4, 10)]
|
||||
#[case::position_5("═══█████══", 5, 10)]
|
||||
#[case::position_6("═══█████══", 6, 10)]
|
||||
#[case::position_7("════█████═", 7, 10)]
|
||||
#[case::position_8("════█████═", 8, 10)]
|
||||
#[case::position_9("═════█████", 9, 10)]
|
||||
#[case::position_out_of_bounds("═════█████", 100, 10)]
|
||||
fn render_scrollbar_horizontal_bottom(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
#[case] description: &str,
|
||||
) {
|
||||
let size = expected.width() as u16;
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, size, 2));
|
||||
@@ -922,30 +901,25 @@ mod tests {
|
||||
.end_symbol(None)
|
||||
.render(buffer.area, &mut buffer, &mut state);
|
||||
let empty_string = " ".repeat(size as usize);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![&empty_string, expected]),
|
||||
"{description}",
|
||||
);
|
||||
assert_eq!(buffer, Buffer::with_lines([&empty_string, expected]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("█████═════", 0, 10, "position_0")]
|
||||
#[case("═█████════", 1, 10, "position_1")]
|
||||
#[case("═█████════", 2, 10, "position_2")]
|
||||
#[case("══█████═══", 3, 10, "position_3")]
|
||||
#[case("══█████═══", 4, 10, "position_4")]
|
||||
#[case("═══█████══", 5, 10, "position_5")]
|
||||
#[case("═══█████══", 6, 10, "position_6")]
|
||||
#[case("════█████═", 7, 10, "position_7")]
|
||||
#[case("════█████═", 8, 10, "position_8")]
|
||||
#[case("═════█████", 9, 10, "position_9")]
|
||||
#[case("═════█████", 100, 10, "position_out_of_bounds")]
|
||||
#[case::position_0("█████═════", 0, 10)]
|
||||
#[case::position_1("═█████════", 1, 10)]
|
||||
#[case::position_2("═█████════", 2, 10)]
|
||||
#[case::position_3("══█████═══", 3, 10)]
|
||||
#[case::position_4("══█████═══", 4, 10)]
|
||||
#[case::position_5("═══█████══", 5, 10)]
|
||||
#[case::position_6("═══█████══", 6, 10)]
|
||||
#[case::position_7("════█████═", 7, 10)]
|
||||
#[case::position_8("════█████═", 8, 10)]
|
||||
#[case::position_9("═════█████", 9, 10)]
|
||||
#[case::position_out_of_bounds("═════█████", 100, 10)]
|
||||
fn render_scrollbar_horizontal_top(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
#[case] description: &str,
|
||||
) {
|
||||
let size = expected.width() as u16;
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, size, 2));
|
||||
@@ -955,30 +929,25 @@ mod tests {
|
||||
.end_symbol(None)
|
||||
.render(buffer.area, &mut buffer, &mut state);
|
||||
let empty_string = " ".repeat(size as usize);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![expected, &empty_string]),
|
||||
"{description}",
|
||||
);
|
||||
assert_eq!(buffer, Buffer::with_lines([expected, &empty_string]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("<####---->", 0, 10, "position_0")]
|
||||
#[case("<#####--->", 1, 10, "position_1")]
|
||||
#[case("<-####--->", 2, 10, "position_2")]
|
||||
#[case("<-####--->", 3, 10, "position_3")]
|
||||
#[case("<--####-->", 4, 10, "position_4")]
|
||||
#[case("<--####-->", 5, 10, "position_5")]
|
||||
#[case("<---####->", 6, 10, "position_6")]
|
||||
#[case("<---####->", 7, 10, "position_7")]
|
||||
#[case("<---#####>", 8, 10, "position_8")]
|
||||
#[case("<----####>", 9, 10, "position_9")]
|
||||
#[case("<----####>", 10, 10, "position_one_out_of_bounds")]
|
||||
#[case::position_0("<####---->", 0, 10)]
|
||||
#[case::position_1("<#####--->", 1, 10)]
|
||||
#[case::position_2("<-####--->", 2, 10)]
|
||||
#[case::position_3("<-####--->", 3, 10)]
|
||||
#[case::position_4("<--####-->", 4, 10)]
|
||||
#[case::position_5("<--####-->", 5, 10)]
|
||||
#[case::position_6("<---####->", 6, 10)]
|
||||
#[case::position_7("<---####->", 7, 10)]
|
||||
#[case::position_8("<---#####>", 8, 10)]
|
||||
#[case::position_9("<----####>", 9, 10)]
|
||||
#[case::position_one_out_of_bounds("<----####>", 10, 10)]
|
||||
fn render_scrollbar_vertical_left(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
#[case] description: &str,
|
||||
) {
|
||||
let size = expected.width() as u16;
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, size));
|
||||
@@ -990,26 +959,25 @@ mod tests {
|
||||
.thumb_symbol("#")
|
||||
.render(buffer.area, &mut buffer, &mut state);
|
||||
let bar = expected.chars().map(|c| format!("{c} "));
|
||||
assert_eq!(buffer, Buffer::with_lines(bar), "{description}");
|
||||
assert_eq!(buffer, Buffer::with_lines(bar));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("<####---->", 0, 10, "position_0")]
|
||||
#[case("<#####--->", 1, 10, "position_1")]
|
||||
#[case("<-####--->", 2, 10, "position_2")]
|
||||
#[case("<-####--->", 3, 10, "position_3")]
|
||||
#[case("<--####-->", 4, 10, "position_4")]
|
||||
#[case("<--####-->", 5, 10, "position_5")]
|
||||
#[case("<---####->", 6, 10, "position_6")]
|
||||
#[case("<---####->", 7, 10, "position_7")]
|
||||
#[case("<---#####>", 8, 10, "position_8")]
|
||||
#[case("<----####>", 9, 10, "position_9")]
|
||||
#[case("<----####>", 10, 10, "position_one_out_of_bounds")]
|
||||
#[case::position_0("<####---->", 0, 10)]
|
||||
#[case::position_1("<#####--->", 1, 10)]
|
||||
#[case::position_2("<-####--->", 2, 10)]
|
||||
#[case::position_3("<-####--->", 3, 10)]
|
||||
#[case::position_4("<--####-->", 4, 10)]
|
||||
#[case::position_5("<--####-->", 5, 10)]
|
||||
#[case::position_6("<---####->", 6, 10)]
|
||||
#[case::position_7("<---####->", 7, 10)]
|
||||
#[case::position_8("<---#####>", 8, 10)]
|
||||
#[case::position_9("<----####>", 9, 10)]
|
||||
#[case::position_one_out_of_bounds("<----####>", 10, 10)]
|
||||
fn render_scrollbar_vertical_rightl(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
#[case] description: &str,
|
||||
) {
|
||||
let size = expected.width() as u16;
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, size));
|
||||
@@ -1021,26 +989,25 @@ mod tests {
|
||||
.thumb_symbol("#")
|
||||
.render(buffer.area, &mut buffer, &mut state);
|
||||
let bar = expected.chars().map(|c| format!(" {c}"));
|
||||
assert_eq!(buffer, Buffer::with_lines(bar), "{description}");
|
||||
assert_eq!(buffer, Buffer::with_lines(bar));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("##--------", 0, 10, "position_0")]
|
||||
#[case("-##-------", 1, 10, "position_1")]
|
||||
#[case("--##------", 2, 10, "position_2")]
|
||||
#[case("---##-----", 3, 10, "position_3")]
|
||||
#[case("----#-----", 4, 10, "position_4")]
|
||||
#[case("-----#----", 5, 10, "position_5")]
|
||||
#[case("-----##---", 6, 10, "position_6")]
|
||||
#[case("------##--", 7, 10, "position_7")]
|
||||
#[case("-------##-", 8, 10, "position_8")]
|
||||
#[case("--------##", 9, 10, "position_9")]
|
||||
#[case("--------##", 10, 10, "position_one_out_of_bounds")]
|
||||
#[case::position_0("##--------", 0, 10)]
|
||||
#[case::position_1("-##-------", 1, 10)]
|
||||
#[case::position_2("--##------", 2, 10)]
|
||||
#[case::position_3("---##-----", 3, 10)]
|
||||
#[case::position_4("----#-----", 4, 10)]
|
||||
#[case::position_5("-----#----", 5, 10)]
|
||||
#[case::position_6("-----##---", 6, 10)]
|
||||
#[case::position_7("------##--", 7, 10)]
|
||||
#[case::position_8("-------##-", 8, 10)]
|
||||
#[case::position_9("--------##", 9, 10)]
|
||||
#[case::position_one_out_of_bounds("--------##", 10, 10)]
|
||||
fn custom_viewport_length(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
#[case] description: &str,
|
||||
scrollbar_no_arrows: Scrollbar,
|
||||
) {
|
||||
let size = expected.width() as u16;
|
||||
@@ -1049,28 +1016,27 @@ mod tests {
|
||||
.position(position)
|
||||
.viewport_content_length(2);
|
||||
scrollbar_no_arrows.render(buffer.area, &mut buffer, &mut state);
|
||||
assert_eq!(buffer, Buffer::with_lines(vec![expected]), "{description}");
|
||||
assert_eq!(buffer, Buffer::with_lines([expected]));
|
||||
}
|
||||
|
||||
/// Fixes <https://github.com/ratatui-org/ratatui/pull/959> which was a bug that would not
|
||||
/// render a thumb when the viewport was very small in comparison to the content length.
|
||||
#[rstest]
|
||||
#[case("#----", 0, 100, "position_0")]
|
||||
#[case("#----", 10, 100, "position_10")]
|
||||
#[case("-#---", 20, 100, "position_20")]
|
||||
#[case("-#---", 30, 100, "position_30")]
|
||||
#[case("--#--", 40, 100, "position_40")]
|
||||
#[case("--#--", 50, 100, "position_50")]
|
||||
#[case("---#-", 60, 100, "position_60")]
|
||||
#[case("---#-", 70, 100, "position_70")]
|
||||
#[case("----#", 80, 100, "position_80")]
|
||||
#[case("----#", 90, 100, "position_90")]
|
||||
#[case("----#", 100, 100, "position_one_out_of_bounds")]
|
||||
#[case::position_0("#----", 0, 100)]
|
||||
#[case::position_10("#----", 10, 100)]
|
||||
#[case::position_20("-#---", 20, 100)]
|
||||
#[case::position_30("-#---", 30, 100)]
|
||||
#[case::position_40("--#--", 40, 100)]
|
||||
#[case::position_50("--#--", 50, 100)]
|
||||
#[case::position_60("---#-", 60, 100)]
|
||||
#[case::position_70("---#-", 70, 100)]
|
||||
#[case::position_80("----#", 80, 100)]
|
||||
#[case::position_90("----#", 90, 100)]
|
||||
#[case::position_one_out_of_bounds("----#", 100, 100)]
|
||||
fn thumb_visible_on_very_small_track(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
#[case] description: &str,
|
||||
scrollbar_no_arrows: Scrollbar,
|
||||
) {
|
||||
let size = expected.width() as u16;
|
||||
@@ -1079,6 +1045,6 @@ mod tests {
|
||||
.position(position)
|
||||
.viewport_content_length(2);
|
||||
scrollbar_no_arrows.render(buffer.area, &mut buffer, &mut state);
|
||||
assert_eq!(buffer, Buffer::with_lines(vec![expected]), "{description}");
|
||||
assert_eq!(buffer, Buffer::with_lines([expected]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ use crate::{prelude::*, widgets::Block};
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
///
|
||||
/// Sparkline::default()
|
||||
/// .block(Block::default().title("Sparkline").borders(Borders::ALL))
|
||||
/// .block(Block::bordered().title("Sparkline"))
|
||||
/// .data(&[0, 2, 3, 4, 1, 4, 10])
|
||||
/// .max(5)
|
||||
/// .direction(RenderDirection::RightToLeft)
|
||||
@@ -225,7 +225,7 @@ mod tests {
|
||||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
use crate::{assert_buffer_eq, buffer::Cell};
|
||||
use crate::buffer::Cell;
|
||||
|
||||
#[test]
|
||||
fn render_direction_to_string() {
|
||||
@@ -264,21 +264,21 @@ mod tests {
|
||||
fn it_does_not_panic_if_max_is_zero() {
|
||||
let widget = Sparkline::default().data(&[0, 0, 0]);
|
||||
let buffer = render(widget, 6);
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" xxx"]));
|
||||
assert_eq!(buffer, Buffer::with_lines([" xxx"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_does_not_panic_if_max_is_set_to_zero() {
|
||||
let widget = Sparkline::default().data(&[0, 1, 2]).max(0);
|
||||
let buffer = render(widget, 6);
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" xxx"]));
|
||||
assert_eq!(buffer, Buffer::with_lines([" xxx"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_draws() {
|
||||
let widget = Sparkline::default().data(&[0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
let buffer = render(widget, 12);
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" ▁▂▃▄▅▆▇█xxx"]));
|
||||
assert_eq!(buffer, Buffer::with_lines([" ▁▂▃▄▅▆▇█xxx"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -287,7 +287,7 @@ mod tests {
|
||||
.data(&[0, 1, 2, 3, 4, 5, 6, 7, 8])
|
||||
.direction(RenderDirection::LeftToRight);
|
||||
let buffer = render(widget, 12);
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" ▁▂▃▄▅▆▇█xxx"]));
|
||||
assert_eq!(buffer, Buffer::with_lines([" ▁▂▃▄▅▆▇█xxx"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -296,7 +296,7 @@ mod tests {
|
||||
.data(&[0, 1, 2, 3, 4, 5, 6, 7, 8])
|
||||
.direction(RenderDirection::RightToLeft);
|
||||
let buffer = render(widget, 12);
|
||||
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["xxx█▇▆▅▄▃▂▁ "]));
|
||||
assert_eq!(buffer, Buffer::with_lines(["xxx█▇▆▅▄▃▂▁ "]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -49,6 +49,7 @@
|
||||
pub struct TableState {
|
||||
pub(crate) offset: usize,
|
||||
pub(crate) selected: Option<usize>,
|
||||
pub(crate) marked: Vec<usize>,
|
||||
}
|
||||
|
||||
impl TableState {
|
||||
@@ -60,8 +61,12 @@ impl TableState {
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let state = TableState::new();
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
offset: 0,
|
||||
selected: None,
|
||||
marked: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the index of the first row to be displayed
|
||||
@@ -172,6 +177,78 @@ impl TableState {
|
||||
self.offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the index of the row as marked
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let mut state = TableState::default();
|
||||
/// state.mark(1);
|
||||
/// ```
|
||||
pub fn mark(&mut self, index: usize) {
|
||||
if !self.marked.contains(&index) {
|
||||
self.marked.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the index of the row as unmarked
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let mut state = TableState::default();
|
||||
/// state.unmark(1);
|
||||
/// ```
|
||||
pub fn unmark(&mut self, index: usize) {
|
||||
self.marked.retain(|i| *i != index);
|
||||
}
|
||||
|
||||
/// Toggles the index of the row as marked or unmarked
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let mut state = TableState::default();
|
||||
/// state.toggle_mark(1);
|
||||
/// ```
|
||||
pub fn toggle_mark(&mut self, index: usize) {
|
||||
if self.marked.contains(&index) {
|
||||
self.unmark(index);
|
||||
} else {
|
||||
self.mark(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a iterator of all marked rows
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # use itertools::Itertools;
|
||||
/// let mut state = TableState::default();
|
||||
/// state.marked().contains(&1);
|
||||
/// ```
|
||||
pub fn marked(&self) -> std::slice::Iter<'_, usize> {
|
||||
self.marked.iter()
|
||||
}
|
||||
|
||||
/// Clears all marks from all rows
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let mut state = TableState::default();
|
||||
/// state.clear_marks();
|
||||
/// ```
|
||||
pub fn clear_marks(&mut self) {
|
||||
self.marked.drain(..);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -17,7 +17,7 @@ const DEFAULT_HIGHLIGHT_STYLE: Style = Style::new().add_modifier(Modifier::REVER
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
///
|
||||
/// Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"])
|
||||
/// .block(Block::default().title("Tabs").borders(Borders::ALL))
|
||||
/// .block(Block::bordered().title("Tabs"))
|
||||
/// .style(Style::default().white())
|
||||
/// .highlight_style(Style::default().yellow())
|
||||
/// .select(2)
|
||||
@@ -333,7 +333,6 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{assert_buffer_eq, widgets::Borders};
|
||||
|
||||
#[test]
|
||||
fn new() {
|
||||
@@ -379,60 +378,61 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
fn render(tabs: Tabs, area: Rect) -> Buffer {
|
||||
#[track_caller]
|
||||
fn test_case(tabs: Tabs, area: Rect, expected: &Buffer) {
|
||||
let mut buffer = Buffer::empty(area);
|
||||
tabs.render(area, &mut buffer);
|
||||
buffer
|
||||
assert_eq!(&buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_default() {
|
||||
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]);
|
||||
let mut expected = Buffer::with_lines(vec![" Tab1 │ Tab2 │ Tab3 │ Tab4 "]);
|
||||
let mut expected = Buffer::with_lines([" Tab1 │ Tab2 │ Tab3 │ Tab4 "]);
|
||||
// first tab selected
|
||||
expected.set_style(Rect::new(1, 0, 4, 1), DEFAULT_HIGHLIGHT_STYLE);
|
||||
assert_buffer_eq!(render(tabs, Rect::new(0, 0, 30, 1)), expected);
|
||||
test_case(tabs, Rect::new(0, 0, 30, 1), &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_no_padding() {
|
||||
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]).padding("", "");
|
||||
let mut expected = Buffer::with_lines(vec!["Tab1│Tab2│Tab3│Tab4 "]);
|
||||
let mut expected = Buffer::with_lines(["Tab1│Tab2│Tab3│Tab4 "]);
|
||||
// first tab selected
|
||||
expected.set_style(Rect::new(0, 0, 4, 1), DEFAULT_HIGHLIGHT_STYLE);
|
||||
assert_buffer_eq!(render(tabs, Rect::new(0, 0, 30, 1)), expected);
|
||||
test_case(tabs, Rect::new(0, 0, 30, 1), &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_more_padding() {
|
||||
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]).padding("---", "++");
|
||||
let mut expected = Buffer::with_lines(vec!["---Tab1++│---Tab2++│---Tab3++│"]);
|
||||
let mut expected = Buffer::with_lines(["---Tab1++│---Tab2++│---Tab3++│"]);
|
||||
// first tab selected
|
||||
expected.set_style(Rect::new(3, 0, 4, 1), DEFAULT_HIGHLIGHT_STYLE);
|
||||
assert_buffer_eq!(render(tabs, Rect::new(0, 0, 30, 1)), expected);
|
||||
test_case(tabs, Rect::new(0, 0, 30, 1), &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_with_block() {
|
||||
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"])
|
||||
.block(Block::default().title("Tabs").borders(Borders::ALL));
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let tabs =
|
||||
Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]).block(Block::bordered().title("Tabs"));
|
||||
let mut expected = Buffer::with_lines([
|
||||
"┌Tabs────────────────────────┐",
|
||||
"│ Tab1 │ Tab2 │ Tab3 │ Tab4 │",
|
||||
"└────────────────────────────┘",
|
||||
]);
|
||||
// first tab selected
|
||||
expected.set_style(Rect::new(2, 1, 4, 1), DEFAULT_HIGHLIGHT_STYLE);
|
||||
assert_buffer_eq!(render(tabs, Rect::new(0, 0, 30, 3)), expected);
|
||||
test_case(tabs, Rect::new(0, 0, 30, 3), &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_style() {
|
||||
let tabs =
|
||||
Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]).style(Style::default().fg(Color::Red));
|
||||
let mut expected = Buffer::with_lines(vec![" Tab1 │ Tab2 │ Tab3 │ Tab4 ".red()]);
|
||||
let mut expected = Buffer::with_lines([" Tab1 │ Tab2 │ Tab3 │ Tab4 ".red()]);
|
||||
expected.set_style(Rect::new(1, 0, 4, 1), DEFAULT_HIGHLIGHT_STYLE.red());
|
||||
assert_buffer_eq!(render(tabs, Rect::new(0, 0, 30, 1)), expected);
|
||||
test_case(tabs, Rect::new(0, 0, 30, 1), &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -440,40 +440,32 @@ mod tests {
|
||||
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]);
|
||||
|
||||
// first tab selected
|
||||
assert_buffer_eq!(
|
||||
render(tabs.clone().select(0), Rect::new(0, 0, 30, 1)),
|
||||
Buffer::with_lines(vec![Line::from(vec![
|
||||
" ".into(),
|
||||
"Tab1".reversed(),
|
||||
" │ Tab2 │ Tab3 │ Tab4 ".into(),
|
||||
])])
|
||||
);
|
||||
let expected = Buffer::with_lines([Line::from(vec![
|
||||
" ".into(),
|
||||
"Tab1".reversed(),
|
||||
" │ Tab2 │ Tab3 │ Tab4 ".into(),
|
||||
])]);
|
||||
test_case(tabs.clone().select(0), Rect::new(0, 0, 30, 1), &expected);
|
||||
|
||||
// second tab selected
|
||||
assert_buffer_eq!(
|
||||
render(tabs.clone().select(1), Rect::new(0, 0, 30, 1)),
|
||||
Buffer::with_lines(vec![Line::from(vec![
|
||||
" Tab1 │ ".into(),
|
||||
"Tab2".reversed(),
|
||||
" │ Tab3 │ Tab4 ".into(),
|
||||
])])
|
||||
);
|
||||
let expected = Buffer::with_lines([Line::from(vec![
|
||||
" Tab1 │ ".into(),
|
||||
"Tab2".reversed(),
|
||||
" │ Tab3 │ Tab4 ".into(),
|
||||
])]);
|
||||
test_case(tabs.clone().select(1), Rect::new(0, 0, 30, 1), &expected);
|
||||
|
||||
// last tab selected
|
||||
assert_buffer_eq!(
|
||||
render(tabs.clone().select(3), Rect::new(0, 0, 30, 1)),
|
||||
Buffer::with_lines(vec![Line::from(vec![
|
||||
" Tab1 │ Tab2 │ Tab3 │ ".into(),
|
||||
"Tab4".reversed(),
|
||||
" ".into(),
|
||||
])])
|
||||
);
|
||||
let expected = Buffer::with_lines([Line::from(vec![
|
||||
" Tab1 │ Tab2 │ Tab3 │ ".into(),
|
||||
"Tab4".reversed(),
|
||||
" ".into(),
|
||||
])]);
|
||||
test_case(tabs.clone().select(3), Rect::new(0, 0, 30, 1), &expected);
|
||||
|
||||
// out of bounds selects no tab
|
||||
assert_buffer_eq!(
|
||||
render(tabs.clone().select(4), Rect::new(0, 0, 30, 1)),
|
||||
Buffer::with_lines(vec![" Tab1 │ Tab2 │ Tab3 │ Tab4 "])
|
||||
);
|
||||
let expected = Buffer::with_lines([" Tab1 │ Tab2 │ Tab3 │ Tab4 "]);
|
||||
test_case(tabs.clone().select(4), Rect::new(0, 0, 30, 1), &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -482,23 +474,21 @@ mod tests {
|
||||
.style(Style::new().red())
|
||||
.highlight_style(Style::new().underlined())
|
||||
.select(0);
|
||||
assert_buffer_eq!(
|
||||
render(tabs, Rect::new(0, 0, 30, 1)),
|
||||
Buffer::with_lines(vec![Line::from(vec![
|
||||
" ".red(),
|
||||
"Tab1".red().underlined(),
|
||||
" │ Tab2 │ Tab3 │ Tab4 ".red(),
|
||||
])])
|
||||
);
|
||||
let expected = Buffer::with_lines([Line::from(vec![
|
||||
" ".red(),
|
||||
"Tab1".red().underlined(),
|
||||
" │ Tab2 │ Tab3 │ Tab4 ".red(),
|
||||
])]);
|
||||
test_case(tabs, Rect::new(0, 0, 30, 1), &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_divider() {
|
||||
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]).divider("--");
|
||||
let mut expected = Buffer::with_lines(vec![" Tab1 -- Tab2 -- Tab3 -- Tab4 "]);
|
||||
let mut expected = Buffer::with_lines([" Tab1 -- Tab2 -- Tab3 -- Tab4 "]);
|
||||
// first tab selected
|
||||
expected.set_style(Rect::new(1, 0, 4, 1), DEFAULT_HIGHLIGHT_STYLE);
|
||||
assert_buffer_eq!(render(tabs, Rect::new(0, 0, 30, 1)), expected);
|
||||
test_case(tabs, Rect::new(0, 0, 30, 1), &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
// not too happy about the redundancy in these tests,
|
||||
// but if that helps readability then it's ok i guess /shrug
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
use ratatui::{backend::TestBackend, prelude::*, widgets::*};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
@@ -42,7 +43,11 @@ impl AppState {
|
||||
|
||||
/// Renders the list to a `TestBackend` and asserts that the result matches the expected buffer.
|
||||
#[track_caller]
|
||||
fn assert_buffer(state: &mut AppState, expected: &Buffer) {
|
||||
fn assert_buffer<'line, Lines>(state: &mut AppState, expected: Lines)
|
||||
where
|
||||
Lines: IntoIterator,
|
||||
Lines::Item: Into<Line<'line>>,
|
||||
{
|
||||
let backend = TestBackend::new(21, 5);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
terminal
|
||||
@@ -62,7 +67,7 @@ fn assert_buffer(state: &mut AppState, expected: &Buffer) {
|
||||
.split(f.size());
|
||||
let list = List::new(items)
|
||||
.highlight_symbol(">>")
|
||||
.block(Block::default().borders(Borders::RIGHT));
|
||||
.block(Block::new().borders(Borders::RIGHT));
|
||||
f.render_stateful_widget(list, layout[0], &mut state.list);
|
||||
|
||||
let table = Table::new(
|
||||
@@ -76,7 +81,7 @@ fn assert_buffer(state: &mut AppState, expected: &Buffer) {
|
||||
f.render_stateful_widget(scrollbar, layout[2], &mut state.scrollbar);
|
||||
})
|
||||
.unwrap();
|
||||
terminal.backend().assert_buffer(expected);
|
||||
terminal.backend().assert_buffer_lines(expected);
|
||||
}
|
||||
|
||||
const DEFAULT_STATE_BUFFER: [&str; 5] = [
|
||||
@@ -94,7 +99,8 @@ const DEFAULT_STATE_REPR: &str = r#"{
|
||||
},
|
||||
"table": {
|
||||
"offset": 0,
|
||||
"selected": null
|
||||
"selected": null,
|
||||
"marked": []
|
||||
},
|
||||
"scrollbar": {
|
||||
"content_length": 10,
|
||||
@@ -106,19 +112,15 @@ const DEFAULT_STATE_REPR: &str = r#"{
|
||||
#[test]
|
||||
fn default_state_serialize() {
|
||||
let mut state = AppState::default();
|
||||
|
||||
let expected = Buffer::with_lines(DEFAULT_STATE_BUFFER);
|
||||
assert_buffer(&mut state, &expected);
|
||||
|
||||
assert_buffer(&mut state, DEFAULT_STATE_BUFFER);
|
||||
let state = serde_json::to_string_pretty(&state).unwrap();
|
||||
assert_eq!(state, DEFAULT_STATE_REPR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_state_deserialize() {
|
||||
let expected = Buffer::with_lines(DEFAULT_STATE_BUFFER);
|
||||
let mut state: AppState = serde_json::from_str(DEFAULT_STATE_REPR).unwrap();
|
||||
assert_buffer(&mut state, &expected);
|
||||
assert_buffer(&mut state, DEFAULT_STATE_BUFFER);
|
||||
}
|
||||
|
||||
const SELECTED_STATE_BUFFER: [&str; 5] = [
|
||||
@@ -135,7 +137,8 @@ const SELECTED_STATE_REPR: &str = r#"{
|
||||
},
|
||||
"table": {
|
||||
"offset": 0,
|
||||
"selected": 1
|
||||
"selected": 1,
|
||||
"marked": []
|
||||
},
|
||||
"scrollbar": {
|
||||
"content_length": 10,
|
||||
@@ -148,19 +151,15 @@ const SELECTED_STATE_REPR: &str = r#"{
|
||||
fn selected_state_serialize() {
|
||||
let mut state = AppState::default();
|
||||
state.select(1);
|
||||
|
||||
let expected = Buffer::with_lines(SELECTED_STATE_BUFFER);
|
||||
assert_buffer(&mut state, &expected);
|
||||
|
||||
assert_buffer(&mut state, SELECTED_STATE_BUFFER);
|
||||
let state = serde_json::to_string_pretty(&state).unwrap();
|
||||
assert_eq!(state, SELECTED_STATE_REPR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_state_deserialize() {
|
||||
let expected = Buffer::with_lines(SELECTED_STATE_BUFFER);
|
||||
let mut state: AppState = serde_json::from_str(SELECTED_STATE_REPR).unwrap();
|
||||
assert_buffer(&mut state, &expected);
|
||||
assert_buffer(&mut state, SELECTED_STATE_BUFFER);
|
||||
}
|
||||
|
||||
const SCROLLED_STATE_BUFFER: [&str; 5] = [
|
||||
@@ -178,7 +177,8 @@ const SCROLLED_STATE_REPR: &str = r#"{
|
||||
},
|
||||
"table": {
|
||||
"offset": 4,
|
||||
"selected": 8
|
||||
"selected": 8,
|
||||
"marked": []
|
||||
},
|
||||
"scrollbar": {
|
||||
"content_length": 10,
|
||||
@@ -191,17 +191,13 @@ const SCROLLED_STATE_REPR: &str = r#"{
|
||||
fn scrolled_state_serialize() {
|
||||
let mut state = AppState::default();
|
||||
state.select(8);
|
||||
|
||||
let expected = Buffer::with_lines(SCROLLED_STATE_BUFFER);
|
||||
assert_buffer(&mut state, &expected);
|
||||
|
||||
assert_buffer(&mut state, SCROLLED_STATE_BUFFER);
|
||||
let state = serde_json::to_string_pretty(&state).unwrap();
|
||||
assert_eq!(state, SCROLLED_STATE_REPR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scrolled_state_deserialize() {
|
||||
let expected = Buffer::with_lines(SCROLLED_STATE_BUFFER);
|
||||
let mut state: AppState = serde_json::from_str(SCROLLED_STATE_REPR).unwrap();
|
||||
assert_buffer(&mut state, &expected);
|
||||
assert_buffer(&mut state, SCROLLED_STATE_BUFFER);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Color, Style, Stylize},
|
||||
widgets::{BarChart, Block, Borders, Paragraph},
|
||||
widgets::{BarChart, Block, Paragraph},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ fn barchart_can_be_stylized() {
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
" ██ ",
|
||||
" ▅▅ ██ ",
|
||||
"▂▂ ██ ██ ",
|
||||
@@ -55,17 +55,15 @@ fn barchart_can_be_stylized() {
|
||||
expected.get_mut(x * 3, 4).set_fg(Color::Blue);
|
||||
expected.get_mut(x * 3 + 1, 4).set_fg(Color::Reset);
|
||||
}
|
||||
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_can_be_stylized() -> io::Result<()> {
|
||||
let block = Block::default()
|
||||
let block = Block::bordered()
|
||||
.title("Title".light_blue())
|
||||
.on_cyan()
|
||||
.cyan()
|
||||
.borders(Borders::ALL);
|
||||
.cyan();
|
||||
|
||||
let area = Rect::new(0, 0, 8, 3);
|
||||
let mut terminal = Terminal::new(TestBackend::new(11, 4))?;
|
||||
@@ -73,7 +71,8 @@ fn block_can_be_stylized() -> io::Result<()> {
|
||||
f.render_widget(block, area);
|
||||
})?;
|
||||
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
#[rustfmt::skip]
|
||||
let mut expected = Buffer::with_lines([
|
||||
"┌Title─┐ ",
|
||||
"│ │ ",
|
||||
"└──────┘ ",
|
||||
@@ -90,7 +89,6 @@ fn block_can_be_stylized() -> io::Result<()> {
|
||||
for x in 1..=5 {
|
||||
expected.get_mut(x, 0).set_fg(Color::LightBlue);
|
||||
}
|
||||
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
Ok(())
|
||||
}
|
||||
@@ -105,7 +103,7 @@ fn paragraph_can_be_stylized() -> io::Result<()> {
|
||||
f.render_widget(paragraph, area);
|
||||
})?;
|
||||
|
||||
let mut expected = Buffer::with_lines(vec!["Text "]);
|
||||
let mut expected = Buffer::with_lines(["Text "]);
|
||||
for x in 0..4 {
|
||||
expected.get_mut(x, 0).set_fg(Color::Cyan);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use std::error::Error;
|
||||
|
||||
use ratatui::{
|
||||
assert_buffer_eq,
|
||||
backend::{Backend, TestBackend},
|
||||
layout::Rect,
|
||||
prelude::Buffer,
|
||||
widgets::{Paragraph, Widget},
|
||||
Terminal, TerminalOptions, Viewport,
|
||||
};
|
||||
@@ -105,16 +103,13 @@ fn terminal_insert_before_moves_viewport() -> Result<(), Box<dyn Error>> {
|
||||
f.render_widget(paragraph, f.size());
|
||||
})?;
|
||||
|
||||
assert_buffer_eq!(
|
||||
terminal.backend().buffer().clone(),
|
||||
Buffer::with_lines(vec![
|
||||
"------ Line 1 ------",
|
||||
"------ Line 2 ------",
|
||||
"[---- Viewport ----]",
|
||||
" ",
|
||||
" ",
|
||||
])
|
||||
);
|
||||
terminal.backend().assert_buffer_lines([
|
||||
"------ Line 1 ------",
|
||||
"------ Line 2 ------",
|
||||
"[---- Viewport ----]",
|
||||
" ",
|
||||
" ",
|
||||
]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -150,16 +145,13 @@ fn terminal_insert_before_scrolls_on_large_input() -> Result<(), Box<dyn Error>>
|
||||
f.render_widget(paragraph, f.size());
|
||||
})?;
|
||||
|
||||
assert_buffer_eq!(
|
||||
terminal.backend().buffer().clone(),
|
||||
Buffer::with_lines(vec![
|
||||
"------ Line 2 ------",
|
||||
"------ Line 3 ------",
|
||||
"------ Line 4 ------",
|
||||
"------ Line 5 ------",
|
||||
"[---- Viewport ----]",
|
||||
])
|
||||
);
|
||||
terminal.backend().assert_buffer_lines([
|
||||
"------ Line 2 ------",
|
||||
"------ Line 3 ------",
|
||||
"------ Line 4 ------",
|
||||
"------ Line 5 ------",
|
||||
"[---- Viewport ----]",
|
||||
]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -205,16 +197,13 @@ fn terminal_insert_before_scrolls_on_many_inserts() -> Result<(), Box<dyn Error>
|
||||
f.render_widget(paragraph, f.size());
|
||||
})?;
|
||||
|
||||
assert_buffer_eq!(
|
||||
terminal.backend().buffer().clone(),
|
||||
Buffer::with_lines(vec![
|
||||
"------ Line 2 ------",
|
||||
"------ Line 3 ------",
|
||||
"------ Line 4 ------",
|
||||
"------ Line 5 ------",
|
||||
"[---- Viewport ----]",
|
||||
])
|
||||
);
|
||||
terminal.backend().assert_buffer_lines([
|
||||
"------ Line 2 ------",
|
||||
"------ Line 3 ------",
|
||||
"------ Line 4 ------",
|
||||
"------ Line 5 ------",
|
||||
"[---- Viewport ----]",
|
||||
]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2,33 +2,28 @@ use ratatui::{
|
||||
backend::TestBackend,
|
||||
buffer::Buffer,
|
||||
style::{Color, Style},
|
||||
widgets::{Bar, BarChart, BarGroup, Block, Borders},
|
||||
widgets::{Bar, BarChart, BarGroup, Block},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
// check that bars fill up correctly up to max value
|
||||
#[test]
|
||||
fn widgets_barchart_not_full_below_max_value() {
|
||||
let test_case = |expected| {
|
||||
let backend = TestBackend::new(30, 10);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
let barchart = BarChart::default()
|
||||
.block(Block::default().borders(Borders::ALL))
|
||||
.data(&[("empty", 0), ("half", 50), ("almost", 99), ("full", 100)])
|
||||
.max(100)
|
||||
.bar_width(7)
|
||||
.bar_gap(0);
|
||||
f.render_widget(barchart, size);
|
||||
})
|
||||
.unwrap();
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
};
|
||||
|
||||
// check that bars fill up correctly up to max value
|
||||
test_case(Buffer::with_lines(vec![
|
||||
let backend = TestBackend::new(30, 10);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
let barchart = BarChart::default()
|
||||
.block(Block::bordered())
|
||||
.data(&[("empty", 0), ("half", 50), ("almost", 99), ("full", 100)])
|
||||
.max(100)
|
||||
.bar_width(7)
|
||||
.bar_gap(0);
|
||||
f.render_widget(barchart, size);
|
||||
})
|
||||
.unwrap();
|
||||
terminal.backend().assert_buffer_lines([
|
||||
"┌────────────────────────────┐",
|
||||
"│ ▇▇▇▇▇▇▇███████│",
|
||||
"│ ██████████████│",
|
||||
@@ -39,47 +34,43 @@ fn widgets_barchart_not_full_below_max_value() {
|
||||
"│ ██50█████99█████100██│",
|
||||
"│ empty half almost full │",
|
||||
"└────────────────────────────┘",
|
||||
]));
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn widgets_barchart_group() {
|
||||
const TERMINAL_HEIGHT: u16 = 11;
|
||||
let test_case = |expected| {
|
||||
let backend = TestBackend::new(35, TERMINAL_HEIGHT);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
let backend = TestBackend::new(35, TERMINAL_HEIGHT);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
let barchart = BarChart::default()
|
||||
.block(Block::bordered())
|
||||
.data(
|
||||
BarGroup::default().label("Mar".into()).bars(&[
|
||||
Bar::default()
|
||||
.value(10)
|
||||
.label("C1".into())
|
||||
.style(Style::default().fg(Color::Red))
|
||||
.value_style(Style::default().fg(Color::Blue)),
|
||||
Bar::default()
|
||||
.value(20)
|
||||
.style(Style::default().fg(Color::Green))
|
||||
.text_value("20M".to_string()),
|
||||
]),
|
||||
)
|
||||
.data(&vec![("C1", 50), ("C2", 40)])
|
||||
.data(&[("C1", 60), ("C2", 90)])
|
||||
.data(&[("xx", 10), ("xx", 10)])
|
||||
.group_gap(2)
|
||||
.bar_width(4)
|
||||
.bar_gap(1);
|
||||
f.render_widget(barchart, size);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
let barchart = BarChart::default()
|
||||
.block(Block::default().borders(Borders::ALL))
|
||||
.data(
|
||||
BarGroup::default().label("Mar".into()).bars(&[
|
||||
Bar::default()
|
||||
.value(10)
|
||||
.label("C1".into())
|
||||
.style(Style::default().fg(Color::Red))
|
||||
.value_style(Style::default().fg(Color::Blue)),
|
||||
Bar::default()
|
||||
.value(20)
|
||||
.style(Style::default().fg(Color::Green))
|
||||
.text_value("20M".to_string()),
|
||||
]),
|
||||
)
|
||||
.data(&vec![("C1", 50), ("C2", 40)])
|
||||
.data(&[("C1", 60), ("C2", 90)])
|
||||
.data(&[("xx", 10), ("xx", 10)])
|
||||
.group_gap(2)
|
||||
.bar_width(4)
|
||||
.bar_gap(1);
|
||||
f.render_widget(barchart, size);
|
||||
})
|
||||
.unwrap();
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
};
|
||||
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
"┌─────────────────────────────────┐",
|
||||
"│ ████│",
|
||||
"│ ████│",
|
||||
@@ -92,16 +83,13 @@ fn widgets_barchart_group() {
|
||||
"│Mar │",
|
||||
"└─────────────────────────────────┘",
|
||||
]);
|
||||
|
||||
for y in 1..(TERMINAL_HEIGHT - 3) {
|
||||
for x in 1..5 {
|
||||
expected.get_mut(x, y).set_fg(Color::Red);
|
||||
expected.get_mut(x + 5, y).set_fg(Color::Green);
|
||||
}
|
||||
}
|
||||
|
||||
expected.get_mut(2, 7).set_fg(Color::Blue);
|
||||
expected.get_mut(3, 7).set_fg(Color::Blue);
|
||||
|
||||
test_case(expected);
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,10 +12,9 @@ use ratatui::{
|
||||
use time::{Date, Month};
|
||||
|
||||
#[track_caller]
|
||||
fn test_render<W: Widget>(widget: W, expected: &Buffer, width: u16, height: u16) {
|
||||
fn test_render<W: Widget>(widget: W, width: u16, height: u16, expected: &Buffer) {
|
||||
let backend = TestBackend::new(width, height);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
terminal
|
||||
.draw(|f| f.render_widget(widget, f.size()))
|
||||
.unwrap();
|
||||
@@ -28,14 +27,14 @@ fn days_layout() {
|
||||
Date::from_calendar_date(2023, Month::January, 1).unwrap(),
|
||||
CalendarEventStore::default(),
|
||||
);
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
" 1 2 3 4 5 6 7",
|
||||
" 8 9 10 11 12 13 14",
|
||||
" 15 16 17 18 19 20 21",
|
||||
" 22 23 24 25 26 27 28",
|
||||
" 29 30 31",
|
||||
]);
|
||||
test_render(c, &expected, 21, 5);
|
||||
test_render(c, 21, 5, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -45,7 +44,7 @@ fn days_layout_show_surrounding() {
|
||||
CalendarEventStore::default(),
|
||||
)
|
||||
.show_surrounding(Style::default());
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
" 26 27 28 29 30 1 2",
|
||||
" 3 4 5 6 7 8 9",
|
||||
" 10 11 12 13 14 15 16",
|
||||
@@ -53,7 +52,7 @@ fn days_layout_show_surrounding() {
|
||||
" 24 25 26 27 28 29 30",
|
||||
" 31 1 2 3 4 5 6",
|
||||
]);
|
||||
test_render(c, &expected, 21, 6);
|
||||
test_render(c, 21, 6, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -63,7 +62,7 @@ fn show_month_header() {
|
||||
CalendarEventStore::default(),
|
||||
)
|
||||
.show_month_header(Style::default());
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
" January 2023 ",
|
||||
" 1 2 3 4 5 6 7",
|
||||
" 8 9 10 11 12 13 14",
|
||||
@@ -71,7 +70,7 @@ fn show_month_header() {
|
||||
" 22 23 24 25 26 27 28",
|
||||
" 29 30 31",
|
||||
]);
|
||||
test_render(c, &expected, 21, 6);
|
||||
test_render(c, 21, 6, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -81,7 +80,7 @@ fn show_weekdays_header() {
|
||||
CalendarEventStore::default(),
|
||||
)
|
||||
.show_weekdays_header(Style::default());
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
" Su Mo Tu We Th Fr Sa",
|
||||
" 1 2 3 4 5 6 7",
|
||||
" 8 9 10 11 12 13 14",
|
||||
@@ -89,7 +88,7 @@ fn show_weekdays_header() {
|
||||
" 22 23 24 25 26 27 28",
|
||||
" 29 30 31",
|
||||
]);
|
||||
test_render(c, &expected, 21, 6);
|
||||
test_render(c, 21, 6, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -101,7 +100,7 @@ fn show_combo() {
|
||||
.show_weekdays_header(Style::default())
|
||||
.show_month_header(Style::default())
|
||||
.show_surrounding(Style::default());
|
||||
let expected = Buffer::with_lines(vec![
|
||||
let expected = Buffer::with_lines([
|
||||
" January 2023 ",
|
||||
" Su Mo Tu We Th Fr Sa",
|
||||
" 1 2 3 4 5 6 7",
|
||||
@@ -110,5 +109,5 @@ fn show_combo() {
|
||||
" 22 23 24 25 26 27 28",
|
||||
" 29 30 31 1 2 3 4",
|
||||
]);
|
||||
test_render(c, &expected, 21, 7);
|
||||
test_render(c, 21, 7, &expected);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ fn widgets_canvas_draw_labels() {
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut expected = Buffer::with_lines(vec![" ", " ", " ", " ", "test "]);
|
||||
let mut expected = Buffer::with_lines(["", "", "", "", "test "]);
|
||||
for row in 0..5 {
|
||||
for col in 0..5 {
|
||||
expected.get_mut(col, row).set_bg(Color::Yellow);
|
||||
|
||||
@@ -5,7 +5,7 @@ use ratatui::{
|
||||
style::{Color, Style},
|
||||
symbols,
|
||||
text::{self, Span},
|
||||
widgets::{Axis, Block, Borders, Chart, Dataset, GraphType::Line},
|
||||
widgets::{Axis, Block, Chart, Dataset, GraphType::Line},
|
||||
Terminal,
|
||||
};
|
||||
use rstest::rstest;
|
||||
@@ -14,9 +14,16 @@ fn create_labels<'a>(labels: &'a [&'a str]) -> Vec<Span<'a>> {
|
||||
labels.iter().map(|l| Span::from(*l)).collect()
|
||||
}
|
||||
|
||||
fn axis_test_case<'a, S>(width: u16, height: u16, x_axis: Axis, y_axis: Axis, lines: Vec<S>)
|
||||
where
|
||||
S: Into<text::Line<'a>>,
|
||||
#[track_caller]
|
||||
fn axis_test_case<'line, Lines>(
|
||||
width: u16,
|
||||
height: u16,
|
||||
x_axis: Axis,
|
||||
y_axis: Axis,
|
||||
expected: Lines,
|
||||
) where
|
||||
Lines: IntoIterator,
|
||||
Lines::Item: Into<text::Line<'line>>,
|
||||
{
|
||||
let backend = TestBackend::new(width, height);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
@@ -26,8 +33,7 @@ where
|
||||
f.render_widget(chart, f.size());
|
||||
})
|
||||
.unwrap();
|
||||
let expected = Buffer::with_lines(lines);
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
terminal.backend().assert_buffer_lines(expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -46,7 +52,7 @@ fn widgets_chart_can_render_on_small_areas(#[case] width: u16, #[case] height: u
|
||||
.style(Style::default().fg(Color::Magenta))
|
||||
.data(&[(0.0, 0.0)])];
|
||||
let chart = Chart::new(datasets)
|
||||
.block(Block::default().title("Plot").borders(Borders::ALL))
|
||||
.block(Block::bordered().title("Plot"))
|
||||
.x_axis(
|
||||
Axis::default()
|
||||
.bounds([0.0, 0.0])
|
||||
@@ -62,195 +68,192 @@ fn widgets_chart_can_render_on_small_areas(#[case] width: u16, #[case] height: u
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn widgets_chart_handles_long_labels() {
|
||||
let test_case = |x_labels, y_labels, x_alignment, lines| {
|
||||
let mut x_axis = Axis::default().bounds([0.0, 1.0]);
|
||||
if let Some((left_label, right_label)) = x_labels {
|
||||
x_axis = x_axis
|
||||
.labels(vec![Span::from(left_label), Span::from(right_label)])
|
||||
.labels_alignment(x_alignment);
|
||||
}
|
||||
|
||||
let mut y_axis = Axis::default().bounds([0.0, 1.0]);
|
||||
if let Some((left_label, right_label)) = y_labels {
|
||||
y_axis = y_axis.labels(vec![Span::from(left_label), Span::from(right_label)]);
|
||||
}
|
||||
|
||||
axis_test_case(10, 5, x_axis, y_axis, lines);
|
||||
};
|
||||
|
||||
test_case(
|
||||
Some(("AAAA", "B")),
|
||||
None,
|
||||
Alignment::Left,
|
||||
vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ───────",
|
||||
"AAA B",
|
||||
],
|
||||
);
|
||||
test_case(
|
||||
Some(("A", "BBBB")),
|
||||
None,
|
||||
Alignment::Left,
|
||||
vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ─────────",
|
||||
"A BBBB",
|
||||
],
|
||||
);
|
||||
test_case(
|
||||
Some(("AAAAAAAAAAA", "B")),
|
||||
None,
|
||||
Alignment::Left,
|
||||
vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ───────",
|
||||
"AAA B",
|
||||
],
|
||||
);
|
||||
test_case(
|
||||
Some(("A", "B")),
|
||||
Some(("CCCCCCC", "D")),
|
||||
Alignment::Left,
|
||||
vec![
|
||||
"D │ ",
|
||||
" │ ",
|
||||
"CCC│ ",
|
||||
" └──────",
|
||||
" A B",
|
||||
],
|
||||
);
|
||||
test_case(
|
||||
Some(("AAAAAAAAAA", "B")),
|
||||
Some(("C", "D")),
|
||||
Alignment::Center,
|
||||
vec![
|
||||
"D │ ",
|
||||
" │ ",
|
||||
"C │ ",
|
||||
" └──────",
|
||||
"AAAAAAA B",
|
||||
],
|
||||
);
|
||||
test_case(
|
||||
Some(("AAAAAAA", "B")),
|
||||
Some(("C", "D")),
|
||||
Alignment::Right,
|
||||
vec![
|
||||
"D│ ",
|
||||
" │ ",
|
||||
"C│ ",
|
||||
" └────────",
|
||||
" AAAAA B",
|
||||
],
|
||||
);
|
||||
test_case(
|
||||
Some(("AAAAAAA", "BBBBBBB")),
|
||||
Some(("C", "D")),
|
||||
Alignment::Right,
|
||||
vec![
|
||||
"D│ ",
|
||||
" │ ",
|
||||
"C│ ",
|
||||
" └────────",
|
||||
" AAAAABBBB",
|
||||
],
|
||||
);
|
||||
#[rstest]
|
||||
#[case(
|
||||
Some(("AAAA", "B")),
|
||||
None,
|
||||
Alignment::Left,
|
||||
vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ───────",
|
||||
"AAA B",
|
||||
],
|
||||
)]
|
||||
#[case(
|
||||
Some(("A", "BBBB")),
|
||||
None,
|
||||
Alignment::Left,
|
||||
vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ─────────",
|
||||
"A BBBB",
|
||||
],
|
||||
)]
|
||||
#[case(
|
||||
Some(("AAAAAAAAAAA", "B")),
|
||||
None,
|
||||
Alignment::Left,
|
||||
vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ───────",
|
||||
"AAA B",
|
||||
],
|
||||
)]
|
||||
#[case(
|
||||
Some(("A", "B")),
|
||||
Some(("CCCCCCC", "D")),
|
||||
Alignment::Left,
|
||||
vec![
|
||||
"D │ ",
|
||||
" │ ",
|
||||
"CCC│ ",
|
||||
" └──────",
|
||||
" A B",
|
||||
],
|
||||
)]
|
||||
#[case(
|
||||
Some(("AAAAAAAAAA", "B")),
|
||||
Some(("C", "D")),
|
||||
Alignment::Center,
|
||||
vec![
|
||||
"D │ ",
|
||||
" │ ",
|
||||
"C │ ",
|
||||
" └──────",
|
||||
"AAAAAAA B",
|
||||
],
|
||||
)]
|
||||
#[case(
|
||||
Some(("AAAAAAA", "B")),
|
||||
Some(("C", "D")),
|
||||
Alignment::Right,
|
||||
vec![
|
||||
"D│ ",
|
||||
" │ ",
|
||||
"C│ ",
|
||||
" └────────",
|
||||
" AAAAA B",
|
||||
],
|
||||
)]
|
||||
#[case(
|
||||
Some(("AAAAAAA", "BBBBBBB")),
|
||||
Some(("C", "D")),
|
||||
Alignment::Right,
|
||||
vec![
|
||||
"D│ ",
|
||||
" │ ",
|
||||
"C│ ",
|
||||
" └────────",
|
||||
" AAAAABBBB",
|
||||
],
|
||||
)]
|
||||
fn widgets_chart_handles_long_labels<'line, Lines>(
|
||||
#[case] x_labels: Option<(&str, &str)>,
|
||||
#[case] y_labels: Option<(&str, &str)>,
|
||||
#[case] x_alignment: Alignment,
|
||||
#[case] expected: Lines,
|
||||
) where
|
||||
Lines: IntoIterator,
|
||||
Lines::Item: Into<text::Line<'line>>,
|
||||
{
|
||||
let mut x_axis = Axis::default().bounds([0.0, 1.0]);
|
||||
if let Some((left_label, right_label)) = x_labels {
|
||||
x_axis = x_axis
|
||||
.labels(vec![Span::from(left_label), Span::from(right_label)])
|
||||
.labels_alignment(x_alignment);
|
||||
}
|
||||
let mut y_axis = Axis::default().bounds([0.0, 1.0]);
|
||||
if let Some((left_label, right_label)) = y_labels {
|
||||
y_axis = y_axis.labels(vec![Span::from(left_label), Span::from(right_label)]);
|
||||
}
|
||||
axis_test_case(10, 5, x_axis, y_axis, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn widgets_chart_handles_x_axis_labels_alignments() {
|
||||
let test_case = |y_alignment, lines| {
|
||||
let x_axis = Axis::default()
|
||||
.labels(vec![Span::from("AAAA"), Span::from("B"), Span::from("C")])
|
||||
.labels_alignment(y_alignment);
|
||||
|
||||
let y_axis = Axis::default();
|
||||
|
||||
axis_test_case(10, 5, x_axis, y_axis, lines);
|
||||
};
|
||||
|
||||
test_case(
|
||||
Alignment::Left,
|
||||
vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ───────",
|
||||
"AAA B C",
|
||||
],
|
||||
);
|
||||
test_case(
|
||||
Alignment::Center,
|
||||
vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ────────",
|
||||
"AAAA B C",
|
||||
],
|
||||
);
|
||||
test_case(
|
||||
Alignment::Right,
|
||||
vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
"──────────",
|
||||
"AAA B C",
|
||||
],
|
||||
);
|
||||
#[rstest]
|
||||
#[case::left(
|
||||
Alignment::Left,
|
||||
vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ───────",
|
||||
"AAA B C",
|
||||
],
|
||||
)]
|
||||
#[case::center(
|
||||
Alignment::Center,
|
||||
vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ────────",
|
||||
"AAAA B C",
|
||||
],
|
||||
)]
|
||||
#[case::right(
|
||||
Alignment::Right,
|
||||
vec![
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
"──────────",
|
||||
"AAA B C",
|
||||
],
|
||||
)]
|
||||
fn widgets_chart_handles_x_axis_labels_alignments<'line, Lines>(
|
||||
#[case] y_alignment: Alignment,
|
||||
#[case] expected: Lines,
|
||||
) where
|
||||
Lines: IntoIterator,
|
||||
Lines::Item: Into<text::Line<'line>>,
|
||||
{
|
||||
let x_axis = Axis::default()
|
||||
.labels(vec![Span::from("AAAA"), Span::from("B"), Span::from("C")])
|
||||
.labels_alignment(y_alignment);
|
||||
let y_axis = Axis::default();
|
||||
axis_test_case(10, 5, x_axis, y_axis, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn widgets_chart_handles_y_axis_labels_alignments() {
|
||||
let test_case = |y_alignment, lines| {
|
||||
let x_axis = Axis::default().labels(create_labels(&["AAAAA", "B"]));
|
||||
|
||||
let y_axis = Axis::default()
|
||||
.labels(create_labels(&["C", "D"]))
|
||||
.labels_alignment(y_alignment);
|
||||
|
||||
axis_test_case(20, 5, x_axis, y_axis, lines);
|
||||
};
|
||||
test_case(
|
||||
Alignment::Left,
|
||||
vec![
|
||||
"D │ ",
|
||||
" │ ",
|
||||
"C │ ",
|
||||
" └───────────────",
|
||||
"AAAAA B",
|
||||
],
|
||||
);
|
||||
test_case(
|
||||
Alignment::Center,
|
||||
vec![
|
||||
" D │ ",
|
||||
" │ ",
|
||||
" C │ ",
|
||||
" └───────────────",
|
||||
"AAAAA B",
|
||||
],
|
||||
);
|
||||
test_case(
|
||||
Alignment::Right,
|
||||
vec![
|
||||
" D│ ",
|
||||
" │ ",
|
||||
" C│ ",
|
||||
" └───────────────",
|
||||
"AAAAA B",
|
||||
],
|
||||
);
|
||||
#[rstest]
|
||||
#[case::left(Alignment::Left, [
|
||||
"D │ ",
|
||||
" │ ",
|
||||
"C │ ",
|
||||
" └───────────────",
|
||||
"AAAAA B",
|
||||
])]
|
||||
#[case::center(Alignment::Center, [
|
||||
" D │ ",
|
||||
" │ ",
|
||||
" C │ ",
|
||||
" └───────────────",
|
||||
"AAAAA B",
|
||||
])]
|
||||
#[case::right(Alignment::Right, [
|
||||
" D│ ",
|
||||
" │ ",
|
||||
" C│ ",
|
||||
" └───────────────",
|
||||
"AAAAA B",
|
||||
])]
|
||||
fn widgets_chart_handles_y_axis_labels_alignments<'line, Lines>(
|
||||
#[case] y_alignment: Alignment,
|
||||
#[case] expected: Lines,
|
||||
) where
|
||||
Lines: IntoIterator,
|
||||
Lines::Item: Into<text::Line<'line>>,
|
||||
{
|
||||
let x_axis = Axis::default().labels(create_labels(&["AAAAA", "B"]));
|
||||
let y_axis = Axis::default()
|
||||
.labels(create_labels(&["C", "D"]))
|
||||
.labels_alignment(y_alignment);
|
||||
axis_test_case(20, 5, x_axis, y_axis, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -265,7 +268,7 @@ fn widgets_chart_can_have_axis_with_zero_length_bounds() {
|
||||
.style(Style::default().fg(Color::Magenta))
|
||||
.data(&[(0.0, 0.0)])];
|
||||
let chart = Chart::new(datasets)
|
||||
.block(Block::default().title("Plot").borders(Borders::ALL))
|
||||
.block(Block::bordered().title("Plot"))
|
||||
.x_axis(
|
||||
Axis::default()
|
||||
.bounds([0.0, 0.0])
|
||||
@@ -305,7 +308,7 @@ fn widgets_chart_handles_overflows() {
|
||||
(1_588_298_496.0, 1.0),
|
||||
])];
|
||||
let chart = Chart::new(datasets)
|
||||
.block(Block::default().title("Plot").borders(Borders::ALL))
|
||||
.block(Block::bordered().title("Plot"))
|
||||
.x_axis(
|
||||
Axis::default()
|
||||
.bounds([1_588_298_471.0, 1_588_992_600.0])
|
||||
@@ -338,11 +341,7 @@ fn widgets_chart_can_have_empty_datasets() {
|
||||
.draw(|f| {
|
||||
let datasets = vec![Dataset::default().data(&[]).graph_type(Line)];
|
||||
let chart = Chart::new(datasets)
|
||||
.block(
|
||||
Block::default()
|
||||
.title("Empty Dataset With Line")
|
||||
.borders(Borders::ALL),
|
||||
)
|
||||
.block(Block::bordered().title("Empty Dataset With Line"))
|
||||
.x_axis(
|
||||
Axis::default()
|
||||
.bounds([0.0, 0.0])
|
||||
@@ -411,7 +410,7 @@ fn widgets_chart_can_have_a_legend() {
|
||||
];
|
||||
let chart = Chart::new(datasets)
|
||||
.style(Style::default().bg(Color::White))
|
||||
.block(Block::default().title("Chart Test").borders(Borders::ALL))
|
||||
.block(Block::bordered().title("Chart Test"))
|
||||
.x_axis(
|
||||
Axis::default()
|
||||
.bounds([0.0, 100.0])
|
||||
@@ -435,7 +434,7 @@ fn widgets_chart_can_have_a_legend() {
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
"┌Chart Test────────────────────────────────────────────────┐",
|
||||
"│10.0│Y Axis ┌─────────┐│",
|
||||
"│ │ •• │Dataset 1││",
|
||||
@@ -615,7 +614,6 @@ fn widgets_chart_can_have_a_legend() {
|
||||
for (col, row) in x_axis_title {
|
||||
expected.get_mut(col, row).set_fg(Color::Yellow);
|
||||
}
|
||||
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
}
|
||||
|
||||
@@ -645,7 +643,7 @@ fn widgets_chart_top_line_styling_is_correct() {
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
"b│abc••••",
|
||||
" │ ",
|
||||
" │ ",
|
||||
|
||||
@@ -5,7 +5,7 @@ use ratatui::{
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
symbols,
|
||||
text::Span,
|
||||
widgets::{Block, Borders, Gauge, LineGauge},
|
||||
widgets::{Block, Gauge, LineGauge},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
@@ -22,20 +22,20 @@ fn widgets_gauge_renders() {
|
||||
.split(f.size());
|
||||
|
||||
let gauge = Gauge::default()
|
||||
.block(Block::default().title("Percentage").borders(Borders::ALL))
|
||||
.block(Block::bordered().title("Percentage"))
|
||||
.gauge_style(Style::default().bg(Color::Blue).fg(Color::Red))
|
||||
.use_unicode(true)
|
||||
.percent(43);
|
||||
f.render_widget(gauge, chunks[0]);
|
||||
let gauge = Gauge::default()
|
||||
.block(Block::default().title("Ratio").borders(Borders::ALL))
|
||||
.block(Block::bordered().title("Ratio"))
|
||||
.gauge_style(Style::default().bg(Color::Blue).fg(Color::Red))
|
||||
.use_unicode(true)
|
||||
.ratio(0.511_313_934_313_1);
|
||||
f.render_widget(gauge, chunks[1]);
|
||||
})
|
||||
.unwrap();
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
" ",
|
||||
" ",
|
||||
" ┌Percentage────────────────────────┐ ",
|
||||
@@ -71,18 +71,18 @@ fn widgets_gauge_renders_no_unicode() {
|
||||
.split(f.size());
|
||||
|
||||
let gauge = Gauge::default()
|
||||
.block(Block::default().title("Percentage").borders(Borders::ALL))
|
||||
.block(Block::bordered().title("Percentage"))
|
||||
.percent(43)
|
||||
.use_unicode(false);
|
||||
f.render_widget(gauge, chunks[0]);
|
||||
let gauge = Gauge::default()
|
||||
.block(Block::default().title("Ratio").borders(Borders::ALL))
|
||||
.block(Block::bordered().title("Ratio"))
|
||||
.ratio(0.211_313_934_313_1)
|
||||
.use_unicode(false);
|
||||
f.render_widget(gauge, chunks[1]);
|
||||
})
|
||||
.unwrap();
|
||||
let expected = Buffer::with_lines(vec![
|
||||
terminal.backend().assert_buffer_lines([
|
||||
" ",
|
||||
" ",
|
||||
" ┌Percentage────────────────────────┐ ",
|
||||
@@ -94,7 +94,6 @@ fn widgets_gauge_renders_no_unicode() {
|
||||
" ",
|
||||
" ",
|
||||
]);
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -106,9 +105,7 @@ fn widgets_gauge_applies_styles() {
|
||||
.draw(|f| {
|
||||
let gauge = Gauge::default()
|
||||
.block(
|
||||
Block::default()
|
||||
.title(Span::styled("Test", Style::default().fg(Color::Red)))
|
||||
.borders(Borders::ALL),
|
||||
Block::bordered().title(Span::styled("Test", Style::default().fg(Color::Red))),
|
||||
)
|
||||
.gauge_style(Style::default().fg(Color::Blue).bg(Color::Red))
|
||||
.percent(43)
|
||||
@@ -121,7 +118,7 @@ fn widgets_gauge_applies_styles() {
|
||||
f.render_widget(gauge, f.size());
|
||||
})
|
||||
.unwrap();
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
"┌Test──────┐",
|
||||
"│████ │",
|
||||
"│███43% │",
|
||||
@@ -173,8 +170,7 @@ fn widgets_gauge_supports_large_labels() {
|
||||
f.render_widget(gauge, f.size());
|
||||
})
|
||||
.unwrap();
|
||||
let expected = Buffer::with_lines(vec!["4333333333"]);
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
terminal.backend().assert_buffer_lines(["4333333333"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -196,7 +192,7 @@ fn widgets_line_gauge_renders() {
|
||||
},
|
||||
);
|
||||
let gauge = LineGauge::default()
|
||||
.block(Block::default().title("Gauge 2").borders(Borders::ALL))
|
||||
.block(Block::bordered().title("Gauge 2"))
|
||||
.gauge_style(Style::default().fg(Color::Green))
|
||||
.line_set(symbols::line::THICK)
|
||||
.ratio(0.211_313_934_313_1);
|
||||
@@ -211,7 +207,7 @@ fn widgets_line_gauge_renders() {
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
"43% ────────────────",
|
||||
"┌Gauge 2───────────┐",
|
||||
"│21% ━━━━━━━━━━━━━━│",
|
||||
|
||||
@@ -8,6 +8,7 @@ use ratatui::{
|
||||
widgets::{Block, Borders, HighlightSpacing, List, ListItem, ListState},
|
||||
Terminal,
|
||||
};
|
||||
use rstest::rstest;
|
||||
|
||||
#[test]
|
||||
fn list_should_shows_the_length() {
|
||||
@@ -45,7 +46,12 @@ fn widgets_list_should_highlight_the_selected_item() {
|
||||
f.render_stateful_widget(list, size, &mut state);
|
||||
})
|
||||
.unwrap();
|
||||
let mut expected = Buffer::with_lines(vec![" Item 1 ", ">> Item 2 ", " Item 3 "]);
|
||||
#[rustfmt::skip]
|
||||
let mut expected = Buffer::with_lines([
|
||||
" Item 1 ",
|
||||
">> Item 2 ",
|
||||
" Item 3 ",
|
||||
]);
|
||||
for x in 0..10 {
|
||||
expected.get_mut(x, 1).set_bg(Color::Yellow);
|
||||
}
|
||||
@@ -75,9 +81,12 @@ fn widgets_list_should_highlight_the_selected_item_wide_symbol() {
|
||||
f.render_stateful_widget(list, size, &mut state);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut expected = Buffer::with_lines(vec![" Item 1 ", "▶ Item 2 ", " Item 3 "]);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let mut expected = Buffer::with_lines([
|
||||
" Item 1 ",
|
||||
"▶ Item 2 ",
|
||||
" Item 3 ",
|
||||
]);
|
||||
for x in 0..10 {
|
||||
expected.get_mut(x, 1).set_bg(Color::Yellow);
|
||||
}
|
||||
@@ -95,7 +104,7 @@ fn widgets_list_should_truncate_items() {
|
||||
let backend = TestBackend::new(10, 2);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
let cases = vec![
|
||||
let cases = [
|
||||
// An item is selected
|
||||
TruncateTestCase {
|
||||
selected: Some(0),
|
||||
@@ -103,7 +112,7 @@ fn widgets_list_should_truncate_items() {
|
||||
ListItem::new("A very long line"),
|
||||
ListItem::new("A very long line"),
|
||||
],
|
||||
expected: Buffer::with_lines(vec![
|
||||
expected: Buffer::with_lines([
|
||||
format!(">> A ve{} ", symbols::line::VERTICAL),
|
||||
format!(" A ve{} ", symbols::line::VERTICAL),
|
||||
]),
|
||||
@@ -115,7 +124,7 @@ fn widgets_list_should_truncate_items() {
|
||||
ListItem::new("A very long line"),
|
||||
ListItem::new("A very long line"),
|
||||
],
|
||||
expected: Buffer::with_lines(vec![
|
||||
expected: Buffer::with_lines([
|
||||
format!("A very {} ", symbols::line::VERTICAL),
|
||||
format!("A very {} ", symbols::line::VERTICAL),
|
||||
]),
|
||||
@@ -127,7 +136,7 @@ fn widgets_list_should_truncate_items() {
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let list = List::new(case.items.clone())
|
||||
.block(Block::default().borders(Borders::RIGHT))
|
||||
.block(Block::new().borders(Borders::RIGHT))
|
||||
.highlight_symbol(">> ");
|
||||
f.render_stateful_widget(list, Rect::new(0, 0, 8, 2), &mut state);
|
||||
})
|
||||
@@ -159,8 +168,12 @@ fn widgets_list_should_clamp_offset_if_items_are_removed() {
|
||||
f.render_stateful_widget(list, size, &mut state);
|
||||
})
|
||||
.unwrap();
|
||||
let expected = Buffer::with_lines(vec![" Item 2 ", " Item 3 ", " Item 4 ", ">> Item 5 "]);
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
terminal.backend().assert_buffer_lines([
|
||||
" Item 2 ",
|
||||
" Item 3 ",
|
||||
" Item 4 ",
|
||||
">> Item 5 ",
|
||||
]);
|
||||
|
||||
// render again with 1 items => check offset is clamped to 1
|
||||
state.select(Some(1));
|
||||
@@ -172,8 +185,12 @@ fn widgets_list_should_clamp_offset_if_items_are_removed() {
|
||||
f.render_stateful_widget(list, size, &mut state);
|
||||
})
|
||||
.unwrap();
|
||||
let expected = Buffer::with_lines(vec![" Item 3 ", " ", " ", " "]);
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
terminal.backend().assert_buffer_lines([
|
||||
" Item 3 ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -196,7 +213,7 @@ fn widgets_list_should_display_multiline_items() {
|
||||
f.render_stateful_widget(list, size, &mut state);
|
||||
})
|
||||
.unwrap();
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
" Item 1 ",
|
||||
" Item 1a",
|
||||
">> Item 2 ",
|
||||
@@ -232,7 +249,7 @@ fn widgets_list_should_repeat_highlight_symbol() {
|
||||
f.render_stateful_widget(list, size, &mut state);
|
||||
})
|
||||
.unwrap();
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
let mut expected = Buffer::with_lines([
|
||||
" Item 1 ",
|
||||
" Item 1a",
|
||||
">> Item 2 ",
|
||||
@@ -251,7 +268,6 @@ fn widgets_list_should_repeat_highlight_symbol() {
|
||||
fn widget_list_should_not_ignore_empty_string_items() {
|
||||
let backend = TestBackend::new(6, 4);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let items = vec![
|
||||
@@ -268,134 +284,98 @@ fn widget_list_should_not_ignore_empty_string_items() {
|
||||
f.render_widget(list, f.size());
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected = Buffer::with_lines(vec!["Item 1", "", "", "Item 4"]);
|
||||
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
terminal
|
||||
.backend()
|
||||
.assert_buffer_lines(["Item 1", "", "", "Item 4"]);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[test]
|
||||
fn widgets_list_enable_always_highlight_spacing() {
|
||||
let test_case = |state: &mut ListState, space: HighlightSpacing, expected: Buffer| {
|
||||
let backend = TestBackend::new(30, 8);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
let table = List::new(vec![
|
||||
ListItem::new(vec![Line::from("Item 1"), Line::from("Item 1a")]),
|
||||
ListItem::new(vec![Line::from("Item 2"), Line::from("Item 2b")]),
|
||||
ListItem::new(vec![Line::from("Item 3"), Line::from("Item 3c")]),
|
||||
])
|
||||
.block(Block::default().borders(Borders::ALL))
|
||||
.highlight_symbol(">> ")
|
||||
.highlight_spacing(space);
|
||||
f.render_stateful_widget(table, size, state);
|
||||
})
|
||||
.unwrap();
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
};
|
||||
|
||||
assert_eq!(HighlightSpacing::default(), HighlightSpacing::WhenSelected);
|
||||
|
||||
let mut state = ListState::default();
|
||||
// no selection, "WhenSelected" should only allocate if selected
|
||||
test_case(
|
||||
&mut state,
|
||||
HighlightSpacing::default(),
|
||||
Buffer::with_lines(vec![
|
||||
"┌────────────────────────────┐",
|
||||
"│Item 1 │",
|
||||
"│Item 1a │",
|
||||
"│Item 2 │",
|
||||
"│Item 2b │",
|
||||
"│Item 3 │",
|
||||
"│Item 3c │",
|
||||
"└────────────────────────────┘",
|
||||
]),
|
||||
);
|
||||
|
||||
// no selection, "Always" should allocate regardless if selected or not
|
||||
test_case(
|
||||
&mut state,
|
||||
HighlightSpacing::Always,
|
||||
Buffer::with_lines(vec![
|
||||
"┌────────────────────────────┐",
|
||||
"│ Item 1 │",
|
||||
"│ Item 1a │",
|
||||
"│ Item 2 │",
|
||||
"│ Item 2b │",
|
||||
"│ Item 3 │",
|
||||
"│ Item 3c │",
|
||||
"└────────────────────────────┘",
|
||||
]),
|
||||
);
|
||||
|
||||
// no selection, "Never" should never allocate regadless if selected or not
|
||||
test_case(
|
||||
&mut state,
|
||||
HighlightSpacing::Never,
|
||||
Buffer::with_lines(vec![
|
||||
"┌────────────────────────────┐",
|
||||
"│Item 1 │",
|
||||
"│Item 1a │",
|
||||
"│Item 2 │",
|
||||
"│Item 2b │",
|
||||
"│Item 3 │",
|
||||
"│Item 3c │",
|
||||
"└────────────────────────────┘",
|
||||
]),
|
||||
);
|
||||
|
||||
// select first, "WhenSelected" should only allocate if selected
|
||||
state.select(Some(0));
|
||||
test_case(
|
||||
&mut state,
|
||||
HighlightSpacing::default(),
|
||||
Buffer::with_lines(vec![
|
||||
"┌────────────────────────────┐",
|
||||
"│>> Item 1 │",
|
||||
"│ Item 1a │",
|
||||
"│ Item 2 │",
|
||||
"│ Item 2b │",
|
||||
"│ Item 3 │",
|
||||
"│ Item 3c │",
|
||||
"└────────────────────────────┘",
|
||||
]),
|
||||
);
|
||||
|
||||
// select first, "Always" should allocate regardless if selected or not
|
||||
state.select(Some(0));
|
||||
test_case(
|
||||
&mut state,
|
||||
HighlightSpacing::Always,
|
||||
Buffer::with_lines(vec![
|
||||
"┌────────────────────────────┐",
|
||||
"│>> Item 1 │",
|
||||
"│ Item 1a │",
|
||||
"│ Item 2 │",
|
||||
"│ Item 2b │",
|
||||
"│ Item 3 │",
|
||||
"│ Item 3c │",
|
||||
"└────────────────────────────┘",
|
||||
]),
|
||||
);
|
||||
|
||||
// select first, "Never" should never allocate regadless if selected or not
|
||||
state.select(Some(0));
|
||||
test_case(
|
||||
&mut state,
|
||||
HighlightSpacing::Never,
|
||||
Buffer::with_lines(vec![
|
||||
"┌────────────────────────────┐",
|
||||
"│Item 1 │",
|
||||
"│Item 1a │",
|
||||
"│Item 2 │",
|
||||
"│Item 2b │",
|
||||
"│Item 3 │",
|
||||
"│Item 3c │",
|
||||
"└────────────────────────────┘",
|
||||
]),
|
||||
);
|
||||
#[rstest]
|
||||
#[case::none_when_selected(None, HighlightSpacing::WhenSelected, [
|
||||
"┌─────────────┐",
|
||||
"│Item 1 │",
|
||||
"│Item 1a │",
|
||||
"│Item 2 │",
|
||||
"│Item 2b │",
|
||||
"│Item 3 │",
|
||||
"│Item 3c │",
|
||||
"└─────────────┘",
|
||||
])]
|
||||
#[case::none_always(None, HighlightSpacing::Always, [
|
||||
"┌─────────────┐",
|
||||
"│ Item 1 │",
|
||||
"│ Item 1a │",
|
||||
"│ Item 2 │",
|
||||
"│ Item 2b │",
|
||||
"│ Item 3 │",
|
||||
"│ Item 3c │",
|
||||
"└─────────────┘",
|
||||
])]
|
||||
#[case::none_never(None, HighlightSpacing::Never, [
|
||||
"┌─────────────┐",
|
||||
"│Item 1 │",
|
||||
"│Item 1a │",
|
||||
"│Item 2 │",
|
||||
"│Item 2b │",
|
||||
"│Item 3 │",
|
||||
"│Item 3c │",
|
||||
"└─────────────┘",
|
||||
])]
|
||||
#[case::first_when_selected(Some(0), HighlightSpacing::WhenSelected, [
|
||||
"┌─────────────┐",
|
||||
"│>> Item 1 │",
|
||||
"│ Item 1a │",
|
||||
"│ Item 2 │",
|
||||
"│ Item 2b │",
|
||||
"│ Item 3 │",
|
||||
"│ Item 3c │",
|
||||
"└─────────────┘",
|
||||
])]
|
||||
#[case::first_always(Some(0), HighlightSpacing::Always, [
|
||||
"┌─────────────┐",
|
||||
"│>> Item 1 │",
|
||||
"│ Item 1a │",
|
||||
"│ Item 2 │",
|
||||
"│ Item 2b │",
|
||||
"│ Item 3 │",
|
||||
"│ Item 3c │",
|
||||
"└─────────────┘",
|
||||
])]
|
||||
#[case::first_never(Some(0), HighlightSpacing::Never, [
|
||||
"┌─────────────┐",
|
||||
"│Item 1 │",
|
||||
"│Item 1a │",
|
||||
"│Item 2 │",
|
||||
"│Item 2b │",
|
||||
"│Item 3 │",
|
||||
"│Item 3c │",
|
||||
"└─────────────┘",
|
||||
])]
|
||||
fn widgets_list_enable_always_highlight_spacing<'line, Lines>(
|
||||
#[case] selected: Option<usize>,
|
||||
#[case] space: HighlightSpacing,
|
||||
#[case] expected: Lines,
|
||||
) where
|
||||
Lines: IntoIterator,
|
||||
Lines::Item: Into<Line<'line>>,
|
||||
{
|
||||
let mut state = ListState::default().with_selected(selected);
|
||||
let backend = TestBackend::new(15, 8);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
let table = List::new(vec![
|
||||
ListItem::new(vec![Line::from("Item 1"), Line::from("Item 1a")]),
|
||||
ListItem::new(vec![Line::from("Item 2"), Line::from("Item 2b")]),
|
||||
ListItem::new(vec![Line::from("Item 3"), Line::from("Item 3c")]),
|
||||
])
|
||||
.block(Block::bordered())
|
||||
.highlight_symbol(">> ")
|
||||
.highlight_spacing(space);
|
||||
f.render_stateful_widget(table, size, &mut state);
|
||||
})
|
||||
.unwrap();
|
||||
terminal
|
||||
.backend()
|
||||
.assert_buffer(&Buffer::with_lines(expected));
|
||||
}
|
||||
|
||||
@@ -3,25 +3,23 @@ use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::Alignment,
|
||||
text::{Line, Span, Text},
|
||||
widgets::{Block, Borders, Padding, Paragraph, Wrap},
|
||||
widgets::{Block, Padding, Paragraph, Wrap},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
/// Tests the [`Paragraph`] widget against the expected [`Buffer`] by rendering it onto an equal
|
||||
/// area and comparing the rendered and expected content.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn test_case(paragraph: Paragraph, expected: Buffer) {
|
||||
#[track_caller]
|
||||
fn test_case(paragraph: Paragraph, expected: &Buffer) {
|
||||
let backend = TestBackend::new(expected.area.width, expected.area.height);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
f.render_widget(paragraph, size);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
terminal.backend().assert_buffer(expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -30,12 +28,12 @@ fn widgets_paragraph_renders_double_width_graphemes() {
|
||||
|
||||
let text = vec![Line::from(s)];
|
||||
let paragraph = Paragraph::new(text)
|
||||
.block(Block::default().borders(Borders::ALL))
|
||||
.block(Block::bordered())
|
||||
.wrap(Wrap { trim: true });
|
||||
|
||||
test_case(
|
||||
paragraph,
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌────────┐",
|
||||
"│コンピュ│",
|
||||
"│ータ上で│",
|
||||
@@ -61,13 +59,12 @@ fn widgets_paragraph_renders_mixed_width_graphemes() {
|
||||
let size = f.size();
|
||||
let text = vec![Line::from(s)];
|
||||
let paragraph = Paragraph::new(text)
|
||||
.block(Block::default().borders(Borders::ALL))
|
||||
.block(Block::bordered())
|
||||
.wrap(Wrap { trim: true });
|
||||
f.render_widget(paragraph, size);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected = Buffer::with_lines(vec![
|
||||
terminal.backend().assert_buffer_lines([
|
||||
// The internal width is 8 so only 4 slots for double-width characters.
|
||||
"┌────────┐",
|
||||
"│aコンピ │", // Here we have 1 latin character so only 3 double-width ones can fit.
|
||||
@@ -77,18 +74,17 @@ fn widgets_paragraph_renders_mixed_width_graphemes() {
|
||||
"│、 │",
|
||||
"└────────┘",
|
||||
]);
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn widgets_paragraph_can_wrap_with_a_trailing_nbsp() {
|
||||
let nbsp = "\u{00a0}";
|
||||
let line = Line::from(vec![Span::raw("NBSP"), Span::raw(nbsp)]);
|
||||
let paragraph = Paragraph::new(line).block(Block::default().borders(Borders::ALL));
|
||||
let paragraph = Paragraph::new(line).block(Block::bordered());
|
||||
|
||||
test_case(
|
||||
paragraph,
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌──────────────────┐",
|
||||
"│NBSP\u{00a0} │",
|
||||
"└──────────────────┘",
|
||||
@@ -99,16 +95,16 @@ fn widgets_paragraph_can_wrap_with_a_trailing_nbsp() {
|
||||
#[test]
|
||||
fn widgets_paragraph_can_scroll_horizontally() {
|
||||
let text =
|
||||
Text::from("段落现在可以水平滚动了!\nParagraph can scroll horizontally!\nShort line");
|
||||
let paragraph = Paragraph::new(text).block(Block::default().borders(Borders::ALL));
|
||||
Text::from("段落现在可以水平滚动了!\nParagraph can scroll horizontally!\nLittle line");
|
||||
let paragraph = Paragraph::new(text).block(Block::bordered());
|
||||
|
||||
test_case(
|
||||
paragraph.clone().alignment(Alignment::Left).scroll((0, 7)),
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌──────────────────┐",
|
||||
"│在可以水平滚动了!│",
|
||||
"│ph can scroll hori│",
|
||||
"│ine │",
|
||||
"│line │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
@@ -120,11 +116,11 @@ fn widgets_paragraph_can_scroll_horizontally() {
|
||||
// only support Alignment::Left
|
||||
test_case(
|
||||
paragraph.clone().alignment(Alignment::Right).scroll((0, 7)),
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌──────────────────┐",
|
||||
"│段落现在可以水平滚│",
|
||||
"│Paragraph can scro│",
|
||||
"│ Short line│",
|
||||
"│ Little line│",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
@@ -144,12 +140,12 @@ const SAMPLE_STRING: &str = "The library is based on the principle of immediate
|
||||
fn widgets_paragraph_can_wrap_its_content() {
|
||||
let text = vec![Line::from(SAMPLE_STRING)];
|
||||
let paragraph = Paragraph::new(text)
|
||||
.block(Block::default().borders(Borders::ALL))
|
||||
.block(Block::bordered())
|
||||
.wrap(Wrap { trim: true });
|
||||
|
||||
test_case(
|
||||
paragraph.clone().alignment(Alignment::Left),
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌──────────────────┐",
|
||||
"│The library is │",
|
||||
"│based on the │",
|
||||
@@ -164,7 +160,7 @@ fn widgets_paragraph_can_wrap_its_content() {
|
||||
);
|
||||
test_case(
|
||||
paragraph.clone().alignment(Alignment::Center),
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌──────────────────┐",
|
||||
"│ The library is │",
|
||||
"│ based on the │",
|
||||
@@ -179,7 +175,7 @@ fn widgets_paragraph_can_wrap_its_content() {
|
||||
);
|
||||
test_case(
|
||||
paragraph.clone().alignment(Alignment::Right),
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌──────────────────┐",
|
||||
"│ The library is│",
|
||||
"│ based on the│",
|
||||
@@ -196,19 +192,19 @@ fn widgets_paragraph_can_wrap_its_content() {
|
||||
|
||||
#[test]
|
||||
fn widgets_paragraph_works_with_padding() {
|
||||
let text = vec![Line::from(SAMPLE_STRING)];
|
||||
let paragraph = Paragraph::new(text)
|
||||
.block(Block::default().borders(Borders::ALL).padding(Padding {
|
||||
left: 2,
|
||||
right: 2,
|
||||
top: 1,
|
||||
bottom: 1,
|
||||
}))
|
||||
let block = Block::bordered().padding(Padding {
|
||||
left: 2,
|
||||
right: 2,
|
||||
top: 1,
|
||||
bottom: 1,
|
||||
});
|
||||
let paragraph = Paragraph::new(vec![Line::from(SAMPLE_STRING)])
|
||||
.block(block.clone())
|
||||
.wrap(Wrap { trim: true });
|
||||
|
||||
test_case(
|
||||
paragraph.clone().alignment(Alignment::Left),
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌────────────────────┐",
|
||||
"│ │",
|
||||
"│ The library is │",
|
||||
@@ -225,7 +221,7 @@ fn widgets_paragraph_works_with_padding() {
|
||||
);
|
||||
test_case(
|
||||
paragraph.clone().alignment(Alignment::Right),
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌────────────────────┐",
|
||||
"│ │",
|
||||
"│ The library is │",
|
||||
@@ -241,20 +237,16 @@ fn widgets_paragraph_works_with_padding() {
|
||||
]),
|
||||
);
|
||||
|
||||
let mut text = vec![Line::from("This is always centered.").alignment(Alignment::Center)];
|
||||
text.push(Line::from(SAMPLE_STRING));
|
||||
let paragraph = Paragraph::new(text)
|
||||
.block(Block::default().borders(Borders::ALL).padding(Padding {
|
||||
left: 2,
|
||||
right: 2,
|
||||
top: 1,
|
||||
bottom: 1,
|
||||
}))
|
||||
.wrap(Wrap { trim: true });
|
||||
let paragraph = Paragraph::new(vec![
|
||||
Line::from("This is always centered.").alignment(Alignment::Center),
|
||||
Line::from(SAMPLE_STRING),
|
||||
])
|
||||
.block(block)
|
||||
.wrap(Wrap { trim: true });
|
||||
|
||||
test_case(
|
||||
paragraph.alignment(Alignment::Right),
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌────────────────────┐",
|
||||
"│ │",
|
||||
"│ This is always │",
|
||||
@@ -283,12 +275,12 @@ fn widgets_paragraph_can_align_spans() {
|
||||
Line::from(default_s),
|
||||
];
|
||||
let paragraph = Paragraph::new(text)
|
||||
.block(Block::default().borders(Borders::ALL))
|
||||
.block(Block::bordered())
|
||||
.wrap(Wrap { trim: true });
|
||||
|
||||
test_case(
|
||||
paragraph.clone().alignment(Alignment::Left),
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌──────────────────┐",
|
||||
"│ This string will│",
|
||||
"│ override the│",
|
||||
@@ -303,7 +295,7 @@ fn widgets_paragraph_can_align_spans() {
|
||||
);
|
||||
test_case(
|
||||
paragraph.alignment(Alignment::Center),
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌──────────────────┐",
|
||||
"│ This string will│",
|
||||
"│ override the│",
|
||||
@@ -333,11 +325,11 @@ fn widgets_paragraph_can_align_spans() {
|
||||
|
||||
let mut text = left_lines.clone();
|
||||
text.append(&mut lines);
|
||||
let paragraph = Paragraph::new(text).block(Block::default().borders(Borders::ALL));
|
||||
let paragraph = Paragraph::new(text).block(Block::bordered());
|
||||
|
||||
test_case(
|
||||
paragraph.clone().alignment(Alignment::Right),
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌──────────────────┐",
|
||||
"│This string │",
|
||||
"│will override the │",
|
||||
@@ -352,7 +344,7 @@ fn widgets_paragraph_can_align_spans() {
|
||||
);
|
||||
test_case(
|
||||
paragraph.alignment(Alignment::Left),
|
||||
Buffer::with_lines(vec![
|
||||
&Buffer::with_lines([
|
||||
"┌──────────────────┐",
|
||||
"│This string │",
|
||||
"│will override the │",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,8 +26,7 @@ fn widgets_tabs_should_not_panic_on_narrow_areas() {
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
let expected = Buffer::with_lines(vec![" "]);
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
terminal.backend().assert_buffer_lines([" "]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -48,7 +47,7 @@ fn widgets_tabs_should_truncate_the_last_item() {
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
let mut expected = Buffer::with_lines(vec![format!(" Tab1 {} T ", symbols::line::VERTICAL)]);
|
||||
let mut expected = Buffer::with_lines([format!(" Tab1 {} T ", symbols::line::VERTICAL)]);
|
||||
expected.set_style(Rect::new(1, 0, 4, 1), Style::new().reversed());
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user