`crossterm`, `termion`, and `termwiz` can now be accessed as
`ratatui::{crossterm, termion, termwiz}` respectively. This makes it
possible to just add the Ratatui crate as a dependency and use the
backend of choice without having to add the backend crates as
dependencies.
To update existing code, replace all instances of `crossterm::` with
`ratatui::crossterm::`, `termion::` with `ratatui::termion::`, and
`termwiz::` with `ratatui::termwiz::`.
- Simplify `assert_buffer_eq!` logic.
- Deprecate `assert_buffer_eq!`.
- Introduce `TestBackend::assert_buffer_lines`.
Also simplify many tests involving buffer comparisons.
For the deprecation, just use `assert_eq` instead of `assert_buffer_eq`:
```diff
-assert_buffer_eq!(actual, expected);
+assert_eq!(actual, expected);
```
---
I noticed `assert_buffer_eq!` creating no test coverage reports and
looked into this macro. First I simplified it. Then I noticed a bunch of
`assert_eq!(buffer, …)` and other indirect usages of this macro (like
`TestBackend::assert_buffer`).
The good thing here is that it's mainly used in tests so not many
changes to the library code.
`Block::bordered()` is shorter than
`Block::new().borders(Borders::ALL)`, requires one less import
(`Borders`) and in case `Block::default()` was used before can even be
`const`.
Fixes many not yet enabled lints (mostly pedantic) on everything that is
not the lib (examples, benchs, 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
---------
Co-authored-by: Josh McKinney <joshka@users.noreply.github.com>
ratatui v0.26.0 brought a regression in the List widget, in which the
highlight symbol width was incorrectly calculated - specifically when
the highlight symbol was a multi-char character, e.g. `▶`.
This can make it easier to use `Buffer::with_lines` with iterators that
don't necessarily produce a `Vec`. For example, this allows using
`Buffer::with_lines` with `&[&str]` directly, without having to call
`collect` on it first.
This PR makes a number of simplifications to the layout and constraint
features that were added after v0.25.0.
For users upgrading from v0.25.0, the net effect of this PR (along with
the other PRs) is the following:
- New `Flex` modes have been added.
- `Flex::Start` (new default)
- `Flex::Center`
- `Flex::End`
- `Flex::SpaceAround`
- `Flex::SpaceBetween`
- `Flex::Legacy` (old default)
- `Min(v)` grows to allocate excess space in all `Flex` modes instead of
shrinking (except in `Flex::Legacy` where it retains old behavior).
- `Fill(1)` grows to allocate excess space, growing equally with
`Min(v)`.
---
The following contains a summary of the changes in this PR and the
motivation behind them.
**`Flex`**
- Removes `Flex::Stretch`
- Renames `Flex::StretchLast` to `Flex::Legacy`
**`Constraint`**
- Removes `Fixed`
- Makes `Min(v)` grow as much as possible everywhere (except
`Flex::Legacy` where it retains the old behavior)
- Makes `Min(v)` grow equally as `Fill(1)` while respecting `Min` lower
bounds. When `Fill` and `Min` are used together, they both fill excess
space equally.
Allowing `Min(v)` to grow still allows users to build the same layouts
as before with `Flex::Start` with no breaking changes to the behavior.
This PR also removes the unstable feature `SegmentSize`.
This is a breaking change to the behavior of constraints. If users want
old behavior, they can use `Flex::Legacy`.
```rust
Layout::vertical([Length(25), Length(25)]).flex(Flex::Legacy)
```
Users that have constraint that exceed the available space will probably
not see any difference or see an improvement in their layouts. Any
layout with `Min` will be identical in `Flex::Start` and `Flex::Legacy`
so any layout with `Min` will not be breaking.
Previously, `Table` used `EvenDistribution` internally by default, but
with that gone the default is now `Flex::Start`. This changes the
behavior of `Table` (for the better in most cases). The only way for
users to get exactly the same as the old behavior is to change their
constraints. I imagine most users will be happier out of the box with
the new Table default.
Resolves https://github.com/ratatui-org/ratatui/issues/843
Thanks to @joshka for the direction
Follow up to https://github.com/ratatui-org/ratatui/pull/783
This PR introduces different priorities for each kind of constraint.
This PR also adds tests that specifies this behavior. This PR resolves a
number of broken tests.
Fixes https://github.com/ratatui-org/ratatui/issues/827
With this PR, the layout algorithm will do the following in order:
1. Ensure that all the segments are within the user provided area and
ensure that all segments and spacers are aligned next to each other
2. if a user provides a `layout.spacing`, it will enforce it.
3. ensure proportional elements are all proportional to each other
4. if a user provides a `Fixed(v)` constraint, it will enforce it.
5. `Min` / `Max` binding inequality constraints
6. `Length`
7. `Percentage`
8. `Ratio`
9. collapse `Min` or collapse `Max`
10. grow `Proportional` as much as possible
11. grow spacers as much as possible
This PR also returns the spacer areas as `Rects` to the user. Users can
then draw into the spacers as they see fit (thanks @joshka for the
idea). Here's a screenshot with the modified flex example:
<img width="569" alt="image"
src="https://github.com/ratatui-org/ratatui/assets/1813121/46c8901d-882c-43b0-ba87-b1d455099d8f">
This PR introduces a `strengths` module that has "default" weights that
give stable solutions as well as predictable behavior.
Many widgets can be rendered without changing their state.
This commit implements The `Widget` trait for references to
widgets and changes their implementations to be immutable.
This allows us to render widgets without consuming them by passing a ref
to the widget when calling `Frame::render_widget()`.
```rust
// this might be stored in a struct
let paragraph = Paragraph::new("Hello world!");
let [left, right] = area.split(&Layout::horizontal([20, 20]));
frame.render_widget(¶graph, left);
frame.render_widget(¶graph, right); // we can reuse the widget
```
Implemented for all widgets except BarChart (which has an implementation
that modifies the internal state and requires a rewrite to fix.
Other widgets will be implemented in follow up commits.
Fixes: https://github.com/ratatui-org/ratatui/discussions/164
Replaces PRs: https://github.com/ratatui-org/ratatui/pull/122 and
https://github.com/ratatui-org/ratatui/pull/16
Enables: https://github.com/ratatui-org/ratatui/issues/132
Validated as a viable working solution by:
https://github.com/ratatui-org/ratatui/pull/836
This uses the new `spacing` feature of the `Layout` struct to allocate
columns spacing in the `Table` widget.
This changes the behavior of the table column layout in the following
ways:
1. Selection width is always allocated.
- if a user does not want a selection width ever they should use
`HighlightSpacing::Never`
2. Column spacing is prioritized over other constraints
- if a user does not want column spacing, they should use
`Table::new(...).column_spacing(0)`
---------
Co-authored-by: Josh McKinney <joshka@users.noreply.github.com>
TableState, ListState, and ScrollbarState can now be serialized and deserialized
using serde.
```rust
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct AppState {
list_state: ListState,
table_state: TableState,
scrollbar_state: ScrollbarState,
}
let app_state = AppState::default();
let serialized = serde_json::to_string(app_state);
let app_state = serde_json::from_str(serialized);
```
Any iterator whose item is convertible into `Row` can now be
collected into a `Table`.
Where previously, `Table::new` accepted `IntoIterator<Item = Row>`, it
now accepts `IntoIterator<Item: Into<Row>>`.
BREAKING CHANGE:
The compiler can no longer infer the element type of the container
passed to `Table::new()`. For example, `Table::new(vec![], widths)`
will no longer compile, as the type of `vec![]` can no longer be
inferred.
Any iterator whose item is convertible into `Line` can now be
collected into `Tabs`.
In addition, where previously `Tabs::new` required a `Vec`, it can now
accept any object that implements `IntoIterator` with an item type
implementing `Into<Line>`.
BREAKING CHANGE:
Calls to `Tabs::new()` whose argument is collected from an iterator
will no longer compile. For example,
`Tabs::new(["a","b"].into_iter().collect())` will no longer compile,
because the return type of `.collect()` can no longer be inferred to
be a `Vec<_>`.
This allows to build list like
```
List::new(["Item 1", "Item 2"])
```
BREAKING CHANGE: `List::new` parameter type changed from `Into<Vec<ListItem<'a>>>`
to `IntoIterator<Item = Into<ListItem<'a>>>`
This prevents creating a table that doesn't actually render anything.
Fixes: https://github.com/ratatui-org/ratatui/issues/537
BREAKING CHANGE: Table::new() now takes an additional widths parameter.
Previously the default highlight_style was set to `Style::default()`,
which meant that the highlight style was the same as the normal style.
This change sets the default highlight_style to reversed text.
BREAKING CHANGE: The `Tab` widget now renders the highlight style as
reversed text by default. This can be changed by setting the
`highlight_style` field of the `Tab` widget.
This allows passing an array, slice or Vec of constraints, which is more
ergonomic than requiring this to always be a slice.
The following calls now all succeed:
```rust
Table::new(rows).widths([Constraint::Length(5), Constraint::Length(5)]);
Table::new(rows).widths(&[Constraint::Length(5), Constraint::Length(5)]);
// widths could also be computed at runtime
let widths = vec![Constraint::Length(5), Constraint::Length(5)];
Table::new(rows).widths(widths.clone());
Table::new(rows).widths(&widths);
```
The Cell::symbol field is now accessible via a getter method (`symbol()`). This will
allow us to make future changes to the Cell internals such as replacing `String` with
`compact_str`.
Fixes issue with inserting content with height>viewport_area.height and adds
the ability to insert content of height>terminal_height
- Adds TestBackend::append_lines() and TestBackend::clear_region() methods to
support testing the changes
The background colors of the gauge had a workaround for the issue we had
with VHS / TTYD rendering the background color of the gauge. This
workaround is no longer necessary in the updated versions of VHS / TTYD.
Fixes https://github.com/ratatui-org/ratatui/issues/501
The `Spans` type (plural, not singular) was replaced with a more ergonomic `Line` type
in Ratatui v0.21.0 and marked deprecated byt left for backwards compatibility. This is now
removed.
- `Line` replaces `Spans`
- `Buffer::set_line` replaces `Buffer::set_spans`
This allows writing unit tests without having to call set_style on the
expected buffer.
E.g.:
```rust
use crate::style::Stylize;
let mut buf = Buffer::empty(Rect::new(0, 0, 10, 10));
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()]));
```
Inspired by https://github.com/ratatui-org/ratatui/issues/493#issuecomment-1714844468
A bug in the rendering caused the top line of the chart to be rendered
using the style of the chart, instead of the dataset style. This is
fixed by only setting the style for the width of the text, and not the
entire row.
Fixes: https://github.com/ratatui-org/ratatui/issues/379
* feat(barchart): Add direction attribute
Enable rendring the bars horizontally. In some cases this allow us to
make more efficient use of the available space.
Signed-off-by: Ben Fekih, Hichem <hichem.f@live.de>
* feat(barchart)!: render the group labels depending on the alignment
This is a breaking change, since the alignment by default is set to
Left and the group labels are always rendered in the center.
Signed-off-by: Ben Fekih, Hichem <hichem.f@live.de>
---------
Signed-off-by: Ben Fekih, Hichem <hichem.f@live.de>
Previously the layout used the floor of the calculated start and width
as the value to use for the split Rects. This resulted in gaps between
the split rects.
This change modifies the layout to round to the nearest column instead
of taking the floor of the start and width. This results in the start
and end of each rect being rounded the same way and being strictly
adjacent without gaps.
Because there is a required constraint that ensures that the last end is
equal to the area end, there is no longer the need to fixup the last
item width when the fill (as e.g. width = x.99 now rounds to x+1 not x).
The colors example has been updated to use Ratio(1, 8) instead of
Percentage(13), as this now renders without gaps for all possible sizes,
whereas previously it would have left odd gaps between columns.
* feat(list): add option to always allocate the "selection" column width
Before this option was available, selecting a item in a list when nothing was selected
previously made the row layout change (the same applies to unselecting) by adding the width
of the "highlight symbol" in the front of the list, this option allows to configure this
behavior.
* style: change "highlight_spacing" doc comment to use inline code-block for reference
* feat(table): add option to configure selection layout changes
Before this option was available, selecting a row in the table when no row was selected
previously made the tables layout change (the same applies to unselecting) by adding the width
of the "highlight symbol" in the front of the first column, this option allows to configure this
behavior.
* refactor(table): refactor "get_columns_widths" to return (x, width)
and "render" to make use of that
* refactor(table): refactor "get_columns_widths" to take in a selection_width instead of a boolean
also refactor "render" to make use of this change
* fix(table): rename "highlight_set_selection_space" to "highlight_spacing"
* style(table): apply doc-comment suggestions from code review
Co-authored-by: Dheepak Krishnamurthy <me@kdheepak.com>
---------
Co-authored-by: Dheepak Krishnamurthy <me@kdheepak.com>
* fix(title): remove default alignment and position
* test(block): add test cases for alignment
* test(block): extend the unit tests for block title alignment
* feat(stylize): allow all widgets to be styled
- Add styled impl to:
- Barchart
- Chart (including Axis and Dataset),
- Guage and LineGuage
- List and ListItem
- Sparkline
- Table, Row, and Cell
- Tabs
- Style
- Allow modifiers to be removed (e.g. .not_italic())
- Allow .bg() to recieve Into<Color>
- Made shorthand methods consistent with modifier names (e.g. dim() not
dimmed() and underlined() not underline())
- Simplify integration tests
- Add doc comments
- Simplified stylize macros with https://crates.io/crates/paste
* build: run clippy before tests
Runny clippy first means that we fail fast when there is an issue that
can easily be fixed rather than having to wait 30-40s for the failure
for now the value is converted to a string and then printed. in many
cases the values are too wide or double values. so it make sense
to set a custom value text instead of the default behavior.
this patch suggests to add a method
"fn text_value(mut self, text_value: String)"
to the Bar, which allows to override the value printed in the bar
Signed-off-by: Ben Fekih, Hichem <hichem.f@live.de>
* feat(barchart): allow to add a group of bars
Example: to show the revenue of different companies:
┌────────────────────────┐
│ ████ │
│ ████ │
│ ████ ████ │
│ ▄▄▄▄ ████ ████ ████ │
│ ████ ████ ████ ████ │
│ ████ ████ ████ ████ │
│ █50█ █60█ █90█ █55█ │
│ Mars April │
└────────────────────────┘
new structs are introduced: Group and Bar.
the data function is modified to accept "impl Into<Group<'a>>".
a new function "group_gap" is introduced to set the gap between each group
unit test changed to allow the label to be in the center
Signed-off-by: Ben Fekih, Hichem <hichem.f@live.de>
* feat(barchart)!: center labels by default
The bar labels are currently printed string from the left side of
bar. This commit centers the labels under the bar.
Signed-off-by: Ben Fekih, Hichem <hichem.f@live.de>
---------
Signed-off-by: Ben Fekih, Hichem <hichem.f@live.de>
* fix(gauge): render gauge with unicode correctly
Gauge now correctly renders a block rather than a space when in unicode mode.
* docs: update demo.gif
- remove existing gif
- upload using VHS (https://github.com/charmbracelet/vhs)
- add instructions to RELEASE.md
- link new gif in README
This is an opinionated default that helps avoid horizontal scrolling.
100 is the most common width on github rust projects and works well for
displaying code on a 16in macbook pro.
`Paragraph` now supports rendering each line in the paragraph with a
different alignment (Center, Left and Right) rather than the entire
paragraph being aligned the same. Each line either overrides the
paragraph's alignment or inherits it if the line's alignment is
unspecified.
- Adds `alignment` field to `Line` and builder methods on `Line` and
`Span`
- Updates reflow module types to be line oriented rather than symbol
oriented to take account of each lines alignment
- Adds unit tests to `Paragraph` to fully capture the existing and new
behavior
---------
Co-authored-by: Josh McKinney <joshka@users.noreply.github.com>
- `Default::default` is hard to read
- a few `map` -> `map_or`
- simplified `match` -> `let-if`
Signed-off-by: Yuri Astrakhan <YuriAstrakhan@gmail.com>
Adds a `border!` macro that takes TOP, BOTTOM, LEFT, RIGHT, and ALL and
returns a Borders object. An empty `border!()` call returns
Borders::NONE
This is gated behind a `macros` feature flag to ensure short build
times. To enable, add the following to your `Cargo.toml`:
```toml
ratatui = { version = 0.21.0, features = ["macros"] }
```
Co-authored-by: C-Jameson <booksncode@gmail.com>
* refactor: add Line type to replace Spans
`Line` is a significantly better name over `Spans` as the plural causes
confusion and the type really is a representation of a line of text made
up of spans.
This is a backwards compatible version of the approach from
https://github.com/tui-rs-revival/ratatui/pull/175
There is a significant amount of code that uses the Spans type and
methods, so instead of just renaming it, we add a new type and replace
parameters that accepts a `Spans` with a parameter that accepts
`Into<Line>`.
Note that the examples have been intentionally left using `Spans` in
this commit to demonstrate the compiler warnings that will be emitted in
existing code.
Implementation notes:
- moves the Spans code to text::spans and publicly reexports on the text
module. This makes the test in that module only relevant to the Spans
type.
- adds a line module with a copy of the code and tests from Spans with a
single addition: `impl<'a> From<Spans<'a>> for Line<'a>`
- adds tests for `Spans` (created and checked before refactoring)
- adds the same tests for `Line`
- updates all widget methods that accept and store Spans to instead
store `Line` and accept `Into<Line>`
* refactor: move text::Masked to text::masked::Masked
Re-exports the Masked type at text::Masked
* refactor: replace Spans with Line in tests/examples/docs