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
This PR adds a new way to space elements in a `Layout`.
Loosely based on
[flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/), this
PR adds a `Flex` enum with the following variants:
- Start
- Center
- End
- SpaceAround
- SpaceBetween
<img width="380" alt="image" src="https://github.com/ratatui-org/ratatui/assets/1813121/b744518c-eae7-4e35-bbc4-fe3c95193cde">
It also adds two more variants, to make this backward compatible and to
make it replace `SegmentSize`:
- StretchLast (default in the `Flex` enum, also behavior matches old
default `SegmentSize::LastTakesRemainder`)
- Stretch (behavior matches `SegmentSize::EvenDistribution`)
The `Start` variant from above matches `SegmentSize::None`.
This allows `Flex` to be a complete replacement for `SegmentSize`, hence
this PR also deprecates the `segment_size` constructor on `Layout`.
`SegmentSize` is still used in `Table` but under the hood `segment_size`
maps to `Flex` with all tests passing unchanged.
I also put together a simple example for `Flex` layouts so that I could
test it visually, shared below:
https://github.com/ratatui-org/ratatui/assets/1813121/c8716c59-493f-4631-add5-feecf4bd4e06
* feat(layout): add vertical and horizontal constructors
This commit adds two new constructors to the `Layout` struct, which
allow the user to create a vertical or horizontal layout with default
values.
```rust
let layout = Layout::vertical([
Constraint::Length(10),
Constraint::Min(5),
Constraint::Length(10),
]);
let layout = Layout::horizontal([
Constraint::Length(10),
Constraint::Min(5),
Constraint::Length(10),
]);
```
Adds helper methods that convert from iterators of u16 values to the
specific Constraint type. This makes it easy to create constraints like:
```rust
// a fixed layout
let constraints = Constraint::from_lengths([10, 20, 10]);
// a centered layout
let constraints = Constraint::from_ratios([(1, 4), (1, 2), (1, 4)]);
let constraints = Constraint::from_percentages([25, 50, 25]);
// a centered layout with a minimum size
let constraints = Constraint::from_mins([0, 100, 0]);
// a sidebar / main layout with maximum sizes
let constraints = Constraint::from_maxes([30, 200]);
```
Structs and enums at the top of the file helps show the interaction
between the types without having to find each type in between longer
impl sections.
Also moved the try_split function into the Layout impl as an associated
function and inlined the `layout::split()` which just called try_split.
This makes the code a bit more contained.
Fixes#650
This PR corrects the "builder methods" expressing to simple `setters`
(see #650#655), and gives a clearer diagnostic notice on setters `must_use`.
`#[must_use = "method moves the value of self and returns the modified value"]`
Details:
docs: Correct wording in docs from builder methods
Add `must_use` on layout setters
chore: add `must_use` on widgets fluent methods
This commit ignored `table.rs` because it is included in other PRs.
test(gauge): fix test
It controls how to distribute extra space to an underconstrained table.
The default, legacy behavior is to leave the extra space unused. The
new options are LastTakesRemainder which gets all space to the rightmost
column that can used it, and EvenDistribution which divides it amongst
all columns.
Fixes#370
Layout and Table now accept IntoIterator for constraints with an Item
that is AsRef<Constraint>. This allows pretty much any collection of
constraints to be passed to the layout functions including arrays,
vectors, slices, and iterators (without having to call collect() on
them).
The layout split will generally fill the remaining area when `split()`
is called. This change allows the caller to configure how any extra
space is allocated to the `Rect`s. This is useful for cases where the
caller wants to have a fixed size for one of the `Rect`s, and have the
other `Rect`s fill the remaining space.
For now, the method and enum are marked as unstable because the exact
name is still being bikeshedded. To enable this functionality, add the
`unstable-segment-size` feature flag in your `Cargo.toml`.
To configure the layout to fill the remaining space evenly, use
`Layout::segment_size(SegmentSize::EvenDistribution)`. The default
behavior is `SegmentSize::LastTakesRemainder`, which gives the last
segment the remaining space. `SegmentSize::None` will disable this
behavior. See the docs for `Layout::segment_size()` and
`layout::SegmentSize` for more information.
Fixes https://github.com/ratatui-org/ratatui/issues/536
Adds a convenience function to create a layout with a direction and a
list of constraints which are the most common parameters that would be
generally configured using the builder pattern. The constraints can be
passed in as any iterator of constraints.
```rust
let layout = Layout::new(Direction::Horizontal, [
Constraint::Percentage(50),
Constraint::Percentage(50),
]);
```
BREAKING CHANGE:
Layout::new() now takes a direction and a list of constraints instead of
no arguments. This is a breaking change because it changes the signature
of the function. Layout::new() is also no longer const because it takes
an iterator of constraints.
This change simplifys UI code that uses the Frame type. E.g.:
```rust
fn draw<B: Backend>(frame: &mut Frame<B>) {
// ...
}
```
Frame was generic over Backend because it stored a reference to the
terminal in the field. Instead it now directly stores the viewport area
and current buffer. These are provided at creation time and are valid
for the duration of the frame.
BREAKING CHANGE: Frame is no longer generic over Backend. Code that
accepted `Frame<Backend>` will now need to accept `Frame`. To migrate
existing code, remove any generic parameters from code that uses an
instance of a Frame. E.g. the above code becomes:
```rust
fn draw(frame: &mut Frame) {
// ...
}
```
- add `Rect::is_empty()` that checks whether either height or width == 0
- refactored `Rect` into layout/rect.rs from layout.rs. No public API change as
the module is private and the type is re-exported under the `layout` module.
The layout cache now uses a LruCache with default size set to 16 entries.
Previously the cache was backed by a HashMap, and was able to grow
without bounds as a new entry was added for every new combination of
layout parameters.
- Added a new method (`layout::init_cache(usize)`) that allows the cache
size to be changed if necessary. This will only have an effect if it is called
prior to any calls to `layout::split()` as the cache is wrapped in a `OnceLock`
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
This commit adds `prelude::*` all doc examples and widget::* to those
that need it. This is done to highlight the use of the prelude and
simplify the examples.
- Examples in Type and module level comments show all imports and use
`prelude::*` and `widget::*` where possible.
- Function level comments hide imports unless there are imports other
than `prelude::*` and `widget::*`.
For image (sixel, iTerm2, Kitty...) support that handles graphics in
terms of `Rect` so that the image area can be included in layouts.
For example: an image is loaded with a known pixel-size, and drawn, but
the image protocol has no mechanism of knowing the actual cell/character
area that been drawn on. It is then impossible to skip overdrawing the
area.
Returning the window size in pixel-width / pixel-height, together with
colums / rows, it can be possible to account the pixel size of each cell
/ character, and then known the `Rect` of a given image, and also resize
the image so that it fits exactly in a `Rect`.
Crossterm and termwiz also both return both sizes from one syscall,
while termion does two.
Add a `Size` struct for the cases where a `Rect`'s `x`/`y` is unused
(always zero).
`Size` is not "clipped" for `area < u16::max_value()` like `Rect`. This
is why there are `From` implementations between the two.
The merge of the table unit tests after the rounding layout fix was not
rebased correctly, this addresses the broken tests, makes them more
concise while adding comments to help clarify that the rounding behavior
is working as expected.
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.
* test(layout): add tests for split()
* refactor(layout): simplify and doc split()
This is mainly a reduction in density of the code with a goal of
improving mainatainability so that the algorithm is clear.
Removes some unnecessary code and makes the function more readable.
Instead of creating a temporary result and mutating it, we just create
the result directly from the list of changes.
Reorder the derive fields to be more consistent:
Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash
Hash trait won't be impl in this PR due to rust std design.
If we need hash trait for f64 related structs in the future,
we should consider wrap f64 into a new type.
see: https://github.com/ratatui-org/ratatui/issues/307
Implement `Clone & Copy` common traits for most structs in src.
Only implement `Copy` for structs that are simple and trivial to copy.
Reorder the derive fields to be more consistent:
Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash
see: https://github.com/ratatui-org/ratatui/issues/307
Implement `Debug & Default` common traits for most structs in src.
Reorder the derive fields to be more consistent:
Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash
see: https://github.com/ratatui-org/ratatui/issues/307
This allows the following types to be used in a constant context:
- `Layout`
- `Rect`
- `Style`
- `BorderType`
- `Padding`
- `Block`
Also adds several missing `new()` functions to the above types.
Blocks can now be used in the following way:
```
const DEFAULT_BLOCK: Block = Block::new()
.title_style(Style::new())
.title_alignment(Alignment::Left)
.title_position(Position::Top)
.borders(Borders::ALL)
.border_style(Style::new())
.style(Style::reset())
.padding(Padding::uniform(1));
```
Layouts can now be used in the following way:
``
const DEFAULT_LAYOUT: Layout = Layout::new()
.direction(Direction::Horizontal)
.margin(1)
.expand_to_fill(false);
```
Rects can now be used in the following way:
```
const RECT: Rect = Rect {
x: 0,
y: 0,
width: 10,
height: 10,
};
```
- `Default::default` is hard to read
- a few `map` -> `map_or`
- simplified `match` -> `let-if`
Signed-off-by: Yuri Astrakhan <YuriAstrakhan@gmail.com>