mirror of
https://github.com/ratatui/ratatui.git
synced 2025-09-27 04:50:46 +00:00
feat(layout)!: Change Flex::default()
(#881)
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 commit is contained in:
parent
984afd580b
commit
540fd2df03
@ -100,11 +100,7 @@ underline-color = ["dep:crossterm"]
|
||||
#! The following features are unstable and may change in the future:
|
||||
|
||||
## Enable all unstable features.
|
||||
unstable = ["unstable-segment-size", "unstable-rendered-line-info"]
|
||||
|
||||
## Enables the [`Layout::segment_size`](crate::layout::Layout::segment_size) method which is experimental and may change in the
|
||||
## future. See [Issue #536](https://github.com/ratatui-org/ratatui/issues/536) for more details.
|
||||
unstable-segment-size = []
|
||||
unstable = ["unstable-rendered-line-info"]
|
||||
|
||||
## Enables the [`Paragraph::line_count`](crate::widgets::Paragraph::line_count)
|
||||
## [`Paragraph::line_width`](crate::widgets::Paragraph::line_width) methods
|
||||
|
@ -28,8 +28,6 @@ const SPACER_HEIGHT: u16 = 0;
|
||||
const ILLUSTRATION_HEIGHT: u16 = 4;
|
||||
const EXAMPLE_HEIGHT: u16 = ILLUSTRATION_HEIGHT + SPACER_HEIGHT;
|
||||
|
||||
// priority 1
|
||||
const FIXED_COLOR: Color = tailwind::RED.c900;
|
||||
// priority 2
|
||||
const MIN_COLOR: Color = tailwind::BLUE.c900;
|
||||
const MAX_COLOR: Color = tailwind::BLUE.c800;
|
||||
@ -54,7 +52,6 @@ struct App {
|
||||
#[derive(Default, Debug, Copy, Clone, Display, FromRepr, EnumIter, PartialEq, Eq)]
|
||||
enum SelectedTab {
|
||||
#[default]
|
||||
Fixed,
|
||||
Min,
|
||||
Max,
|
||||
Length,
|
||||
@ -163,8 +160,8 @@ impl App {
|
||||
impl Widget for App {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let [tabs, axis, demo] = area.split(&Layout::vertical([
|
||||
Constraint::Fixed(3),
|
||||
Constraint::Fixed(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Fill(0),
|
||||
]));
|
||||
|
||||
@ -268,7 +265,6 @@ impl SelectedTab {
|
||||
fn get_example_count(&self) -> u16 {
|
||||
use SelectedTab::*;
|
||||
match self {
|
||||
Fixed => 4,
|
||||
Length => 4,
|
||||
Percentage => 5,
|
||||
Ratio => 4,
|
||||
@ -282,7 +278,6 @@ impl SelectedTab {
|
||||
use SelectedTab::*;
|
||||
let text = format!(" {value} ");
|
||||
let color = match value {
|
||||
Fixed => FIXED_COLOR,
|
||||
Length => LENGTH_COLOR,
|
||||
Percentage => PERCENTAGE_COLOR,
|
||||
Ratio => RATIO_COLOR,
|
||||
@ -297,7 +292,6 @@ impl SelectedTab {
|
||||
impl Widget for SelectedTab {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
match self {
|
||||
SelectedTab::Fixed => self.render_fixed_example(area, buf),
|
||||
SelectedTab::Length => self.render_length_example(area, buf),
|
||||
SelectedTab::Percentage => self.render_percentage_example(area, buf),
|
||||
SelectedTab::Ratio => self.render_ratio_example(area, buf),
|
||||
@ -309,30 +303,18 @@ impl Widget for SelectedTab {
|
||||
}
|
||||
|
||||
impl SelectedTab {
|
||||
fn render_fixed_example(&self, area: Rect, buf: &mut Buffer) {
|
||||
let [example1, example2, example3, example4, _] =
|
||||
area.split(&Layout::vertical([Fixed(EXAMPLE_HEIGHT); 5]));
|
||||
|
||||
Example::new(&[Fixed(40), Fill(0)]).render(example1, buf);
|
||||
Example::new(&[Fixed(20), Fixed(20), Fill(0)]).render(example2, buf);
|
||||
Example::new(&[Fixed(20), Min(20), Max(20)]).render(example3, buf);
|
||||
Example::new(&[Length(20), Percentage(20), Ratio(1, 5), Fill(1), Fixed(15)])
|
||||
.render(example4, buf);
|
||||
}
|
||||
|
||||
fn render_length_example(&self, area: Rect, buf: &mut Buffer) {
|
||||
let [example1, example2, example3, example4, _] =
|
||||
area.split(&Layout::vertical([Fixed(EXAMPLE_HEIGHT); 5]));
|
||||
let [example1, example2, example3, _] =
|
||||
area.split(&Layout::vertical([Length(EXAMPLE_HEIGHT); 4]));
|
||||
|
||||
Example::new(&[Length(20), Fixed(20)]).render(example1, buf);
|
||||
Example::new(&[Length(20), Length(20)]).render(example2, buf);
|
||||
Example::new(&[Length(20), Min(20)]).render(example3, buf);
|
||||
Example::new(&[Length(20), Max(20)]).render(example4, buf);
|
||||
Example::new(&[Length(20), Length(20)]).render(example1, buf);
|
||||
Example::new(&[Length(20), Min(20)]).render(example2, buf);
|
||||
Example::new(&[Length(20), Max(20)]).render(example3, buf);
|
||||
}
|
||||
|
||||
fn render_percentage_example(&self, area: Rect, buf: &mut Buffer) {
|
||||
let [example1, example2, example3, example4, example5, _] =
|
||||
area.split(&Layout::vertical([Fixed(EXAMPLE_HEIGHT); 6]));
|
||||
area.split(&Layout::vertical([Length(EXAMPLE_HEIGHT); 6]));
|
||||
|
||||
Example::new(&[Percentage(75), Fill(0)]).render(example1, buf);
|
||||
Example::new(&[Percentage(25), Fill(0)]).render(example2, buf);
|
||||
@ -343,7 +325,7 @@ impl SelectedTab {
|
||||
|
||||
fn render_ratio_example(&self, area: Rect, buf: &mut Buffer) {
|
||||
let [example1, example2, example3, example4, _] =
|
||||
area.split(&Layout::vertical([Fixed(EXAMPLE_HEIGHT); 5]));
|
||||
area.split(&Layout::vertical([Length(EXAMPLE_HEIGHT); 5]));
|
||||
|
||||
Example::new(&[Ratio(1, 2); 2]).render(example1, buf);
|
||||
Example::new(&[Ratio(1, 4); 4]).render(example2, buf);
|
||||
@ -352,7 +334,7 @@ impl SelectedTab {
|
||||
}
|
||||
|
||||
fn render_fill_example(&self, area: Rect, buf: &mut Buffer) {
|
||||
let [example1, example2, _] = area.split(&Layout::vertical([Fixed(EXAMPLE_HEIGHT); 3]));
|
||||
let [example1, example2, _] = area.split(&Layout::vertical([Length(EXAMPLE_HEIGHT); 3]));
|
||||
|
||||
Example::new(&[Fill(1), Fill(2), Fill(3)]).render(example1, buf);
|
||||
Example::new(&[Fill(1), Percentage(50), Fill(1)]).render(example2, buf);
|
||||
@ -360,7 +342,7 @@ impl SelectedTab {
|
||||
|
||||
fn render_min_example(&self, area: Rect, buf: &mut Buffer) {
|
||||
let [example1, example2, example3, example4, example5, _] =
|
||||
area.split(&Layout::vertical([Fixed(EXAMPLE_HEIGHT); 6]));
|
||||
area.split(&Layout::vertical([Length(EXAMPLE_HEIGHT); 6]));
|
||||
|
||||
Example::new(&[Percentage(100), Min(0)]).render(example1, buf);
|
||||
Example::new(&[Percentage(100), Min(20)]).render(example2, buf);
|
||||
@ -371,7 +353,7 @@ impl SelectedTab {
|
||||
|
||||
fn render_max_example(&self, area: Rect, buf: &mut Buffer) {
|
||||
let [example1, example2, example3, example4, example5, _] =
|
||||
area.split(&Layout::vertical([Fixed(EXAMPLE_HEIGHT); 6]));
|
||||
area.split(&Layout::vertical([Length(EXAMPLE_HEIGHT); 6]));
|
||||
|
||||
Example::new(&[Percentage(0), Max(0)]).render(example1, buf);
|
||||
Example::new(&[Percentage(0), Max(20)]).render(example2, buf);
|
||||
@ -396,8 +378,8 @@ impl Example {
|
||||
impl Widget for Example {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let [area, _] = area.split(&Layout::vertical([
|
||||
Fixed(ILLUSTRATION_HEIGHT),
|
||||
Fixed(SPACER_HEIGHT),
|
||||
Length(ILLUSTRATION_HEIGHT),
|
||||
Length(SPACER_HEIGHT),
|
||||
]));
|
||||
let blocks = Layout::horizontal(&self.constraints).split(area);
|
||||
|
||||
@ -411,7 +393,6 @@ impl Widget for Example {
|
||||
impl Example {
|
||||
fn illustration(&self, constraint: Constraint, width: u16) -> Paragraph {
|
||||
let color = match constraint {
|
||||
Constraint::Fixed(_) => FIXED_COLOR,
|
||||
Constraint::Length(_) => LENGTH_COLOR,
|
||||
Constraint::Percentage(_) => PERCENTAGE_COLOR,
|
||||
Constraint::Ratio(_, _) => RATIO_COLOR,
|
||||
|
@ -32,8 +32,8 @@ use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
|
||||
|
||||
const EXAMPLE_DATA: &[(&str, &[Constraint])] = &[
|
||||
(
|
||||
"Min(u16) takes any excess space when using `Stretch` or `StretchLast`",
|
||||
&[Fixed(10), Min(10), Max(10), Percentage(10), Ratio(1,10)],
|
||||
"Min(u16) takes any excess space always",
|
||||
&[Length(10), Min(10), Max(10), Percentage(10), Ratio(1,10)],
|
||||
),
|
||||
(
|
||||
"Fill(u16) takes any excess space always",
|
||||
@ -41,39 +41,51 @@ const EXAMPLE_DATA: &[(&str, &[Constraint])] = &[
|
||||
),
|
||||
(
|
||||
"Here's all constraints in one line",
|
||||
&[Fixed(10), Min(10), Max(10), Percentage(10), Ratio(1,10), Fill(1)],
|
||||
&[Length(10), Min(10), Max(10), Percentage(10), Ratio(1,10), Fill(1)],
|
||||
),
|
||||
(
|
||||
"",
|
||||
&[Percentage(50), Percentage(25), Ratio(1, 8), Min(10)],
|
||||
&[Max(50), Min(50)],
|
||||
),
|
||||
(
|
||||
"In `StretchLast`, the last constraint of lowest priority takes excess space",
|
||||
&[Length(20), Fixed(20), Percentage(20)],
|
||||
"",
|
||||
&[Max(20), Length(10)],
|
||||
),
|
||||
("", &[Fixed(20), Percentage(20), Length(20)]),
|
||||
(
|
||||
"",
|
||||
&[Max(20), Length(10)],
|
||||
),
|
||||
(
|
||||
"Min grows always but also allows Fill to grow",
|
||||
&[Percentage(50), Fill(1), Fill(2), Min(50)],
|
||||
),
|
||||
(
|
||||
"In `Legacy`, the last constraint of lowest priority takes excess space",
|
||||
&[Length(20), Length(20), Percentage(20)],
|
||||
),
|
||||
("", &[Length(20), Percentage(20), Length(20)]),
|
||||
("A lowest priority constraint will be broken before a high priority constraint", &[Ratio(1,4), Percentage(20)]),
|
||||
("`Length` is higher priority than `Percentage`", &[Percentage(20), Length(10)]),
|
||||
("`Min/Max` is higher priority than `Length`", &[Length(10), Max(20)]),
|
||||
("", &[Length(100), Min(20)]),
|
||||
("`Fixed` is higher priority than `Min/Max`", &[Max(20), Fixed(10)]),
|
||||
("", &[Min(20), Fixed(90)]),
|
||||
("`Length` is higher priority than `Min/Max`", &[Max(20), Length(10)]),
|
||||
("", &[Min(20), Length(90)]),
|
||||
("Fill is the lowest priority and will fill any excess space", &[Fill(1), Ratio(1, 4)]),
|
||||
("Fill can be used to scale proportionally with other Fill blocks", &[Fill(1), Percentage(20), Fill(2)]),
|
||||
("", &[Ratio(1, 3), Percentage(20), Ratio(2, 3)]),
|
||||
("StretchLast will stretch the last lowest priority constraint\nStretch will only stretch equal weighted constraints", &[Length(20), Length(15)]),
|
||||
("Legacy will stretch the last lowest priority constraint\nStretch will only stretch equal weighted constraints", &[Length(20), Length(15)]),
|
||||
("", &[Percentage(20), Length(15)]),
|
||||
("`Fill(u16)` fills up excess space, but is lower priority to spacers.\ni.e. Fill will only have widths in Flex::Stretch and Flex::StretchLast", &[Fill(1), Fill(1)]),
|
||||
("", &[Length(20), Fixed(20)]),
|
||||
("`Fill(u16)` fills up excess space, but is lower priority to spacers.\ni.e. Fill will only have widths in Flex::Stretch and Flex::Legacy", &[Fill(1), Fill(1)]),
|
||||
("", &[Length(20), Length(20)]),
|
||||
(
|
||||
"When not using `Flex::Stretch` or `Flex::StretchLast`,\n`Min(u16)` and `Max(u16)` collapse to their lowest values",
|
||||
"When not using `Flex::Stretch` or `Flex::Legacy`,\n`Min(u16)` and `Max(u16)` collapse to their lowest values",
|
||||
&[Min(20), Max(20)],
|
||||
),
|
||||
(
|
||||
"",
|
||||
&[Max(20)],
|
||||
),
|
||||
("", &[Min(20), Max(20), Length(20), Fixed(20)]),
|
||||
("", &[Min(20), Max(20), Length(20), Length(20)]),
|
||||
("", &[Fill(0), Fill(0)]),
|
||||
(
|
||||
"`Fill(1)` can be to scale with respect to other `Fill(2)`",
|
||||
@ -124,8 +136,7 @@ struct Example {
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, FromRepr, Display, EnumIter)]
|
||||
enum SelectedTab {
|
||||
#[default]
|
||||
StretchLast,
|
||||
Stretch,
|
||||
Legacy,
|
||||
Start,
|
||||
Center,
|
||||
End,
|
||||
@ -244,7 +255,7 @@ fn example_height() -> u16 {
|
||||
|
||||
impl Widget for App {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let layout = Layout::vertical([Fixed(3), Fixed(1), Fill(0)]);
|
||||
let layout = Layout::vertical([Length(3), Length(1), Fill(0)]);
|
||||
let [tabs, axis, demo] = area.split(&layout);
|
||||
self.tabs().render(tabs, buf);
|
||||
let scroll_needed = self.render_demo(demo, buf);
|
||||
@ -363,8 +374,7 @@ impl SelectedTab {
|
||||
use SelectedTab::*;
|
||||
let text = value.to_string();
|
||||
let color = match value {
|
||||
StretchLast => ORANGE.c400,
|
||||
Stretch => ORANGE.c300,
|
||||
Legacy => ORANGE.c400,
|
||||
Start => SKY.c400,
|
||||
Center => SKY.c300,
|
||||
End => SKY.c200,
|
||||
@ -380,8 +390,7 @@ impl StatefulWidget for SelectedTab {
|
||||
fn render(self, area: Rect, buf: &mut Buffer, spacing: &mut Self::State) {
|
||||
let spacing = *spacing;
|
||||
match self {
|
||||
SelectedTab::StretchLast => self.render_examples(area, buf, Flex::StretchLast, spacing),
|
||||
SelectedTab::Stretch => self.render_examples(area, buf, Flex::Stretch, spacing),
|
||||
SelectedTab::Legacy => self.render_examples(area, buf, Flex::Legacy, spacing),
|
||||
SelectedTab::Start => self.render_examples(area, buf, Flex::Start, spacing),
|
||||
SelectedTab::Center => self.render_examples(area, buf, Flex::Center, spacing),
|
||||
SelectedTab::End => self.render_examples(area, buf, Flex::End, spacing),
|
||||
@ -419,7 +428,7 @@ impl Example {
|
||||
impl Widget for Example {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let title_height = get_description_height(&self.description);
|
||||
let layout = Layout::vertical([Fixed(title_height), Fill(0)]);
|
||||
let layout = Layout::vertical([Length(title_height), Fill(0)]);
|
||||
let [title, illustrations] = area.split(&layout);
|
||||
|
||||
let (blocks, spacers) = Layout::horizontal(&self.constraints)
|
||||
@ -512,7 +521,6 @@ impl Example {
|
||||
fn color_for_constraint(constraint: Constraint) -> Color {
|
||||
use tailwind::*;
|
||||
match constraint {
|
||||
Constraint::Fixed(_) => RED.c900,
|
||||
Constraint::Min(_) => BLUE.c900,
|
||||
Constraint::Max(_) => BLUE.c800,
|
||||
Constraint::Length(_) => SLATE.c700,
|
||||
|
@ -218,7 +218,6 @@ fn constraint_label(constraint: Constraint) -> String {
|
||||
Max(n) => format!("{n}"),
|
||||
Percentage(n) => format!("{n}"),
|
||||
Fill(n) => format!("{n}"),
|
||||
Fixed(n) => format!("{n}"),
|
||||
Ratio(a, b) => format!("{a}:{b}"),
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ mod layout;
|
||||
mod margin;
|
||||
mod position;
|
||||
mod rect;
|
||||
mod segment_size;
|
||||
mod size;
|
||||
|
||||
pub use alignment::Alignment;
|
||||
@ -20,8 +19,4 @@ pub use layout::Layout;
|
||||
pub use margin::Margin;
|
||||
pub use position::Position;
|
||||
pub use rect::*;
|
||||
#[cfg(feature = "unstable-segment-size")]
|
||||
pub use segment_size::SegmentSize;
|
||||
#[cfg(not(feature = "unstable-segment-size"))]
|
||||
pub(crate) use segment_size::SegmentSize;
|
||||
pub use size::Size;
|
||||
|
@ -15,13 +15,12 @@ use strum::EnumIs;
|
||||
///
|
||||
/// Constraints are prioritized in the following order:
|
||||
///
|
||||
/// 1. [`Constraint::Fixed`]
|
||||
/// 2. [`Constraint::Min`]
|
||||
/// 3. [`Constraint::Max`]
|
||||
/// 4. [`Constraint::Length`]
|
||||
/// 5. [`Constraint::Percentage`]
|
||||
/// 6. [`Constraint::Ratio`]
|
||||
/// 7. [`Constraint::Fill`]
|
||||
/// 1. [`Constraint::Min`]
|
||||
/// 2. [`Constraint::Max`]
|
||||
/// 3. [`Constraint::Length`]
|
||||
/// 4. [`Constraint::Percentage`]
|
||||
/// 5. [`Constraint::Ratio`]
|
||||
/// 6. [`Constraint::Fill`]
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -32,9 +31,6 @@ use strum::EnumIs;
|
||||
/// // Create a layout with specified lengths for each element
|
||||
/// let constraints = Constraint::from_lengths([10, 20, 10]);
|
||||
///
|
||||
/// // Create a layout with specified fixed lengths for each element
|
||||
/// let constraints = Constraint::from_fixed_lengths([10, 20, 10]);
|
||||
///
|
||||
/// // Create a centered layout using ratio or percentage constraints
|
||||
/// let constraints = Constraint::from_ratios([(1, 4), (1, 2), (1, 4)]);
|
||||
/// let constraints = Constraint::from_percentages([25, 50, 25]);
|
||||
@ -50,29 +46,6 @@ use strum::EnumIs;
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumIs)]
|
||||
pub enum Constraint {
|
||||
/// Applies a fixed size to the element
|
||||
///
|
||||
/// The element size is set to the specified amount.
|
||||
/// [`Constraint::Fixed`] will take precedence over all other constraints.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// `[Fixed(40), Fill(1)]`
|
||||
///
|
||||
/// ```plain
|
||||
/// ┌──────────────────────────────────────┐┌────────┐
|
||||
/// │ 40 px ││ 10 px │
|
||||
/// └──────────────────────────────────────┘└────────┘
|
||||
/// ```
|
||||
///
|
||||
/// `[Fixed(20), Fixed(20), Fill(1)]`
|
||||
///
|
||||
/// ```plain
|
||||
/// ┌──────────────────┐┌──────────────────┐┌────────┐
|
||||
/// │ 20 px ││ 20 px ││ 10 px │
|
||||
/// └──────────────────┘└──────────────────┘└────────┘
|
||||
/// ```
|
||||
Fixed(u16),
|
||||
/// Applies a minimum size constraint to the element
|
||||
///
|
||||
/// The element size is set to at least the specified amount.
|
||||
@ -95,6 +68,7 @@ pub enum Constraint {
|
||||
/// └──────────────────────────────────────┘└────────┘
|
||||
/// ```
|
||||
Min(u16),
|
||||
|
||||
/// Applies a maximum size constraint to the element
|
||||
///
|
||||
/// The element size is set to at most the specified amount.
|
||||
@ -117,21 +91,22 @@ pub enum Constraint {
|
||||
/// └──────────────────────────────────────┘└────────┘
|
||||
/// ```
|
||||
Max(u16),
|
||||
|
||||
/// Applies a length constraint to the element
|
||||
///
|
||||
/// The element size is set to the specified amount.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// `[Length(20), Fixed(20)]`
|
||||
/// `[Length(20), Length(20)]`
|
||||
///
|
||||
/// ```plain
|
||||
/// ┌────────────────────────────┐┌──────────────────┐
|
||||
/// │ 30 px ││ 20 px │
|
||||
/// └────────────────────────────┘└──────────────────┘
|
||||
/// ┌──────────────────┐┌──────────────────┐
|
||||
/// │ 20 px ││ 20 px │
|
||||
/// └──────────────────┘└──────────────────┘
|
||||
/// ```
|
||||
///
|
||||
/// `[Length(20), Length(20)]`
|
||||
/// `[Length(20), Length(30)]`
|
||||
///
|
||||
/// ```plain
|
||||
/// ┌──────────────────┐┌────────────────────────────┐
|
||||
@ -139,6 +114,7 @@ pub enum Constraint {
|
||||
/// └──────────────────┘└────────────────────────────┘
|
||||
/// ```
|
||||
Length(u16),
|
||||
|
||||
/// Applies a percentage of the available space to the element
|
||||
///
|
||||
/// Converts the given percentage to a floating-point value and multiplies that with area.
|
||||
@ -162,6 +138,7 @@ pub enum Constraint {
|
||||
/// └───────────────────────┘└───────────────────────┘
|
||||
/// ```
|
||||
Percentage(u16),
|
||||
|
||||
/// Applies a ratio of the available space to the element
|
||||
///
|
||||
/// Converts the given ratio to a floating-point value and multiplies that with area.
|
||||
@ -185,6 +162,7 @@ pub enum Constraint {
|
||||
/// └───────────┘└──────────┘└───────────┘└──────────┘
|
||||
/// ```
|
||||
Ratio(u32, u32),
|
||||
|
||||
/// Applies the scaling factor proportional to all other [`Constraint::Fill`] elements
|
||||
/// to fill excess space
|
||||
///
|
||||
@ -232,7 +210,6 @@ impl Constraint {
|
||||
(percentage * length).min(length) as u16
|
||||
}
|
||||
Constraint::Length(l) => length.min(l),
|
||||
Constraint::Fixed(l) => length.min(l),
|
||||
Constraint::Fill(l) => length.min(l),
|
||||
Constraint::Max(m) => length.min(m),
|
||||
Constraint::Min(m) => length.max(m),
|
||||
@ -256,26 +233,6 @@ impl Constraint {
|
||||
lengths.into_iter().map(Constraint::Length).collect_vec()
|
||||
}
|
||||
|
||||
/// Convert an iterator of fixed lengths into a vector of constraints
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// # let area = Rect::default();
|
||||
/// let constraints = Constraint::from_fixed_lengths([1, 2, 3]);
|
||||
/// let layout = Layout::default().constraints(constraints).split(area);
|
||||
/// ```
|
||||
pub fn from_fixed_lengths<T>(fixed_lengths: T) -> Vec<Constraint>
|
||||
where
|
||||
T: IntoIterator<Item = u16>,
|
||||
{
|
||||
fixed_lengths
|
||||
.into_iter()
|
||||
.map(Constraint::Fixed)
|
||||
.collect_vec()
|
||||
}
|
||||
|
||||
/// Convert an iterator of ratios into a vector of constraints
|
||||
///
|
||||
/// # Examples
|
||||
@ -415,7 +372,6 @@ impl Display for Constraint {
|
||||
Constraint::Percentage(p) => write!(f, "Percentage({})", p),
|
||||
Constraint::Ratio(n, d) => write!(f, "Ratio({}, {})", n, d),
|
||||
Constraint::Length(l) => write!(f, "Length({})", l),
|
||||
Constraint::Fixed(l) => write!(f, "Fixed({})", l),
|
||||
Constraint::Fill(l) => write!(f, "Fill({})", l),
|
||||
Constraint::Max(m) => write!(f, "Max({})", m),
|
||||
Constraint::Min(m) => write!(f, "Min({})", m),
|
||||
@ -452,17 +408,6 @@ mod tests {
|
||||
assert_eq!(Constraint::from_lengths(vec![1, 2, 3]), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_fixed_lengths() {
|
||||
let expected = [
|
||||
Constraint::Fixed(1),
|
||||
Constraint::Fixed(2),
|
||||
Constraint::Fixed(3),
|
||||
];
|
||||
assert_eq!(Constraint::from_fixed_lengths([1, 2, 3]), expected);
|
||||
assert_eq!(Constraint::from_fixed_lengths(vec![1, 2, 3]), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_ratios() {
|
||||
let expected = [
|
||||
|
@ -1,4 +1,4 @@
|
||||
use strum::{Display, EnumString};
|
||||
use strum::{Display, EnumIs, EnumString};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use super::constraint::Constraint;
|
||||
@ -7,15 +7,14 @@ use super::constraint::Constraint;
|
||||
///
|
||||
/// This enumeration controls the distribution of space when layout constraints are met.
|
||||
///
|
||||
/// - `StretchLast`: Fills the available space within the container, putting excess space into the
|
||||
/// last element.
|
||||
/// - `Stretch`: Always fills the available space within the container.
|
||||
/// - `Legacy`: Fills the available space within the container, putting excess space into the last
|
||||
/// element.
|
||||
/// - `Start`: Aligns items to the start of the container.
|
||||
/// - `End`: Aligns items to the end of the container.
|
||||
/// - `Center`: Centers items within the container.
|
||||
/// - `SpaceBetween`: Adds excess space between each element.
|
||||
/// - `SpaceAround`: Adds excess space around each element.
|
||||
#[derive(Copy, Debug, Default, Display, EnumString, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Copy, Debug, Default, Display, EnumString, Clone, Eq, PartialEq, Hash, EnumIs)]
|
||||
pub enum Flex {
|
||||
/// Fills the available space within the container, putting excess space into the last
|
||||
/// constraint of the lowest priority. This matches the default behavior of ratatui and tui
|
||||
@ -24,13 +23,12 @@ pub enum Flex {
|
||||
/// The following examples illustrate the allocation of excess in various combinations of
|
||||
/// constraints. As a refresher, the priorities of constraints are as follows:
|
||||
///
|
||||
/// 1. [`Constraint::Fixed`]
|
||||
/// 2. [`Constraint::Min`]
|
||||
/// 3. [`Constraint::Max`]
|
||||
/// 4. [`Constraint::Length`]
|
||||
/// 5. [`Constraint::Percentage`]
|
||||
/// 6. [`Constraint::Ratio`]
|
||||
/// 7. [`Constraint::Fill`]
|
||||
/// 1. [`Constraint::Min`]
|
||||
/// 2. [`Constraint::Max`]
|
||||
/// 3. [`Constraint::Length`]
|
||||
/// 4. [`Constraint::Percentage`]
|
||||
/// 5. [`Constraint::Ratio`]
|
||||
/// 6. [`Constraint::Fill`]
|
||||
///
|
||||
/// When every constraint is `Length`, the last element gets the excess.
|
||||
///
|
||||
@ -42,55 +40,13 @@ pub enum Flex {
|
||||
/// ^^^^^^^^^^^^^^^^ EXCESS ^^^^^^^^^^^^^^^^
|
||||
/// ```
|
||||
///
|
||||
/// If we replace the constraint at the end with a `Fixed`, because it has a
|
||||
/// higher priority, the last constraint with the lowest priority, i.e. the last
|
||||
/// `Length` gets the excess.
|
||||
///
|
||||
/// ```plain
|
||||
/// <----------------------------------- 80 px ------------------------------------>
|
||||
/// ┌──────20 px───────┐┌────────────────40 px─────────────────┐┌──────20 px───────┐
|
||||
/// │ Length(20) ││ Length(20) ││ Fixed(20) │
|
||||
/// └──────────────────┘└──────────────────────────────────────┘└──────────────────┘
|
||||
/// ^^^^^^^^^^^^^^^^ EXCESS ^^^^^^^^^^^^^^^^
|
||||
/// ```
|
||||
///
|
||||
/// Violating a `Max` is lower priority than `Fixed` but higher
|
||||
/// than `Length`.
|
||||
///
|
||||
/// ```plain
|
||||
/// <----------------------------------- 80 px ------------------------------------>
|
||||
/// ┌────────────────40 px─────────────────┐┌──────20 px───────┐┌──────20 px───────┐
|
||||
/// │ Length(20) ││ Max(20) ││ Fixed(20) │
|
||||
/// └──────────────────────────────────────┘└──────────────────┘└──────────────────┘
|
||||
/// ^^^^^^^^^^^^^^^^ EXCESS ^^^^^^^^^^^^^^^^
|
||||
/// ```
|
||||
///
|
||||
/// It's important to note that while not violating a `Min` or `Max` constraint is
|
||||
/// prioritized higher than a `Length`, `Min` and `Max` constraints allow for a range
|
||||
/// of values and excess can (and will) be dumped into these ranges first, if possible,
|
||||
/// even if it not the last constraint.
|
||||
///
|
||||
/// ```plain
|
||||
/// <----------------------------------- 80 px ------------------------------------>
|
||||
/// ┌──────20 px───────┐┌────────────────40 px─────────────────┐┌──────20 px───────┐
|
||||
/// │ Length(20) ││ Min(20) ││ Fixed(20) │
|
||||
/// └──────────────────┘└──────────────────────────────────────┘└──────────────────┘
|
||||
/// ^^^^^^^^^^^^^^^^ EXCESS ^^^^^^^^^^^^^^^^
|
||||
///
|
||||
/// <----------------------------------- 80 px ------------------------------------>
|
||||
/// ┌────────────────40 px─────────────────┐┌──────20 px───────┐┌──────20 px───────┐
|
||||
/// │ Min(20) ││ Length(20) ││ Fixed(20) │
|
||||
/// └──────────────────────────────────────┘└──────────────────┘└──────────────────┘
|
||||
/// ^^^^^^^^^^^^^^^^ EXCESS ^^^^^^^^^^^^^^^^
|
||||
/// ```
|
||||
///
|
||||
/// Fill constraints have the lowest priority amongst all the constraints and hence
|
||||
/// will always take up any excess space available.
|
||||
///
|
||||
/// ```plain
|
||||
/// <----------------------------------- 80 px ------------------------------------>
|
||||
/// ┌──────20 px───────┐┌──────20 px───────┐┌──────20 px───────┐┌──────20 px───────┐
|
||||
/// │ Fill(0) ││ Min(20) ││ Length(20) ││ Fixed(20) │
|
||||
/// │ Fill(0) ││ Max(20) ││ Length(20) ││ Length(20) │
|
||||
/// └──────────────────┘└──────────────────┘└──────────────────┘└──────────────────┘
|
||||
/// ^^^^^^ EXCESS ^^^^^^
|
||||
/// ```
|
||||
@ -99,11 +55,6 @@ pub enum Flex {
|
||||
///
|
||||
/// ```plain
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
/// ┌───────────30 px────────────┐┌───────────30 px────────────┐┌──────20 px───────┐
|
||||
/// │ Percentage(20) ││ Length(20) ││ Fixed(20) │
|
||||
/// └────────────────────────────┘└────────────────────────────┘└──────────────────┘
|
||||
///
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
/// ┌──────────────────────────60 px───────────────────────────┐┌──────20 px───────┐
|
||||
/// │ Min(20) ││ Max(20) │
|
||||
/// └──────────────────────────────────────────────────────────┘└──────────────────┘
|
||||
@ -113,30 +64,7 @@ pub enum Flex {
|
||||
/// │ Max(20) │
|
||||
/// └──────────────────────────────────────────────────────────────────────────────┘
|
||||
/// ```
|
||||
#[default]
|
||||
StretchLast,
|
||||
|
||||
/// Always fills the available space within the container.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```plain
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
/// ┌────16 px─────┐┌──────────────────44 px───────────────────┐┌──────20 px───────┐
|
||||
/// │Percentage(20)││ Length(20) ││ Fixed(20) │
|
||||
/// └──────────────┘└──────────────────────────────────────────┘└──────────────────┘
|
||||
///
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
/// ┌──────────────────────────60 px───────────────────────────┐┌──────20 px───────┐
|
||||
/// │ Min(20) ││ Max(20) │
|
||||
/// └──────────────────────────────────────────────────────────┘└──────────────────┘
|
||||
///
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
/// ┌────────────────────────────────────80 px─────────────────────────────────────┐
|
||||
/// │ Max(20) │
|
||||
/// └──────────────────────────────────────────────────────────────────────────────┘
|
||||
/// ```
|
||||
Stretch,
|
||||
Legacy,
|
||||
|
||||
/// Aligns items to the start of the container.
|
||||
///
|
||||
@ -150,7 +78,7 @@ pub enum Flex {
|
||||
///
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
/// ┌──────20 px───────┐┌──────20 px───────┐
|
||||
/// │ Min(20) ││ Max(20) │
|
||||
/// │ Max(20) ││ Max(20) │
|
||||
/// └──────────────────┘└──────────────────┘
|
||||
///
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
@ -158,6 +86,7 @@ pub enum Flex {
|
||||
/// │ Max(20) │
|
||||
/// └──────────────────┘
|
||||
/// ```
|
||||
#[default]
|
||||
Start,
|
||||
|
||||
/// Aligns items to the end of the container.
|
||||
@ -167,12 +96,12 @@ pub enum Flex {
|
||||
/// ```plain
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
/// ┌────16 px─────┐┌──────20 px───────┐┌──────20 px───────┐
|
||||
/// │Percentage(20)││ Length(20) ││ Fixed(20) │
|
||||
/// │Percentage(20)││ Length(20) ││ Length(20) │
|
||||
/// └──────────────┘└──────────────────┘└──────────────────┘
|
||||
///
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
/// ┌──────20 px───────┐┌──────20 px───────┐
|
||||
/// │ Min(20) ││ Max(20) │
|
||||
/// │ Max(20) ││ Max(20) │
|
||||
/// └──────────────────┘└──────────────────┘
|
||||
///
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
@ -189,12 +118,12 @@ pub enum Flex {
|
||||
/// ```plain
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
/// ┌────16 px─────┐┌──────20 px───────┐┌──────20 px───────┐
|
||||
/// │Percentage(20)││ Length(20) ││ Fixed(20) │
|
||||
/// │Percentage(20)││ Length(20) ││ Length(20) │
|
||||
/// └──────────────┘└──────────────────┘└──────────────────┘
|
||||
///
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
/// ┌──────20 px───────┐┌──────20 px───────┐
|
||||
/// │ Min(20) ││ Max(20) │
|
||||
/// │ Max(20) ││ Max(20) │
|
||||
/// └──────────────────┘└──────────────────┘
|
||||
///
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
@ -211,12 +140,12 @@ pub enum Flex {
|
||||
/// ```plain
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
/// ┌────16 px─────┐ ┌──────20 px───────┐ ┌──────20 px───────┐
|
||||
/// │Percentage(20)│ │ Length(20) │ │ Fixed(20) │
|
||||
/// │Percentage(20)│ │ Length(20) │ │ Length(20) │
|
||||
/// └──────────────┘ └──────────────────┘ └──────────────────┘
|
||||
///
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
/// ┌──────20 px───────┐ ┌──────20 px───────┐
|
||||
/// │ Min(20) │ │ Max(20) │
|
||||
/// │ Max(20) │ │ Max(20) │
|
||||
/// └──────────────────┘ └──────────────────┘
|
||||
///
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
@ -233,12 +162,12 @@ pub enum Flex {
|
||||
/// ```plain
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
/// ┌────16 px─────┐ ┌──────20 px───────┐ ┌──────20 px───────┐
|
||||
/// │Percentage(20)│ │ Length(20) │ │ Fixed(20) │
|
||||
/// │Percentage(20)│ │ Length(20) │ │ Length(20) │
|
||||
/// └──────────────┘ └──────────────────┘ └──────────────────┘
|
||||
///
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
/// ┌──────20 px───────┐ ┌──────20 px───────┐
|
||||
/// │ Min(20) │ │ Max(20) │
|
||||
/// │ Max(20) │ │ Max(20) │
|
||||
/// └──────────────────┘ └──────────────────┘
|
||||
///
|
||||
/// <------------------------------------80 px------------------------------------->
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::{cell::RefCell, collections::HashMap, iter, num::NonZeroUsize, rc::Rc, sync::OnceLock};
|
||||
|
||||
use cassowary::{
|
||||
strength::REQUIRED,
|
||||
strength::{REQUIRED, WEAK},
|
||||
AddConstraintError, Expression, Solver, Variable,
|
||||
WeightedRelation::{EQ, GE, LE},
|
||||
};
|
||||
@ -9,10 +9,10 @@ use itertools::Itertools;
|
||||
use lru::LruCache;
|
||||
|
||||
use self::strengths::{
|
||||
FILL_GROW, FIXED_SIZE_EQ, LENGTH_SIZE_EQ, MAX_SIZE_EQ, MAX_SIZE_LE, MIN_SIZE_EQ, MIN_SIZE_GE,
|
||||
FILL_GROW, LENGTH_SIZE_EQ, MAX_SIZE_EQ, MAX_SIZE_LE, MIN_SIZE_EQ, MIN_SIZE_GE,
|
||||
PERCENTAGE_SIZE_EQ, RATIO_SIZE_EQ, *,
|
||||
};
|
||||
use super::{Flex, SegmentSize};
|
||||
use super::Flex;
|
||||
use crate::prelude::*;
|
||||
|
||||
type Rects = Rc<[Rect]>;
|
||||
@ -40,25 +40,21 @@ thread_local! {
|
||||
///
|
||||
/// A layout is composed of:
|
||||
/// - a direction (horizontal or vertical)
|
||||
/// - a set of constraints (length, ratio, percentage, min, max)
|
||||
/// - a set of constraints (length, ratio, percentage, fill, min, max)
|
||||
/// - a margin (horizontal and vertical), the space between the edge of the main area and the split
|
||||
/// areas
|
||||
/// - extra options for segment size preferences
|
||||
/// - a flex option
|
||||
/// - a spacing option
|
||||
///
|
||||
/// The algorithm used to compute the layout is based on the [`cassowary-rs`] solver. It is a simple
|
||||
/// linear solver that can be used to solve linear equations and inequalities. In our case, we
|
||||
/// define a set of constraints that are applied to split the provided area into Rects aligned in a
|
||||
/// single direction, and the solver computes the values of the position and sizes that satisfy as
|
||||
/// many of the constraints as possible.
|
||||
///
|
||||
/// By default, the last chunk of the computed layout is expanded to fill the remaining space. To
|
||||
/// avoid this behavior, add an unused `Constraint::Min(0)` as the last constraint. There is also an
|
||||
/// unstable API to prefer equal chunks if other constraints are all satisfied, see [`SegmentSize`]
|
||||
/// for more info.
|
||||
/// many of the constraints in order of their priorities.
|
||||
///
|
||||
/// When the layout is computed, the result is cached in a thread-local cache, so that subsequent
|
||||
/// calls with the same parameters are faster. The cache is a simple HashMap, and grows
|
||||
/// indefinitely. (See <https://github.com/ratatui-org/ratatui/issues/402> for more information)
|
||||
/// calls with the same parameters are faster. The cache is a LruCache, and the size of the cache
|
||||
/// can be configured using [`Layout::init_cache()`].
|
||||
///
|
||||
/// # Constructors
|
||||
///
|
||||
@ -132,7 +128,7 @@ impl Layout {
|
||||
/// Default values for the other fields are:
|
||||
///
|
||||
/// - `margin`: 0, 0
|
||||
/// - `flex`: Flex::StretchLast
|
||||
/// - `flex`: Flex::Start
|
||||
/// - `spacing`: 0
|
||||
///
|
||||
/// # Examples
|
||||
@ -367,9 +363,7 @@ impl Layout {
|
||||
///
|
||||
/// * `flex`: A `Flex` enum value that represents the flex behavior of the layout. It can be one
|
||||
/// of the following:
|
||||
/// - [`Flex::Stretch`]: The items are stretched equally after satisfying constraints to fill
|
||||
/// excess space.
|
||||
/// - [`Flex::StretchLast`]: The last item is stretched to fill the excess space.
|
||||
/// - [`Flex::Legacy`]: The last item is stretched to fill the excess space.
|
||||
/// - [`Flex::Start`]: The items are aligned to the start of the layout.
|
||||
/// - [`Flex::Center`]: The items are aligned to the center of the layout.
|
||||
/// - [`Flex::End`]: The items are aligned to the end of the layout.
|
||||
@ -390,7 +384,7 @@ impl Layout {
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::layout::{Flex, Layout, Constraint::*};
|
||||
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).flex(Flex::Stretch);
|
||||
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).flex(Flex::Legacy);
|
||||
/// ```
|
||||
pub const fn flex(mut self, flex: Flex) -> Layout {
|
||||
self.flex = flex;
|
||||
@ -421,34 +415,6 @@ impl Layout {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether chunks should be of equal size.
|
||||
///
|
||||
/// This determines how the space is distributed when the constraints are satisfied. By default,
|
||||
/// the last chunk is expanded to fill the remaining space, but this can be changed to prefer
|
||||
/// equal chunks or to not distribute extra space at all (which is the default used for laying
|
||||
/// out the columns for [`Table`] widgets).
|
||||
///
|
||||
/// This function exists for backwards compatibility reasons. Use [`Layout::flex`] instead.
|
||||
///
|
||||
/// - `Flex::StretchLast` does now what `SegmentSize::LastTakesRemainder` did (default).
|
||||
/// - `Flex::Stretch` does now what `SegmentSize::EvenDistribution` did.
|
||||
/// - `Flex::Start` does now what `SegmentSize::None` did.
|
||||
#[stability::unstable(
|
||||
feature = "segment-size",
|
||||
reason = "The name for this feature is not final and may change in the future",
|
||||
issue = "https://github.com/ratatui-org/ratatui/issues/536"
|
||||
)]
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
#[deprecated(since = "0.26.0", note = "You should use `Layout::flex` instead.")]
|
||||
pub const fn segment_size(self, segment_size: SegmentSize) -> Layout {
|
||||
let flex = match segment_size {
|
||||
SegmentSize::None => Flex::Start,
|
||||
SegmentSize::LastTakesRemainder => Flex::StretchLast,
|
||||
SegmentSize::EvenDistribution => Flex::Stretch,
|
||||
};
|
||||
self.flex(flex)
|
||||
}
|
||||
|
||||
/// Wrapper function around the cassowary-rs solver to be able to split a given area into
|
||||
/// smaller ones based on the preferred widths or heights and the direction.
|
||||
///
|
||||
@ -584,7 +550,7 @@ impl Layout {
|
||||
// │ │ │ │ │ │ │ │
|
||||
// V V V V V V V V
|
||||
// ┌ ┐┌──────────────────┐┌ ┐┌──────────────────┐┌ ┐┌──────────────────┐┌ ┐
|
||||
// │ Fixed(20) │ │ Min(20) │ │ Max(20) │
|
||||
// │ Max(20) │ │ Max(20) │ │ Max(20) │
|
||||
// └ ┘└──────────────────┘└ ┘└──────────────────┘└ ┘└──────────────────┘└ ┘
|
||||
// ^ ^ ^ ^ ^ ^ ^ ^
|
||||
// │ │ │ │ │ │ │ │
|
||||
@ -618,9 +584,15 @@ impl Layout {
|
||||
let area_size = Element::from((*variables.first().unwrap(), *variables.last().unwrap()));
|
||||
configure_area(&mut solver, area_size, area_start, area_end)?;
|
||||
configure_variable_constraints(&mut solver, &variables, area_size)?;
|
||||
configure_flex_constraints(&mut solver, area_size, &spacers, &segments, flex, spacing)?;
|
||||
configure_constraints(&mut solver, area_size, &segments, constraints)?;
|
||||
configure_fill_constraints(&mut solver, &segments, constraints)?;
|
||||
configure_flex_constraints(&mut solver, area_size, &spacers, flex, spacing)?;
|
||||
configure_constraints(&mut solver, area_size, &segments, constraints, flex)?;
|
||||
configure_fill_constraints(&mut solver, &segments, constraints, flex)?;
|
||||
|
||||
if !flex.is_legacy() {
|
||||
for (left, right) in segments.iter().tuple_windows() {
|
||||
solver.add_constraint(left.has_size(right, WEAK / 100.0))?;
|
||||
}
|
||||
}
|
||||
|
||||
// `solver.fetch_changes()` can only be called once per solve
|
||||
let changes: HashMap<Variable, f64> = solver.fetch_changes().iter().copied().collect();
|
||||
@ -668,19 +640,21 @@ fn configure_constraints(
|
||||
area: Element,
|
||||
segments: &[Element],
|
||||
constraints: &[Constraint],
|
||||
flex: Flex,
|
||||
) -> Result<(), AddConstraintError> {
|
||||
for (&constraint, &element) in constraints.iter().zip(segments.iter()) {
|
||||
match constraint {
|
||||
Constraint::Fixed(length) => {
|
||||
solver.add_constraint(element.has_int_size(length, FIXED_SIZE_EQ))?
|
||||
}
|
||||
Constraint::Max(max) => {
|
||||
solver.add_constraint(element.has_max_size(max, MAX_SIZE_LE))?;
|
||||
solver.add_constraint(element.has_int_size(max, MAX_SIZE_EQ))?;
|
||||
}
|
||||
Constraint::Min(min) => {
|
||||
solver.add_constraint(element.has_min_size(min, MIN_SIZE_GE))?;
|
||||
solver.add_constraint(element.has_int_size(min, MIN_SIZE_EQ))?;
|
||||
if flex.is_legacy() {
|
||||
solver.add_constraint(element.has_int_size(min, MIN_SIZE_EQ))?;
|
||||
} else {
|
||||
solver.add_constraint(element.has_size(area, FILL_GROW))?;
|
||||
}
|
||||
}
|
||||
Constraint::Length(length) => {
|
||||
solver.add_constraint(element.has_int_size(length, LENGTH_SIZE_EQ))?
|
||||
@ -707,13 +681,21 @@ fn configure_flex_constraints(
|
||||
solver: &mut Solver,
|
||||
area: Element,
|
||||
spacers: &[Element],
|
||||
segments: &[Element],
|
||||
flex: Flex,
|
||||
spacing: u16,
|
||||
) -> Result<(), AddConstraintError> {
|
||||
let spacers_except_first_and_last = spacers.get(1..spacers.len() - 1).unwrap_or(&[]);
|
||||
let spacing = f64::from(spacing);
|
||||
match flex {
|
||||
Flex::Legacy => {
|
||||
for spacer in spacers_except_first_and_last.iter() {
|
||||
solver.add_constraint(spacer.has_size(spacing, SPACER_SIZE_EQ))?;
|
||||
}
|
||||
if let (Some(first), Some(last)) = (spacers.first(), spacers.last()) {
|
||||
solver.add_constraint(first.is_empty())?;
|
||||
solver.add_constraint(last.is_empty())?;
|
||||
}
|
||||
}
|
||||
// all spacers are the same size and will grow to fill any remaining space after the
|
||||
// constraints are satisfied
|
||||
Flex::SpaceAround => {
|
||||
@ -739,27 +721,6 @@ fn configure_flex_constraints(
|
||||
solver.add_constraint(last.is_empty())?;
|
||||
}
|
||||
}
|
||||
Flex::StretchLast => {
|
||||
for spacer in spacers_except_first_and_last.iter() {
|
||||
solver.add_constraint(spacer.has_size(spacing, SPACER_SIZE_EQ))?;
|
||||
}
|
||||
if let (Some(first), Some(last)) = (spacers.first(), spacers.last()) {
|
||||
solver.add_constraint(first.is_empty())?;
|
||||
solver.add_constraint(last.is_empty())?;
|
||||
}
|
||||
}
|
||||
Flex::Stretch => {
|
||||
for spacer in spacers_except_first_and_last {
|
||||
solver.add_constraint(spacer.has_size(spacing, SPACER_SIZE_EQ))?;
|
||||
}
|
||||
for (left, right) in segments.iter().tuple_combinations() {
|
||||
solver.add_constraint(left.has_size(right, GROW))?;
|
||||
}
|
||||
if let (Some(first), Some(last)) = (spacers.first(), spacers.last()) {
|
||||
solver.add_constraint(first.is_empty())?;
|
||||
solver.add_constraint(last.is_empty())?;
|
||||
}
|
||||
}
|
||||
Flex::Start => {
|
||||
for spacer in spacers_except_first_and_last {
|
||||
solver.add_constraint(spacer.has_size(spacing, SPACER_SIZE_EQ))?;
|
||||
@ -800,7 +761,7 @@ fn configure_flex_constraints(
|
||||
/// │abcdef││abcdef│
|
||||
/// └──────┘└──────┘
|
||||
///
|
||||
/// [Fill(1), Fill(2)]
|
||||
/// [Min(0), Fill(2)]
|
||||
/// ┌──────┐┌────────────┐
|
||||
/// │abcdef││abcdefabcdef│
|
||||
/// └──────┘└────────────┘
|
||||
@ -810,44 +771,29 @@ fn configure_fill_constraints(
|
||||
solver: &mut Solver,
|
||||
segments: &[Element],
|
||||
constraints: &[Constraint],
|
||||
flex: Flex,
|
||||
) -> Result<(), AddConstraintError> {
|
||||
for ((&l_constraint, &l_element), (&r_constraint, &r_element)) in constraints
|
||||
for ((&left_constraint, &left_element), (&right_constraint, &right_element)) in constraints
|
||||
.iter()
|
||||
.zip(segments.iter())
|
||||
.filter(|(c, _)| c.is_fill())
|
||||
.filter(|(c, _)| c.is_fill() || (!flex.is_legacy() && c.is_min()))
|
||||
.tuple_combinations()
|
||||
{
|
||||
// `Fill` will only expand into _excess_ available space. You can think of
|
||||
// `Fill` element sizes as starting from `0` and incrementally
|
||||
// increasing while proportionally matching other `Fill` spaces AND
|
||||
// also meeting all other constraints.
|
||||
if let (Constraint::Fill(l_scaling_factor), Constraint::Fill(r_scaling_factor)) =
|
||||
(l_constraint, r_constraint)
|
||||
{
|
||||
// because of the way cassowary works, we need to use `*` instead of `/`
|
||||
// l_size / l_scaling_factor == l_size / l_scaling_factor
|
||||
// ≡
|
||||
// l_size * r_scaling_factor == r_size * r_scaling_factor
|
||||
//
|
||||
// we make `0` act as `1e-6`.
|
||||
// this gives us a numerically stable solution and more consistent behavior along
|
||||
// the number line
|
||||
//
|
||||
// I choose `1e-6` because we want a value that is as close to `0.0` as possible
|
||||
// without causing it to behave like `0.0`. `1e-9` for example gives the same
|
||||
// results as true `0.0`.
|
||||
// I found `1e-6` worked well in all the various combinations of constraints I
|
||||
// experimented with.
|
||||
let (l_scaling_factor, r_scaling_factor) = (
|
||||
f64::from(l_scaling_factor).max(1e-6),
|
||||
f64::from(r_scaling_factor).max(1e-6),
|
||||
);
|
||||
solver.add_constraint(
|
||||
(r_scaling_factor * l_element.size())
|
||||
| EQ(FILL_SCALING_EQ)
|
||||
| (l_scaling_factor * r_element.size()),
|
||||
)?;
|
||||
}
|
||||
let left_scaling_factor = match left_constraint {
|
||||
Constraint::Fill(scale) => f64::from(scale).max(1e-6),
|
||||
Constraint::Min(_) => 1.0,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let right_scaling_factor = match right_constraint {
|
||||
Constraint::Fill(scale) => f64::from(scale).max(1e-6),
|
||||
Constraint::Min(_) => 1.0,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
solver.add_constraint(
|
||||
(right_scaling_factor * left_element.size())
|
||||
| EQ(GROW)
|
||||
| (left_scaling_factor * right_element.size()),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -967,20 +913,6 @@ mod strengths {
|
||||
/// └ ┘└───┘└ ┘└───┘└ ┘
|
||||
pub const SPACER_SIZE_EQ: f64 = REQUIRED - 1.0;
|
||||
|
||||
/// The strength to apply to Fill constraints so that their sizes are proportional.
|
||||
///
|
||||
/// ┌───────────────┐┌───────────────┐
|
||||
/// │ Fill(x) ││ Fill(x) │
|
||||
/// └───────────────┘└───────────────┘
|
||||
pub const FILL_SCALING_EQ: f64 = REQUIRED - 1.0;
|
||||
|
||||
/// The strength to apply to Fixed constraints.
|
||||
///
|
||||
/// ┌──────────┐
|
||||
/// │Fixed(==x)│
|
||||
/// └──────────┘
|
||||
pub const FIXED_SIZE_EQ: f64 = REQUIRED / 10.0;
|
||||
|
||||
/// The strength to apply to Min inequality constraints.
|
||||
///
|
||||
/// ┌────────┐
|
||||
@ -1016,13 +948,6 @@ mod strengths {
|
||||
/// └────────────┘
|
||||
pub const RATIO_SIZE_EQ: f64 = MEDIUM;
|
||||
|
||||
/// The strength to apply to Min equality constraints.
|
||||
///
|
||||
/// ┌────────┐
|
||||
/// │Min(==x)│
|
||||
/// └────────┘
|
||||
pub const MIN_SIZE_EQ: f64 = MEDIUM / 10.0;
|
||||
|
||||
/// The strength to apply to Max equality constraints.
|
||||
///
|
||||
/// ┌────────┐
|
||||
@ -1030,6 +955,13 @@ mod strengths {
|
||||
/// └────────┘
|
||||
pub const MAX_SIZE_EQ: f64 = MEDIUM / 10.0;
|
||||
|
||||
/// The strength to apply to Min equality constraints.
|
||||
///
|
||||
/// ┌────────┐
|
||||
/// │Min(==x)│
|
||||
/// └────────┘
|
||||
pub const MIN_SIZE_EQ: f64 = MEDIUM / 10.0;
|
||||
|
||||
/// The strength to apply to Fill growing constraints.
|
||||
///
|
||||
/// ┌─────────────────────┐
|
||||
@ -1053,12 +985,8 @@ mod strengths {
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_valid() -> bool {
|
||||
SPACER_SIZE_EQ > FIXED_SIZE_EQ
|
||||
&& FILL_SCALING_EQ > FIXED_SIZE_EQ
|
||||
&& FIXED_SIZE_EQ > MIN_SIZE_GE
|
||||
&& MIN_SIZE_GE > MIN_SIZE_EQ
|
||||
SPACER_SIZE_EQ > MAX_SIZE_LE
|
||||
&& MAX_SIZE_LE > MAX_SIZE_EQ
|
||||
&& MIN_SIZE_EQ == MAX_SIZE_EQ
|
||||
&& MIN_SIZE_GE == MAX_SIZE_LE
|
||||
&& MAX_SIZE_LE > LENGTH_SIZE_EQ
|
||||
&& LENGTH_SIZE_EQ > PERCENTAGE_SIZE_EQ
|
||||
@ -1082,13 +1010,10 @@ mod tests {
|
||||
fn strength() {
|
||||
assert!(strengths::is_valid());
|
||||
assert_eq!(strengths::SPACER_SIZE_EQ, REQUIRED - 1.0);
|
||||
assert_eq!(strengths::FILL_SCALING_EQ, REQUIRED - 1.0);
|
||||
assert_eq!(strengths::FIXED_SIZE_EQ, REQUIRED / 10.0);
|
||||
assert_eq!(strengths::MIN_SIZE_GE, STRONG * 10.0);
|
||||
assert_eq!(strengths::LENGTH_SIZE_EQ, STRONG / 10.0);
|
||||
assert_eq!(strengths::PERCENTAGE_SIZE_EQ, MEDIUM * 10.0);
|
||||
assert_eq!(strengths::RATIO_SIZE_EQ, MEDIUM);
|
||||
assert_eq!(strengths::MIN_SIZE_EQ, MEDIUM / 10.0);
|
||||
assert_eq!(strengths::FILL_GROW, WEAK * 10.0);
|
||||
assert_eq!(strengths::GROW, WEAK);
|
||||
assert_eq!(strengths::SPACE_GROW, WEAK / 10.0);
|
||||
@ -1322,28 +1247,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn flex_default() {
|
||||
assert_eq!(Layout::default().flex, Flex::StretchLast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn segment_size() {
|
||||
assert_eq!(
|
||||
Layout::default()
|
||||
.segment_size(SegmentSize::EvenDistribution)
|
||||
.flex,
|
||||
Flex::Stretch
|
||||
);
|
||||
assert_eq!(
|
||||
Layout::default()
|
||||
.segment_size(SegmentSize::LastTakesRemainder)
|
||||
.flex,
|
||||
Flex::StretchLast
|
||||
);
|
||||
assert_eq!(
|
||||
Layout::default().segment_size(SegmentSize::None).flex,
|
||||
Flex::Start
|
||||
);
|
||||
assert_eq!(Layout::default().flex, Flex::Start);
|
||||
}
|
||||
|
||||
/// Tests for the `Layout::split()` function.
|
||||
@ -1405,7 +1309,7 @@ mod tests {
|
||||
#[case] constraints: &[Constraint],
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
letters(area, constraints, expected, Flex::Stretch)
|
||||
letters(area, constraints, expected, Flex::Legacy)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@ -1431,8 +1335,8 @@ mod tests {
|
||||
#[case(Rect::new(0, 0, 2, 1), &[Length(0), Length(3)], "bb")] // zero, overflow
|
||||
#[case(Rect::new(0, 0, 2, 1), &[Length(1), Length(0)], "ab")] // underflow, zero
|
||||
#[case(Rect::new(0, 0, 2, 1), &[Length(1), Length(1)], "ab")] // underflow, underflow
|
||||
#[case(Rect::new(0, 0, 2, 1), &[Length(1), Length(2)], "ab")] // underflow, exact with stretchlast
|
||||
#[case(Rect::new(0, 0, 2, 1), &[Length(1), Length(3)], "ab")] // underflow, overflow with stretchlast
|
||||
#[case(Rect::new(0, 0, 2, 1), &[Length(1), Length(2)], "ab")] // underflow, exact
|
||||
#[case(Rect::new(0, 0, 2, 1), &[Length(1), Length(3)], "ab")] // underflow, overflow
|
||||
#[case(Rect::new(0, 0, 2, 1), &[Length(2), Length(0)], "aa")] // exact, zero
|
||||
#[case(Rect::new(0, 0, 2, 1), &[Length(2), Length(1)], "aa")] // exact, underflow
|
||||
#[case(Rect::new(0, 0, 2, 1), &[Length(2), Length(2)], "aa")] // exact, exact
|
||||
@ -1443,7 +1347,7 @@ mod tests {
|
||||
#[case(Rect::new(0, 0, 2, 1), &[Length(3), Length(3)], "aa")] // overflow, overflow
|
||||
#[case(Rect::new(0, 0, 3, 1), &[Length(2), Length(2)], "aab")] // with stretchlast
|
||||
fn length(#[case] area: Rect, #[case] constraints: &[Constraint], #[case] expected: &str) {
|
||||
letters(area, constraints, expected, Flex::StretchLast)
|
||||
letters(area, constraints, expected, Flex::Legacy)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@ -1481,7 +1385,7 @@ mod tests {
|
||||
#[case(Rect::new(0, 0, 2, 1), &[Max(3), Max(3)], "aa")] // overflow, overflow
|
||||
#[case(Rect::new(0, 0, 3, 1), &[Max(2), Max(2)], "aab")]
|
||||
fn max(#[case] area: Rect, #[case] constraints: &[Constraint], #[case] expected: &str) {
|
||||
letters(area, constraints, expected, Flex::StretchLast)
|
||||
letters(area, constraints, expected, Flex::Legacy)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@ -1512,7 +1416,7 @@ mod tests {
|
||||
#[case(Rect::new(0, 0, 2, 1), &[Min(3), Min(3)], "aa")] // overflow, overflow
|
||||
#[case(Rect::new(0, 0, 3, 1), &[Min(2), Min(2)], "aab")]
|
||||
fn min(#[case] area: Rect, #[case] constraints: &[Constraint], #[case] expected: &str) {
|
||||
letters(area, constraints, expected, Flex::StretchLast)
|
||||
letters(area, constraints, expected, Flex::Legacy)
|
||||
}
|
||||
|
||||
mod percentage {
|
||||
@ -1601,7 +1505,7 @@ mod tests {
|
||||
#[case] constraints: &[Constraint],
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
letters(area, constraints, expected, Flex::StretchLast)
|
||||
letters(area, constraints, expected, Flex::Legacy)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1690,7 +1594,7 @@ mod tests {
|
||||
#[case] constraints: &[Constraint],
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
letters(area, constraints, expected, Flex::StretchLast)
|
||||
letters(area, constraints, expected, Flex::Legacy)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1771,19 +1675,6 @@ mod tests {
|
||||
]
|
||||
);
|
||||
|
||||
let layout = Layout::default()
|
||||
.constraints([Min(1), Fixed(0), Min(1)])
|
||||
.direction(Direction::Horizontal)
|
||||
.split(Rect::new(0, 0, 1, 1));
|
||||
assert_eq!(
|
||||
layout[..],
|
||||
[
|
||||
Rect::new(0, 0, 1, 1),
|
||||
Rect::new(1, 0, 0, 1),
|
||||
Rect::new(1, 0, 0, 1),
|
||||
]
|
||||
);
|
||||
|
||||
// This stretches the 2nd last length instead of the last min based on ranking
|
||||
let layout = Layout::default()
|
||||
.constraints([Length(3), Min(4), Length(1), Min(4)])
|
||||
@ -1809,14 +1700,14 @@ mod tests {
|
||||
#[case::length_priority(vec![75, 25], vec![Percentage(25), Length(25)])]
|
||||
#[case::length_priority(vec![25, 75], vec![Length(25), Ratio(1, 4)])]
|
||||
#[case::length_priority(vec![75, 25], vec![Ratio(1, 4), Length(25)])]
|
||||
#[case::length_priority(vec![75, 25], vec![Length(25), Fixed(25)])]
|
||||
#[case::length_priority(vec![25, 75], vec![Fixed(25), Length(25)])]
|
||||
#[case::length_priority(vec![25, 75], vec![Length(25), Length(25)])]
|
||||
#[case::excess_in_last_variable(vec![25, 25, 50], vec![Length(25), Length(25), Length(25)])]
|
||||
#[case::excess_in_last_variable(vec![15, 35, 50], vec![Length(15), Length(35), Length(25)])]
|
||||
#[case::three_lengths(vec![25, 25, 50], vec![Length(25), Length(25), Length(25)])]
|
||||
fn constraint_length(#[case] expected: Vec<u16>, #[case] constraints: Vec<Constraint>) {
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(constraints)
|
||||
.flex(Flex::Legacy)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
@ -1826,8 +1717,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::table_length_test(vec![Length(4), Length(4)], vec![(0, 4), (5, 2)], 7)]
|
||||
#[case::table_length_test(vec![Length(4), Length(4)], vec![(0, 3), (4, 0)], 4)]
|
||||
#[case::table_length_test(vec![Length(4), Length(4)], vec![(0, 3), (4, 3)], 7)]
|
||||
#[case::table_length_test(vec![Length(4), Length(4)], vec![(0, 2), (3, 1)], 4)]
|
||||
fn table_length(
|
||||
#[case] constraints: Vec<Constraint>,
|
||||
#[case] expected: Vec<(u16, u16)>,
|
||||
@ -1846,26 +1737,29 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::fixed_is_higher_priority_than_min_max(vec![50, 25, 25], vec![Min(25), Fixed(25), Max(25)])]
|
||||
#[case::fixed_is_higher_priority_than_min_max(vec![25, 25, 50], vec![Max(25), Fixed(25), Min(25)])]
|
||||
#[case::excess_in_lowest_priority(vec![33, 33, 34], vec![Fixed(33), Fixed(33), Fixed(33)])]
|
||||
#[case::excess_in_lowest_priority(vec![25, 25, 50], vec![Fixed(25), Fixed(25), Fixed(25)])]
|
||||
#[case::fixed_higher_priority(vec![25, 25, 50], vec![Percentage(25), Fixed(25), Ratio(1, 4)])]
|
||||
#[case::fixed_higher_priority(vec![25, 50, 25], vec![Fixed(25), Ratio(1, 4), Percentage(25)])]
|
||||
#[case::fixed_higher_priority(vec![50, 25, 25], vec![Ratio(1, 4), Fixed(25), Percentage(25)])]
|
||||
#[case::fixed_higher_priority(vec![50, 25, 25], vec![Ratio(1, 4), Percentage(25), Fixed(25)])]
|
||||
#[case::fixed_higher_priority(vec![79, 1, 20], vec![Length(100), Fixed(1), Min(20)])]
|
||||
#[case::fixed_higher_priority(vec![20, 1, 79], vec![Min(20), Fixed(1), Length(100)])]
|
||||
#[case::fixed_higher_priority(vec![45, 10, 45], vec![Fill(1), Fixed(10), Fill(1)])]
|
||||
#[case::fixed_higher_priority(vec![30, 10, 60], vec![Fill(1), Fixed(10), Fill(2)])]
|
||||
#[case::fixed_higher_priority(vec![18, 10, 72], vec![Fill(1), Fixed(10), Fill(4)])]
|
||||
#[case::fixed_higher_priority(vec![15, 10, 75], vec![Fill(1), Fixed(10), Fill(5)])]
|
||||
#[case::length_is_higher_priority_than_min_max(vec![50, 25, 25], vec![Min(25), Length(25), Max(25)])]
|
||||
#[case::length_is_higher_priority_than_min_max(vec![25, 25, 50], vec![Max(25), Length(25), Min(25)])]
|
||||
#[case::excess_in_lowest_priority(vec![33, 33, 34], vec![Length(33), Length(33), Length(33)])]
|
||||
#[case::excess_in_lowest_priority(vec![25, 25, 50], vec![Length(25), Length(25), Length(25)])]
|
||||
#[case::length_higher_priority(vec![25, 25, 50], vec![Percentage(25), Length(25), Ratio(1, 4)])]
|
||||
#[case::length_higher_priority(vec![25, 50, 25], vec![Length(25), Ratio(1, 4), Percentage(25)])]
|
||||
#[case::length_higher_priority(vec![50, 25, 25], vec![Ratio(1, 4), Length(25), Percentage(25)])]
|
||||
#[case::length_higher_priority(vec![50, 25, 25], vec![Ratio(1, 4), Percentage(25), Length(25)])]
|
||||
#[case::length_higher_priority(vec![80, 0, 20], vec![Length(100), Length(1), Min(20)])]
|
||||
#[case::length_higher_priority(vec![20, 1, 79], vec![Min(20), Length(1), Length(100)])]
|
||||
#[case::length_higher_priority(vec![45, 10, 45], vec![Fill(1), Length(10), Fill(1)])]
|
||||
#[case::length_higher_priority(vec![30, 10, 60], vec![Fill(1), Length(10), Fill(2)])]
|
||||
#[case::length_higher_priority(vec![18, 10, 72], vec![Fill(1), Length(10), Fill(4)])]
|
||||
#[case::length_higher_priority(vec![15, 10, 75], vec![Fill(1), Length(10), Fill(5)])]
|
||||
#[case::three_lengths_reference(vec![25, 25, 50], vec![Length(25), Length(25), Length(25)])]
|
||||
// #[case::previously_unstable_test(vec![25, 50, 25], vec![Length(25), Length(25),
|
||||
// Fixed(25)])]
|
||||
fn fixed(#[case] expected: Vec<u16>, #[case] constraints: Vec<Constraint>) {
|
||||
#[case::previously_unstable_test(vec![25, 25, 50], vec![Length(25), Length(25), Length(25)])]
|
||||
fn length_is_higher_priority(
|
||||
#[case] expected: Vec<u16>,
|
||||
#[case] constraints: Vec<Constraint>,
|
||||
) {
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(constraints)
|
||||
.flex(Flex::Legacy)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
@ -1875,11 +1769,83 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::excess_in_last_variable(vec![13, 10, 27], vec![Fill(1), Fixed(10), Fill(2)])]
|
||||
#[case::excess_in_last_variable(vec![10, 27, 13], vec![Fixed(10), Fill(2), Fill(1)])] // might be unstable?
|
||||
#[case::length_is_higher_priority_than_min_max(vec![50, 25, 25], vec![Min(25), Length(25), Max(25)])]
|
||||
#[case::length_is_higher_priority_than_min_max(vec![25, 25, 50], vec![Max(25), Length(25), Min(25)])]
|
||||
#[case::excess_in_lowest_priority(vec![33, 33, 33], vec![Length(33), Length(33), Length(33)])]
|
||||
#[case::excess_in_lowest_priority(vec![25, 25, 25], vec![Length(25), Length(25), Length(25)])]
|
||||
#[case::length_higher_priority(vec![25, 25, 25], vec![Percentage(25), Length(25), Ratio(1, 4)])]
|
||||
#[case::length_higher_priority(vec![25, 25, 25], vec![Length(25), Ratio(1, 4), Percentage(25)])]
|
||||
#[case::length_higher_priority(vec![25, 25, 25], vec![Ratio(1, 4), Length(25), Percentage(25)])]
|
||||
#[case::length_higher_priority(vec![25, 25, 25], vec![Ratio(1, 4), Percentage(25), Length(25)])]
|
||||
#[case::length_higher_priority(vec![79, 1, 20], vec![Length(100), Length(1), Min(20)])]
|
||||
#[case::length_higher_priority(vec![20, 1, 79], vec![Min(20), Length(1), Length(100)])]
|
||||
#[case::length_higher_priority(vec![45, 10, 45], vec![Fill(1), Length(10), Fill(1)])]
|
||||
#[case::length_higher_priority(vec![30, 10, 60], vec![Fill(1), Length(10), Fill(2)])]
|
||||
#[case::length_higher_priority(vec![18, 10, 72], vec![Fill(1), Length(10), Fill(4)])]
|
||||
#[case::length_higher_priority(vec![15, 10, 75], vec![Fill(1), Length(10), Fill(5)])]
|
||||
#[case::previously_unstable_test(vec![25, 25, 25], vec![Length(25), Length(25), Length(25)])]
|
||||
fn length_is_higher_priority_in_flex(
|
||||
#[case] expected: Vec<u16>,
|
||||
#[case] constraints: Vec<Constraint>,
|
||||
) {
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(&constraints)
|
||||
.flex(Flex::Start)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(&constraints)
|
||||
.flex(Flex::Center)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(&constraints)
|
||||
.flex(Flex::End)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(&constraints)
|
||||
.flex(Flex::SpaceAround)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(&constraints)
|
||||
.flex(Flex::SpaceBetween)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|r| r.width)
|
||||
.collect::<Vec<u16>>();
|
||||
assert_eq!(expected, r);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::excess_in_last_variable(vec![13, 10, 27], vec![Fill(1), Length(10), Fill(2)])]
|
||||
#[case::excess_in_last_variable(vec![10, 27, 13], vec![Length(10), Fill(2), Fill(1)])] // might be unstable?
|
||||
fn fixed_with_50_width(#[case] expected: Vec<u16>, #[case] constraints: Vec<Constraint>) {
|
||||
let rect = Rect::new(0, 0, 50, 1);
|
||||
let r = Layout::horizontal(constraints)
|
||||
.flex(Flex::Legacy)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
@ -1893,13 +1859,13 @@ mod tests {
|
||||
#[case::incremental(vec![10, 20, 30, 40], vec![Fill(1), Fill(2), Fill(3), Fill(4)])]
|
||||
#[case::decremental(vec![40, 30, 20, 10], vec![Fill(4), Fill(3), Fill(2), Fill(1)])]
|
||||
#[case::randomly_ordered(vec![10, 30, 20, 40], vec![Fill(1), Fill(3), Fill(2), Fill(4)])]
|
||||
#[case::randomly_ordered(vec![5, 15, 50, 10, 20], vec![Fill(1), Fill(3), Fixed(50), Fill(2), Fill(4)])]
|
||||
#[case::randomly_ordered(vec![5, 15, 50, 10, 20], vec![Fill(1), Fill(3), Length(50), Fill(2), Fill(4)])]
|
||||
#[case::randomly_ordered(vec![5, 15, 50, 10, 20], vec![Fill(1), Fill(3), Length(50), Fill(2), Fill(4)])]
|
||||
#[case::randomly_ordered(vec![5, 15, 50, 10, 20], vec![Fill(1), Fill(3), Percentage(50), Fill(2), Fill(4)])]
|
||||
#[case::randomly_ordered(vec![5, 15, 50, 10, 20], vec![Fill(1), Fill(3), Min(50), Fill(2), Fill(4)])]
|
||||
#[case::randomly_ordered(vec![5, 15, 50, 10, 20], vec![Fill(1), Fill(3), Max(50), Fill(2), Fill(4)])]
|
||||
#[case::zero_width(vec![0, 100, 0], vec![Fill(0), Fill(1), Fill(0)])]
|
||||
#[case::zero_width(vec![50, 1, 49], vec![Fill(0), Fixed(1), Fill(0)])]
|
||||
#[case::zero_width(vec![50, 1, 49], vec![Fill(0), Length(1), Fill(0)])]
|
||||
#[case::zero_width(vec![50, 1, 49], vec![Fill(0), Length(1), Fill(0)])]
|
||||
#[case::zero_width(vec![50, 1, 49], vec![Fill(0), Percentage(1), Fill(0)])]
|
||||
#[case::zero_width(vec![50, 1, 49], vec![Fill(0), Min(1), Fill(0)])]
|
||||
@ -1914,7 +1880,7 @@ mod tests {
|
||||
#[case::space_filler(vec![80, 20], vec![Fill(1), Percentage(20)])]
|
||||
#[case::space_filler(vec![80, 20], vec![Fill(u16::MAX), Percentage(20)])]
|
||||
#[case::space_filler(vec![80, 0, 20], vec![Fill(u16::MAX), Fill(0), Percentage(20)])]
|
||||
#[case::space_filler(vec![80, 20], vec![Fill(0), Fixed(20)])]
|
||||
#[case::space_filler(vec![80, 20], vec![Fill(0), Length(20)])]
|
||||
#[case::space_filler(vec![80, 20], vec![Fill(0), Length(20)])]
|
||||
#[case::space_filler(vec![80, 20], vec![Fill(0), Min(20)])]
|
||||
#[case::space_filler(vec![80, 20], vec![Fill(0), Max(20)])]
|
||||
@ -1927,6 +1893,7 @@ mod tests {
|
||||
fn fill(#[case] expected: Vec<u16>, #[case] constraints: Vec<Constraint>) {
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(constraints)
|
||||
.flex(Flex::Legacy)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
@ -1944,6 +1911,7 @@ mod tests {
|
||||
) {
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(constraints)
|
||||
.flex(Flex::Legacy)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
@ -1962,6 +1930,7 @@ mod tests {
|
||||
fn min_max(#[case] expected: Vec<u16>, #[case] constraints: Vec<Constraint>) {
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(constraints)
|
||||
.flex(Flex::Legacy)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
@ -1971,69 +1940,48 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::length(vec![(0, 100)], vec![Constraint::Length(50)], Flex::StretchLast)]
|
||||
#[case::length(vec![(0, 100)], vec![Constraint::Length(50)], Flex::Stretch)]
|
||||
#[case::length(vec![(0, 50)], vec![Constraint::Length(50)], Flex::Start)]
|
||||
#[case::length(vec![(50, 50)], vec![Constraint::Length(50)], Flex::End)]
|
||||
#[case::length(vec![(25, 50)], vec![Constraint::Length(50)], Flex::Center)]
|
||||
#[case::fixed(vec![(0, 100)], vec![Fixed(50)], Flex::StretchLast)]
|
||||
#[case::fixed(vec![(0, 100)], vec![Fixed(50)], Flex::Stretch)]
|
||||
#[case::fixed(vec![(0, 50)], vec![Fixed(50)], Flex::Start)]
|
||||
#[case::fixed(vec![(50, 50)], vec![Fixed(50)], Flex::End)]
|
||||
#[case::fixed(vec![(25, 50)], vec![Fixed(50)], Flex::Center)]
|
||||
#[case::ratio(vec![(0, 100)], vec![Ratio(1, 2)], Flex::StretchLast)]
|
||||
#[case::ratio(vec![(0, 100)], vec![Ratio(1, 2)], Flex::Stretch)]
|
||||
#[case::length(vec![(0, 100)], vec![Length(50)], Flex::Legacy)]
|
||||
#[case::length(vec![(0, 50)], vec![Length(50)], Flex::Start)]
|
||||
#[case::length(vec![(50, 50)], vec![Length(50)], Flex::End)]
|
||||
#[case::length(vec![(25, 50)], vec![Length(50)], Flex::Center)]
|
||||
#[case::ratio(vec![(0, 100)], vec![Ratio(1, 2)], Flex::Legacy)]
|
||||
#[case::ratio(vec![(0, 50)], vec![Ratio(1, 2)], Flex::Start)]
|
||||
#[case::ratio(vec![(50, 50)], vec![Ratio(1, 2)], Flex::End)]
|
||||
#[case::ratio(vec![(25, 50)], vec![Ratio(1, 2)], Flex::Center)]
|
||||
#[case::percent(vec![(0, 100)], vec![Percentage(50)], Flex::StretchLast)]
|
||||
#[case::percent(vec![(0, 100)], vec![Percentage(50)], Flex::Stretch)]
|
||||
#[case::percent(vec![(0, 100)], vec![Percentage(50)], Flex::Legacy)]
|
||||
#[case::percent(vec![(0, 50)], vec![Percentage(50)], Flex::Start)]
|
||||
#[case::percent(vec![(50, 50)], vec![Percentage(50)], Flex::End)]
|
||||
#[case::percent(vec![(25, 50)], vec![Percentage(50)], Flex::Center)]
|
||||
#[case::min(vec![(0, 100)], vec![Min(50)], Flex::StretchLast)]
|
||||
#[case::min(vec![(0, 100)], vec![Min(50)], Flex::Stretch)]
|
||||
#[case::min(vec![(0, 50)], vec![Min(50)], Flex::Start)]
|
||||
#[case::min(vec![(50, 50)], vec![Min(50)], Flex::End)]
|
||||
#[case::min(vec![(25, 50)], vec![Min(50)], Flex::Center)]
|
||||
#[case::max(vec![(0, 100)], vec![Max(50)], Flex::StretchLast)]
|
||||
#[case::max(vec![(0, 100)], vec![Max(50)], Flex::Stretch)]
|
||||
#[case::min(vec![(0, 100)], vec![Min(50)], Flex::Legacy)]
|
||||
#[case::min(vec![(0, 100)], vec![Min(50)], Flex::Start)]
|
||||
#[case::min(vec![(0, 100)], vec![Min(50)], Flex::End)]
|
||||
#[case::min(vec![(0, 100)], vec![Min(50)], Flex::Center)]
|
||||
#[case::max(vec![(0, 100)], vec![Max(50)], Flex::Legacy)]
|
||||
#[case::max(vec![(0, 50)], vec![Max(50)], Flex::Start)]
|
||||
#[case::max(vec![(50, 50)], vec![Max(50)], Flex::End)]
|
||||
#[case::max(vec![(25, 50)], vec![Max(50)], Flex::Center)]
|
||||
#[case::spacebetween_becomes_stretch(vec![(0, 100)], vec![Min(1)], Flex::SpaceBetween)]
|
||||
#[case::spacebetween_becomes_stretch(vec![(0, 100)], vec![Max(20)], Flex::SpaceBetween)]
|
||||
#[case::spacebetween_becomes_stretch(vec![(0, 100)], vec![Fixed(20)], Flex::SpaceBetween)]
|
||||
#[case::length(vec![(0, 25), (25, 75)], vec![Length(25), Length(25)], Flex::StretchLast)]
|
||||
#[case::length(vec![(0, 50), (50, 50)], vec![Length(25), Length(25)], Flex::Stretch)]
|
||||
#[case::spacebetween_becomes_stretch(vec![(0, 100)], vec![Length(20)], Flex::SpaceBetween)]
|
||||
#[case::length(vec![(0, 25), (25, 75)], vec![Length(25), Length(25)], Flex::Legacy)]
|
||||
#[case::length(vec![(0, 25), (25, 25)], vec![Length(25), Length(25)], Flex::Start)]
|
||||
#[case::length(vec![(25, 25), (50, 25)], vec![Length(25), Length(25)], Flex::Center)]
|
||||
#[case::length(vec![(50, 25), (75, 25)], vec![Length(25), Length(25)], Flex::End)]
|
||||
#[case::length(vec![(0, 25), (75, 25)], vec![Length(25), Length(25)], Flex::SpaceBetween)]
|
||||
#[case::length(vec![(17, 25), (58, 25)], vec![Length(25), Length(25)], Flex::SpaceAround)]
|
||||
#[case::fixed(vec![(0, 25), (25, 75)], vec![Fixed(25), Fixed(25)], Flex::StretchLast)]
|
||||
#[case::fixed(vec![(0, 50), (50, 50)], vec![Fixed(25), Fixed(25)], Flex::Stretch)]
|
||||
#[case::fixed(vec![(0, 25), (25, 25)], vec![Fixed(25), Fixed(25)], Flex::Start)]
|
||||
#[case::fixed(vec![(25, 25), (50, 25)], vec![Fixed(25), Fixed(25)], Flex::Center)]
|
||||
#[case::fixed(vec![(50, 25), (75, 25)], vec![Fixed(25), Fixed(25)], Flex::End)]
|
||||
#[case::fixed(vec![(0, 25), (75, 25)], vec![Fixed(25), Fixed(25)], Flex::SpaceBetween)]
|
||||
#[case::fixed(vec![(17, 25), (58, 25)], vec![Fixed(25), Fixed(25)], Flex::SpaceAround)]
|
||||
#[case::percentage(vec![(0, 25), (25, 75)], vec![Percentage(25), Percentage(25)], Flex::StretchLast)]
|
||||
#[case::percentage(vec![(0, 50), (50, 50)], vec![Percentage(25), Percentage(25)], Flex::Stretch)]
|
||||
#[case::percentage(vec![(0, 25), (25, 75)], vec![Percentage(25), Percentage(25)], Flex::Legacy)]
|
||||
#[case::percentage(vec![(0, 25), (25, 25)], vec![Percentage(25), Percentage(25)], Flex::Start)]
|
||||
#[case::percentage(vec![(25, 25), (50, 25)], vec![Percentage(25), Percentage(25)], Flex::Center)]
|
||||
#[case::percentage(vec![(50, 25), (75, 25)], vec![Percentage(25), Percentage(25)], Flex::End)]
|
||||
#[case::percentage(vec![(0, 25), (75, 25)], vec![Percentage(25), Percentage(25)], Flex::SpaceBetween)]
|
||||
#[case::percentage(vec![(17, 25), (58, 25)], vec![Percentage(25), Percentage(25)], Flex::SpaceAround)]
|
||||
#[case::min(vec![(0, 25), (25, 75)], vec![Min(25), Min(25)], Flex::StretchLast)]
|
||||
#[case::min(vec![(0, 50), (50, 50)], vec![Min(25), Min(25)], Flex::Stretch)]
|
||||
#[case::min(vec![(0, 25), (25, 25)], vec![Min(25), Min(25)], Flex::Start)]
|
||||
#[case::min(vec![(25, 25), (50, 25)], vec![Min(25), Min(25)], Flex::Center)]
|
||||
#[case::min(vec![(50, 25), (75, 25)], vec![Min(25), Min(25)], Flex::End)]
|
||||
#[case::min(vec![(0, 25), (75, 25)], vec![Min(25), Min(25)], Flex::SpaceBetween)]
|
||||
#[case::min(vec![(17, 25), (58, 25)], vec![Min(25), Min(25)], Flex::SpaceAround)]
|
||||
#[case::max(vec![(0, 25), (25, 75)], vec![Max(25), Max(25)], Flex::StretchLast)]
|
||||
#[case::max(vec![(0, 50), (50, 50)], vec![Max(25), Max(25)], Flex::Stretch)]
|
||||
#[case::min(vec![(0, 25), (25, 75)], vec![Min(25), Min(25)], Flex::Legacy)]
|
||||
#[case::min(vec![(0, 50), (50, 50)], vec![Min(25), Min(25)], Flex::Start)]
|
||||
#[case::min(vec![(0, 50), (50, 50)], vec![Min(25), Min(25)], Flex::Center)]
|
||||
#[case::min(vec![(0, 50), (50, 50)], vec![Min(25), Min(25)], Flex::End)]
|
||||
#[case::min(vec![(0, 50), (50, 50)], vec![Min(25), Min(25)], Flex::SpaceBetween)]
|
||||
#[case::min(vec![(0, 50), (50, 50)], vec![Min(25), Min(25)], Flex::SpaceAround)]
|
||||
#[case::max(vec![(0, 25), (25, 75)], vec![Max(25), Max(25)], Flex::Legacy)]
|
||||
#[case::max(vec![(0, 25), (25, 25)], vec![Max(25), Max(25)], Flex::Start)]
|
||||
#[case::max(vec![(25, 25), (50, 25)], vec![Max(25), Max(25)], Flex::Center)]
|
||||
#[case::max(vec![(50, 25), (75, 25)], vec![Max(25), Max(25)], Flex::End)]
|
||||
@ -2061,11 +2009,9 @@ mod tests {
|
||||
#[case::length_spacing(vec![(0 , 20), (22, 20) , (44, 20)], vec![Length(20), Length(20), Length(20)], Flex::Start , 2)]
|
||||
#[case::length_spacing(vec![(18, 20), (40, 20) , (62, 20)], vec![Length(20), Length(20), Length(20)], Flex::Center , 2)]
|
||||
#[case::length_spacing(vec![(36, 20), (58, 20) , (80, 20)], vec![Length(20), Length(20), Length(20)], Flex::End , 2)]
|
||||
#[case::length_spacing(vec![(0 , 32), (34, 32) , (68, 32)], vec![Length(20), Length(20), Length(20)], Flex::Stretch , 2)]
|
||||
#[case::length_spacing(vec![(0 , 20), (22, 20) , (44, 56)], vec![Length(20), Length(20), Length(20)], Flex::StretchLast, 2)]
|
||||
#[case::fixed_spacing(vec![(0 , 20), (22, 20) , (44, 56)], vec![Fixed(20) , Fixed(20) , Fixed(20)] , Flex::StretchLast, 2)]
|
||||
#[case::fixed_spacing(vec![(0 , 32), (34, 32) , (68, 32)], vec![Fixed(20) , Fixed(20) , Fixed(20)] , Flex::Stretch , 2)]
|
||||
#[case::fixed_spacing(vec![(10 , 20), (40, 20) , (70, 20)], vec![Fixed(20) , Fixed(20) , Fixed(20)] , Flex::SpaceAround, 2)]
|
||||
#[case::length_spacing(vec![(0 , 20), (22, 20) , (44, 56)], vec![Length(20), Length(20), Length(20)], Flex::Legacy , 2)]
|
||||
#[case::length_spacing(vec![(0 , 20), (40, 20) , (80, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceBetween, 2)]
|
||||
#[case::length_spacing(vec![(10, 20), (40, 20) , (70, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceAround, 2)]
|
||||
fn flex_spacing(
|
||||
#[case] expected: Vec<(u16, u16)>,
|
||||
#[case] constraints: Vec<Constraint>,
|
||||
@ -2085,30 +2031,30 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::a(vec![(0, 25), (25, 75)], vec![Fixed(25), Length(25)])]
|
||||
#[case::b(vec![(0, 75), (75, 25)], vec![Length(25), Fixed(25)])]
|
||||
#[case::c(vec![(0, 25), (25, 75)], vec![Length(25), Percentage(25)])]
|
||||
#[case::d(vec![(0, 75), (75, 25)], vec![Percentage(25), Length(25)])]
|
||||
#[case::e(vec![(0, 75), (75, 25)], vec![Min(25), Percentage(25)])]
|
||||
#[case::f(vec![(0, 25), (25, 75)], vec![Percentage(25), Min(25)])]
|
||||
#[case::g(vec![(0, 25), (25, 75)], vec![Min(25), Percentage(100)])]
|
||||
#[case::h(vec![(0, 75), (75, 25)], vec![Percentage(100), Min(25)])]
|
||||
#[case::i(vec![(0, 25), (25, 75)], vec![Max(75), Percentage(75)])]
|
||||
#[case::j(vec![(0, 75), (75, 25)], vec![Percentage(75), Max(75)])]
|
||||
#[case::k(vec![(0, 25), (25, 75)], vec![Max(25), Percentage(25)])]
|
||||
#[case::l(vec![(0, 75), (75, 25)], vec![Percentage(25), Max(25)])]
|
||||
#[case::m(vec![(0, 25), (25, 75)], vec![Length(25), Ratio(1, 4)])]
|
||||
#[case::n(vec![(0, 75), (75, 25)], vec![Ratio(1, 4), Length(25)])]
|
||||
#[case::o(vec![(0, 25), (25, 75)], vec![Percentage(25), Ratio(1, 4)])]
|
||||
#[case::p(vec![(0, 75), (75, 25)], vec![Ratio(1, 4), Percentage(25)])]
|
||||
#[case::q(vec![(0, 25), (25, 75)], vec![Ratio(1, 4), Fill(25)])]
|
||||
#[case::r(vec![(0, 75), (75, 25)], vec![Fill(25), Ratio(1, 4)])]
|
||||
#[case::a(vec![(0, 25), (25, 75)], vec![Length(25), Length(25)])]
|
||||
#[case::b(vec![(0, 25), (25, 75)], vec![Length(25), Percentage(25)])]
|
||||
#[case::c(vec![(0, 75), (75, 25)], vec![Percentage(25), Length(25)])]
|
||||
#[case::d(vec![(0, 75), (75, 25)], vec![Min(25), Percentage(25)])]
|
||||
#[case::e(vec![(0, 25), (25, 75)], vec![Percentage(25), Min(25)])]
|
||||
#[case::f(vec![(0, 25), (25, 75)], vec![Min(25), Percentage(100)])]
|
||||
#[case::g(vec![(0, 75), (75, 25)], vec![Percentage(100), Min(25)])]
|
||||
#[case::h(vec![(0, 25), (25, 75)], vec![Max(75), Percentage(75)])]
|
||||
#[case::i(vec![(0, 75), (75, 25)], vec![Percentage(75), Max(75)])]
|
||||
#[case::j(vec![(0, 25), (25, 75)], vec![Max(25), Percentage(25)])]
|
||||
#[case::k(vec![(0, 75), (75, 25)], vec![Percentage(25), Max(25)])]
|
||||
#[case::l(vec![(0, 25), (25, 75)], vec![Length(25), Ratio(1, 4)])]
|
||||
#[case::m(vec![(0, 75), (75, 25)], vec![Ratio(1, 4), Length(25)])]
|
||||
#[case::n(vec![(0, 25), (25, 75)], vec![Percentage(25), Ratio(1, 4)])]
|
||||
#[case::o(vec![(0, 75), (75, 25)], vec![Ratio(1, 4), Percentage(25)])]
|
||||
#[case::p(vec![(0, 25), (25, 75)], vec![Ratio(1, 4), Fill(25)])]
|
||||
#[case::q(vec![(0, 75), (75, 25)], vec![Fill(25), Ratio(1, 4)])]
|
||||
fn constraint_specification_tests_for_priority(
|
||||
#[case] expected: Vec<(u16, u16)>,
|
||||
#[case] constraints: Vec<Constraint>,
|
||||
) {
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(constraints)
|
||||
.flex(Flex::Legacy)
|
||||
.split(rect)
|
||||
.iter()
|
||||
.cloned()
|
||||
@ -2121,11 +2067,9 @@ mod tests {
|
||||
#[case::a(vec![(0, 20), (20, 20), (40, 20)], vec![Length(20), Length(20), Length(20)], Flex::Start, 0)]
|
||||
#[case::b(vec![(18, 20), (40, 20), (62, 20)], vec![Length(20), Length(20), Length(20)], Flex::Center, 2)]
|
||||
#[case::c(vec![(36, 20), (58, 20), (80, 20)], vec![Length(20), Length(20), Length(20)], Flex::End, 2)]
|
||||
#[case::d(vec![(0, 32), (34, 32), (68, 32)], vec![Length(20), Length(20), Length(20)], Flex::Stretch, 2)]
|
||||
#[case::e(vec![(0, 20), (22, 20), (44, 56)], vec![Length(20), Length(20), Length(20)], Flex::StretchLast, 2)]
|
||||
#[case::f(vec![(0, 20), (22, 20), (44, 56)], vec![Fixed(20), Fixed(20), Fixed(20)], Flex::StretchLast, 2)]
|
||||
#[case::g(vec![(0, 32), (34, 32), (68, 32)], vec![Fixed(20), Fixed(20), Fixed(20)], Flex::Stretch, 2)]
|
||||
#[case::h(vec![(10, 20), (40, 20), (70, 20)], vec![Fixed(20), Fixed(20), Fixed(20)], Flex::SpaceAround, 2)]
|
||||
#[case::d(vec![(0, 20), (22, 20), (44, 56)], vec![Length(20), Length(20), Length(20)], Flex::Legacy, 2)]
|
||||
#[case::e(vec![(0, 20), (22, 20), (44, 56)], vec![Length(20), Length(20), Length(20)], Flex::Legacy, 2)]
|
||||
#[case::f(vec![(10, 20), (40, 20), (70, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceAround, 2)]
|
||||
fn constraint_specification_tests_for_priority_with_spacing(
|
||||
#[case] expected: Vec<(u16, u16)>,
|
||||
#[case] constraints: Vec<Constraint>,
|
||||
@ -2145,16 +2089,16 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::prop(vec![(0 , 10), (10, 80), (90 , 10)] , vec![Fixed(10), Fill(1), Fixed(10)], Flex::Stretch)]
|
||||
#[case::flex(vec![(0 , 10), (90 , 10)] , vec![Fixed(10), Fixed(10)], Flex::SpaceBetween)]
|
||||
#[case::prop(vec![(0 , 27), (27, 10), (37, 26), (63, 10), (73, 27)] , vec![Fill(1), Fixed(10), Fill(1), Fixed(10), Fill(1)], Flex::Stretch)]
|
||||
#[case::flex(vec![(27 , 10), (63, 10)] , vec![Fixed(10), Fixed(10)], Flex::SpaceAround)]
|
||||
#[case::prop(vec![(0 , 10), (10, 10), (20 , 80)] , vec![Fixed(10), Fixed(10), Fill(1)], Flex::Stretch)]
|
||||
#[case::flex(vec![(0 , 10), (10, 10)] , vec![Fixed(10), Fixed(10)], Flex::Start)]
|
||||
#[case::prop(vec![(0 , 80), (80 , 10), (90, 10)] , vec![Fill(1), Fixed(10), Fixed(10)], Flex::Stretch)]
|
||||
#[case::flex(vec![(80 , 10), (90, 10)] , vec![Fixed(10), Fixed(10)], Flex::End)]
|
||||
#[case::prop(vec![(0 , 40), (40, 10), (50, 10), (60, 40)] , vec![Fill(1), Fixed(10), Fixed(10), Fill(1)], Flex::Stretch)]
|
||||
#[case::flex(vec![(40 , 10), (50, 10)] , vec![Fixed(10), Fixed(10)], Flex::Center)]
|
||||
#[case::prop(vec![(0 , 10), (10, 80), (90 , 10)] , vec![Length(10), Fill(1), Length(10)], Flex::Legacy)]
|
||||
#[case::flex(vec![(0 , 10), (90 , 10)] , vec![Length(10), Length(10)], Flex::SpaceBetween)]
|
||||
#[case::prop(vec![(0 , 27), (27, 10), (37, 26), (63, 10), (73, 27)] , vec![Fill(1), Length(10), Fill(1), Length(10), Fill(1)], Flex::Legacy)]
|
||||
#[case::flex(vec![(27 , 10), (63, 10)] , vec![Length(10), Length(10)], Flex::SpaceAround)]
|
||||
#[case::prop(vec![(0 , 10), (10, 10), (20 , 80)] , vec![Length(10), Length(10), Fill(1)], Flex::Legacy)]
|
||||
#[case::flex(vec![(0 , 10), (10, 10)] , vec![Length(10), Length(10)], Flex::Start)]
|
||||
#[case::prop(vec![(0 , 80), (80 , 10), (90, 10)] , vec![Fill(1), Length(10), Length(10)], Flex::Legacy)]
|
||||
#[case::flex(vec![(80 , 10), (90, 10)] , vec![Length(10), Length(10)], Flex::End)]
|
||||
#[case::prop(vec![(0 , 40), (40, 10), (50, 10), (60, 40)] , vec![Fill(1), Length(10), Length(10), Fill(1)], Flex::Legacy)]
|
||||
#[case::flex(vec![(40 , 10), (50, 10)] , vec![Length(10), Length(10)], Flex::Center)]
|
||||
fn fill_vs_flex(
|
||||
#[case] expected: Vec<(u16, u16)>,
|
||||
#[case] constraints: Vec<Constraint>,
|
||||
@ -2170,36 +2114,32 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Stretch , 0)]
|
||||
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::StretchLast , 0)]
|
||||
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Legacy , 0)]
|
||||
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceAround , 0)]
|
||||
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceBetween , 0)]
|
||||
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Start , 0)]
|
||||
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Center , 0)]
|
||||
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::End , 0)]
|
||||
#[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::Stretch , 10)]
|
||||
#[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::StretchLast , 10)]
|
||||
#[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::Legacy , 10)]
|
||||
#[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::Start , 10)]
|
||||
#[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::Center , 10)]
|
||||
#[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::End , 10)]
|
||||
// SpaceAround and SpaceBetween spacers behave differently from other flexes
|
||||
#[case::flex10(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceAround , 10)]
|
||||
#[case::flex10(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceBetween , 10)]
|
||||
#[case::flex_fixed0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Fixed(10), Fill(1)], Flex::Stretch , 0)]
|
||||
#[case::flex_fixed0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Fixed(10), Fill(1)], Flex::StretchLast , 0)]
|
||||
#[case::flex_fixed0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Fixed(10), Fill(1)], Flex::SpaceAround , 0)]
|
||||
#[case::flex_fixed0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Fixed(10), Fill(1)], Flex::SpaceBetween , 0)]
|
||||
#[case::flex_fixed0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Fixed(10), Fill(1)], Flex::Start , 0)]
|
||||
#[case::flex_fixed0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Fixed(10), Fill(1)], Flex::Center , 0)]
|
||||
#[case::flex_fixed0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Fixed(10), Fill(1)], Flex::End , 0)]
|
||||
#[case::flex_fixed10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Fixed(10), Fill(1)], Flex::Stretch , 10)]
|
||||
#[case::flex_fixed10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Fixed(10), Fill(1)], Flex::StretchLast , 10)]
|
||||
#[case::flex_fixed10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Fixed(10), Fill(1)], Flex::Start , 10)]
|
||||
#[case::flex_fixed10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Fixed(10), Fill(1)], Flex::Center , 10)]
|
||||
#[case::flex_fixed10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Fixed(10), Fill(1)], Flex::End , 10)]
|
||||
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::Legacy , 0)]
|
||||
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceAround , 0)]
|
||||
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceBetween , 0)]
|
||||
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::Start , 0)]
|
||||
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::Center , 0)]
|
||||
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::End , 0)]
|
||||
#[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::Legacy , 10)]
|
||||
#[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::Start , 10)]
|
||||
#[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::Center , 10)]
|
||||
#[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::End , 10)]
|
||||
// SpaceAround and SpaceBetween spacers behave differently from other flexes
|
||||
#[case::flex_fixed10(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Fixed(10), Fill(1)], Flex::SpaceAround , 10)]
|
||||
#[case::flex_fixed10(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Fixed(10), Fill(1)], Flex::SpaceBetween , 10)]
|
||||
#[case::flex_length10(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceAround , 10)]
|
||||
#[case::flex_length10(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceBetween , 10)]
|
||||
fn fill_spacing(
|
||||
#[case] expected: Vec<(u16, u16)>,
|
||||
#[case] constraints: Vec<Constraint>,
|
||||
@ -2219,7 +2159,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::flex_fixed10(vec![(0, 10), (90, 10)], vec![Length(10), Length(10)], Flex::Center, 80)]
|
||||
#[case::flex_length10(vec![(0, 10), (90, 10)], vec![Length(10), Length(10)], Flex::Center, 80)]
|
||||
fn flex_spacing_lower_priority_than_user_spacing(
|
||||
#[case] expected: Vec<(u16, u16)>,
|
||||
#[case] constraints: Vec<Constraint>,
|
||||
@ -2239,8 +2179,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::spacers(vec![(0, 0), (10, 0), (100, 0)], vec![Length(10), Length(10)], Flex::StretchLast)]
|
||||
#[case::spacers(vec![(0, 0), (50, 0), (100, 0)], vec![Length(10), Length(10)], Flex::Stretch)]
|
||||
#[case::spacers(vec![(0, 0), (10, 0), (100, 0)], vec![Length(10), Length(10)], Flex::Legacy)]
|
||||
#[case::spacers(vec![(0, 0), (10, 80), (100, 0)], vec![Length(10), Length(10)], Flex::SpaceBetween)]
|
||||
#[case::spacers(vec![(0, 27), (37, 26), (73, 27)], vec![Length(10), Length(10)], Flex::SpaceAround)]
|
||||
#[case::spacers(vec![(0, 0), (10, 0), (20, 80)], vec![Length(10), Length(10)], Flex::Start)]
|
||||
@ -2264,8 +2203,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::spacers(vec![(0, 0), (10, 5), (100, 0)], vec![Length(10), Length(10)], Flex::StretchLast, 5)]
|
||||
#[case::spacers(vec![(0, 0), (48, 5), (100, 0)], vec![Length(10), Length(10)], Flex::Stretch, 5)]
|
||||
#[case::spacers(vec![(0, 0), (10, 5), (100, 0)], vec![Length(10), Length(10)], Flex::Legacy, 5)]
|
||||
#[case::spacers(vec![(0, 0), (10, 80), (100, 0)], vec![Length(10), Length(10)], Flex::SpaceBetween, 5)]
|
||||
#[case::spacers(vec![(0, 27), (37, 26), (73, 27)], vec![Length(10), Length(10)], Flex::SpaceAround, 5)]
|
||||
#[case::spacers(vec![(0, 0), (10, 5), (25, 75)], vec![Length(10), Length(10)], Flex::Start, 5)]
|
||||
@ -2291,8 +2229,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::StretchLast, 200)]
|
||||
#[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::Stretch, 200)]
|
||||
#[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::Legacy, 200)]
|
||||
#[case::spacers(vec![(0, 0), (10, 80), (100, 0)], vec![Length(10), Length(10)], Flex::SpaceBetween, 200)]
|
||||
#[case::spacers(vec![(0, 27), (37, 26), (73, 27)], vec![Length(10), Length(10)], Flex::SpaceAround, 200)]
|
||||
#[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::Start, 200)]
|
||||
@ -2316,6 +2253,27 @@ mod tests {
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::compare(vec![(0, 90), (90, 10)], vec![Min(10), Length(10)], Flex::Legacy)]
|
||||
#[case::compare(vec![(0, 90), (90, 10)], vec![Min(10), Length(10)], Flex::Start)]
|
||||
#[case::compare(vec![(0, 10), (10, 90)], vec![Min(10), Percentage(100)], Flex::Legacy)]
|
||||
#[case::compare(vec![(0, 10), (10, 90)], vec![Min(10), Percentage(100)], Flex::Start)]
|
||||
#[case::compare(vec![(0, 50), (50, 50)], vec![Percentage(50), Percentage(50)], Flex::Legacy)]
|
||||
#[case::compare(vec![(0, 50), (50, 50)], vec![Percentage(50), Percentage(50)], Flex::Start)]
|
||||
fn legacy_vs_default(
|
||||
#[case] expected: Vec<(u16, u16)>,
|
||||
#[case] constraints: Vec<Constraint>,
|
||||
#[case] flex: Flex,
|
||||
) {
|
||||
let rect = Rect::new(0, 0, 100, 1);
|
||||
let r = Layout::horizontal(constraints).flex(flex).split(rect);
|
||||
let result = r
|
||||
.iter()
|
||||
.map(|r| (r.x, r.width))
|
||||
.collect::<Vec<(u16, u16)>>();
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1,147 +0,0 @@
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
/// Option for segment size preferences
|
||||
///
|
||||
/// This controls how the space is distributed when the constraints are satisfied. By default, the
|
||||
/// last chunk is expanded to fill the remaining space, but this can be changed to prefer equal
|
||||
/// chunks or to not distribute extra space at all (which is the default used for laying out the
|
||||
/// columns for [`Table`] widgets).
|
||||
///
|
||||
/// Note: If you're using this feature please help us come up with a good name. See [Issue
|
||||
/// #536](https://github.com/ratatui-org/ratatui/issues/536) for more information.
|
||||
///
|
||||
/// [`Table`]: crate::widgets::Table
|
||||
#[stability::unstable(
|
||||
feature = "segment-size",
|
||||
reason = "The name for this feature is not final and may change in the future",
|
||||
issue = "https://github.com/ratatui-org/ratatui/issues/536"
|
||||
)]
|
||||
#[derive(Copy, Debug, Default, Display, EnumString, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum SegmentSize {
|
||||
/// prefer equal chunks if other constraints are all satisfied
|
||||
EvenDistribution,
|
||||
|
||||
/// the last chunk is expanded to fill the remaining space
|
||||
#[default]
|
||||
LastTakesRemainder,
|
||||
|
||||
/// extra space is not distributed
|
||||
None,
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use strum::ParseError;
|
||||
|
||||
use super::{SegmentSize::*, *};
|
||||
use crate::prelude::{Constraint::*, *};
|
||||
#[test]
|
||||
fn segment_size_to_string() {
|
||||
assert_eq!(EvenDistribution.to_string(), "EvenDistribution");
|
||||
assert_eq!(LastTakesRemainder.to_string(), "LastTakesRemainder");
|
||||
assert_eq!(None.to_string(), "None");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn segment_size_from_string() {
|
||||
assert_eq!(
|
||||
"EvenDistribution".parse::<SegmentSize>(),
|
||||
Ok(EvenDistribution)
|
||||
);
|
||||
assert_eq!(
|
||||
"LastTakesRemainder".parse::<SegmentSize>(),
|
||||
Ok(LastTakesRemainder)
|
||||
);
|
||||
assert_eq!("None".parse::<SegmentSize>(), Ok(None));
|
||||
assert_eq!("".parse::<SegmentSize>(), Err(ParseError::VariantNotFound));
|
||||
}
|
||||
|
||||
fn get_x_width_with_segment_size(
|
||||
segment_size: SegmentSize,
|
||||
constraints: Vec<Constraint>,
|
||||
target: Rect,
|
||||
) -> Vec<(u16, u16)> {
|
||||
#[allow(deprecated)]
|
||||
let layout = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(constraints)
|
||||
.segment_size(segment_size);
|
||||
let chunks = layout.split(target);
|
||||
chunks.iter().map(|r| (r.x, r.width)).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_equally_in_underspecified_case() {
|
||||
let target = Rect::new(100, 200, 10, 10);
|
||||
assert_eq!(
|
||||
get_x_width_with_segment_size(LastTakesRemainder, vec![Min(2), Min(2), Min(0)], target),
|
||||
[(100, 2), (102, 2), (104, 6)]
|
||||
);
|
||||
assert_eq!(
|
||||
get_x_width_with_segment_size(EvenDistribution, vec![Min(2), Min(2), Min(0)], target),
|
||||
[(100, 3), (103, 4), (107, 3)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_equally_in_overconstrained_case_for_min() {
|
||||
let target = Rect::new(100, 200, 100, 10);
|
||||
assert_eq!(
|
||||
get_x_width_with_segment_size(
|
||||
LastTakesRemainder,
|
||||
vec![Percentage(50), Min(10), Percentage(50)],
|
||||
target
|
||||
),
|
||||
[(100, 50), (150, 10), (160, 40)]
|
||||
);
|
||||
assert_eq!(
|
||||
get_x_width_with_segment_size(
|
||||
EvenDistribution,
|
||||
vec![Percentage(50), Min(10), Percentage(50)],
|
||||
target
|
||||
),
|
||||
[(100, 45), (145, 10), (155, 45)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_equally_in_overconstrained_case_for_max() {
|
||||
let target = Rect::new(100, 200, 100, 10);
|
||||
assert_eq!(
|
||||
get_x_width_with_segment_size(
|
||||
LastTakesRemainder,
|
||||
vec![Percentage(30), Max(10), Percentage(30)],
|
||||
target
|
||||
),
|
||||
[(100, 30), (130, 10), (140, 60)]
|
||||
);
|
||||
assert_eq!(
|
||||
get_x_width_with_segment_size(
|
||||
EvenDistribution,
|
||||
vec![Percentage(30), Max(10), Percentage(30)],
|
||||
target
|
||||
),
|
||||
[(100, 45), (145, 10), (155, 45)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_equally_in_overconstrained_case_for_length() {
|
||||
let target = Rect::new(100, 200, 100, 10);
|
||||
assert_eq!(
|
||||
get_x_width_with_segment_size(
|
||||
LastTakesRemainder,
|
||||
vec![Percentage(50), Length(10), Percentage(50)],
|
||||
target
|
||||
),
|
||||
[(100, 50), (150, 10), (160, 40)]
|
||||
);
|
||||
assert_eq!(
|
||||
get_x_width_with_segment_size(
|
||||
EvenDistribution,
|
||||
vec![Percentage(50), Length(10), Percentage(50)],
|
||||
target
|
||||
),
|
||||
[(100, 45), (145, 10), (155, 45)]
|
||||
);
|
||||
}
|
||||
}
|
@ -1,11 +1,7 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
layout::{Flex, SegmentSize},
|
||||
prelude::*,
|
||||
widgets::Block,
|
||||
};
|
||||
use crate::{layout::Flex, prelude::*, widgets::Block};
|
||||
|
||||
/// A widget to display data in formatted columns.
|
||||
///
|
||||
@ -539,42 +535,6 @@ impl<'a> Table<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set how extra space is distributed amongst columns.
|
||||
///
|
||||
/// This determines how the space is distributed when the constraints are satisfied. By default,
|
||||
/// the extra space is not distributed at all. But this can be changed to distribute all extra
|
||||
/// space to the last column or to distribute it equally.
|
||||
///
|
||||
/// This is a fluent setter method which must be chained or used as it consumes self
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Create a table that needs at least 30 columns to display. Any extra space will be assigned
|
||||
/// to the last column.
|
||||
#[cfg_attr(feature = "unstable", doc = " ```")]
|
||||
#[cfg_attr(not(feature = "unstable"), doc = " ```ignore")]
|
||||
/// # use ratatui::layout::{Constraint, SegmentSize};
|
||||
/// # use ratatui::widgets::{Table, Row};
|
||||
/// let widths = [Constraint::Min(10), Constraint::Min(10), Constraint::Min(10)];
|
||||
/// let table = Table::new(Vec::<Row>::new(), widths)
|
||||
/// .segment_size(SegmentSize::LastTakesRemainder);
|
||||
/// ```
|
||||
#[stability::unstable(
|
||||
feature = "segment-size",
|
||||
reason = "The name for this feature is not final and may change in the future",
|
||||
issue = "https://github.com/ratatui-org/ratatui/issues/536"
|
||||
)]
|
||||
#[deprecated(since = "0.26.0", note = "You should use Table::flex instead.")]
|
||||
pub const fn segment_size(self, segment_size: SegmentSize) -> Self {
|
||||
let translated_to_flex = match segment_size {
|
||||
SegmentSize::None => Flex::Start,
|
||||
SegmentSize::EvenDistribution => Flex::Stretch,
|
||||
SegmentSize::LastTakesRemainder => Flex::StretchLast,
|
||||
};
|
||||
|
||||
self.flex(translated_to_flex)
|
||||
}
|
||||
|
||||
/// Set how extra space is distributed amongst columns.
|
||||
///
|
||||
/// This determines how the space is distributed when the constraints are satisfied. By default,
|
||||
@ -595,7 +555,7 @@ impl<'a> Table<'a> {
|
||||
/// Constraint::Min(10),
|
||||
/// Constraint::Min(10),
|
||||
/// ];
|
||||
/// let table = Table::new(Vec::<Row>::new(), widths).flex(Flex::StretchLast);
|
||||
/// let table = Table::new(Vec::<Row>::new(), widths).flex(Flex::Legacy);
|
||||
/// ```
|
||||
pub const fn flex(mut self, flex: Flex) -> Self {
|
||||
self.flex = flex;
|
||||
@ -774,7 +734,7 @@ impl Table<'_> {
|
||||
// this will always allocate a selection area
|
||||
let [_selection_area, columns_area] =
|
||||
Rect::new(0, 0, max_width, 1).split(&Layout::horizontal([
|
||||
Constraint::Fixed(selection_width),
|
||||
Constraint::Length(selection_width),
|
||||
Constraint::Fill(0),
|
||||
]));
|
||||
let rects = Layout::horizontal(widths)
|
||||
@ -1246,16 +1206,16 @@ mod tests {
|
||||
|
||||
// without selection, less than needed width
|
||||
let table = Table::default().widths([Length(4), Length(4)]);
|
||||
assert_eq!(table.get_columns_widths(7, 0), [(0, 4), (5, 2)]);
|
||||
assert_eq!(table.get_columns_widths(7, 0), [(0, 3), (4, 3)]);
|
||||
|
||||
// with selection, less than needed width
|
||||
// <--------7px-------->
|
||||
// ┌────────┐x┌────────┐
|
||||
// │ (3, 3) │x│ (7, 0) │
|
||||
// │ (3, 2) │x│ (6, 1) │
|
||||
// └────────┘x└────────┘
|
||||
// column spacing (i.e. `x`) is always prioritized
|
||||
let table = Table::default().widths([Length(4), Length(4)]);
|
||||
assert_eq!(table.get_columns_widths(7, 3), [(3, 3), (7, 0)]);
|
||||
assert_eq!(table.get_columns_widths(7, 3), [(3, 2), (6, 1)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1270,11 +1230,11 @@ mod tests {
|
||||
|
||||
// without selection, less than needed width
|
||||
let table = Table::default().widths([Max(4), Max(4)]);
|
||||
assert_eq!(table.get_columns_widths(7, 0), [(0, 4), (5, 2)]);
|
||||
assert_eq!(table.get_columns_widths(7, 0), [(0, 3), (4, 3)]);
|
||||
|
||||
// with selection, less than needed width
|
||||
let table = Table::default().widths([Max(4), Max(4)]);
|
||||
assert_eq!(table.get_columns_widths(7, 3), [(3, 3), (7, 0)]);
|
||||
assert_eq!(table.get_columns_widths(7, 3), [(3, 2), (6, 1)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1285,21 +1245,21 @@ mod tests {
|
||||
|
||||
// without selection, more than needed width
|
||||
let table = Table::default().widths([Min(4), Min(4)]);
|
||||
assert_eq!(table.get_columns_widths(20, 0), [(0, 4), (5, 4)]);
|
||||
assert_eq!(table.get_columns_widths(20, 0), [(0, 10), (11, 9)]);
|
||||
|
||||
// with selection, more than needed width
|
||||
let table = Table::default().widths([Min(4), Min(4)]);
|
||||
assert_eq!(table.get_columns_widths(20, 3), [(3, 4), (8, 4)]);
|
||||
assert_eq!(table.get_columns_widths(20, 3), [(3, 8), (12, 8)]);
|
||||
|
||||
// without selection, less than needed width
|
||||
// allocates spacer
|
||||
let table = Table::default().widths([Min(4), Min(4)]);
|
||||
assert_eq!(table.get_columns_widths(7, 0), [(0, 4), (5, 2)]);
|
||||
assert_eq!(table.get_columns_widths(7, 0), [(0, 3), (4, 3)]);
|
||||
|
||||
// with selection, less than needed width
|
||||
// always allocates selection and spacer
|
||||
let table = Table::default().widths([Min(4), Min(4)]);
|
||||
assert_eq!(table.get_columns_widths(7, 3), [(3, 3), (7, 0)]);
|
||||
assert_eq!(table.get_columns_widths(7, 3), [(3, 2), (6, 1)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1352,12 +1312,12 @@ mod tests {
|
||||
let table = Table::default().widths([Min(10), Min(10), Min(1)]);
|
||||
assert_eq!(
|
||||
table.get_columns_widths(62, 0),
|
||||
&[(0, 10), (11, 10), (22, 1)]
|
||||
&[(0, 20), (21, 20), (42, 20)]
|
||||
);
|
||||
|
||||
let table = Table::default()
|
||||
.widths([Min(10), Min(10), Min(1)])
|
||||
.flex(Flex::StretchLast);
|
||||
.flex(Flex::Legacy);
|
||||
assert_eq!(
|
||||
table.get_columns_widths(62, 0),
|
||||
&[(0, 10), (11, 10), (22, 40)]
|
||||
@ -1365,10 +1325,10 @@ mod tests {
|
||||
|
||||
let table = Table::default()
|
||||
.widths([Min(10), Min(10), Min(1)])
|
||||
.flex(Flex::Stretch);
|
||||
.flex(Flex::SpaceBetween);
|
||||
assert_eq!(
|
||||
table.get_columns_widths(62, 0),
|
||||
&[(0, 20), (21, 20), (42, 20)]
|
||||
&[(0, 21), (21, 20), (41, 21)]
|
||||
);
|
||||
}
|
||||
|
||||
@ -1379,24 +1339,16 @@ mod tests {
|
||||
let table = Table::default().widths([Min(10), Min(10), Min(1)]);
|
||||
assert_eq!(
|
||||
table.get_columns_widths(62, 0),
|
||||
&[(0, 10), (11, 10), (22, 1)]
|
||||
&[(0, 20), (21, 20), (42, 20)]
|
||||
);
|
||||
|
||||
let table = Table::default()
|
||||
.widths([Min(10), Min(10), Min(1)])
|
||||
.segment_size(SegmentSize::LastTakesRemainder);
|
||||
.flex(Flex::Legacy);
|
||||
assert_eq!(
|
||||
table.get_columns_widths(62, 0),
|
||||
&[(0, 10), (11, 10), (22, 40)]
|
||||
);
|
||||
|
||||
let table = Table::default()
|
||||
.widths([Min(10), Min(10), Min(1)])
|
||||
.segment_size(SegmentSize::EvenDistribution);
|
||||
assert_eq!(
|
||||
table.get_columns_widths(62, 0),
|
||||
&[(0, 20), (21, 20), (42, 20)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1526,7 +1478,7 @@ mod tests {
|
||||
Some(0), // selection
|
||||
),
|
||||
Buffer::with_lines(vec![
|
||||
">>>ABCDE 12345", // row 1
|
||||
">>>ABCDE 12345 ", // row 1
|
||||
" ", // row 2
|
||||
" ", // row 3
|
||||
])
|
||||
@ -1541,7 +1493,7 @@ mod tests {
|
||||
None, // selection
|
||||
),
|
||||
Buffer::with_lines(vec![
|
||||
" ABCDE 12345", // row 1
|
||||
" ABCDE 12345 ", // row 1
|
||||
" ", // row 2
|
||||
" ", // row 3
|
||||
])
|
||||
@ -1556,7 +1508,7 @@ mod tests {
|
||||
Some(0), // selection
|
||||
),
|
||||
Buffer::with_lines(vec![
|
||||
">>>ABCDE 12345", // row 1
|
||||
">>>ABCDE 12345 ", // row 1
|
||||
" ", // row 2
|
||||
" ", // row 3
|
||||
])
|
||||
@ -1609,7 +1561,7 @@ mod tests {
|
||||
None, // selection
|
||||
),
|
||||
Buffer::with_lines(vec![
|
||||
" ABCDE 1", // highlight_symbol and spacing are prioritized
|
||||
" ABC 123", // highlight_symbol and spacing are prioritized
|
||||
" ", // row 2
|
||||
" ", // row 3
|
||||
])
|
||||
@ -1624,7 +1576,7 @@ mod tests {
|
||||
None, // selection
|
||||
),
|
||||
Buffer::with_lines(vec![
|
||||
" ABCD 1", // highlight_symbol and spacing are prioritized
|
||||
" ABC 12", // highlight_symbol and spacing are prioritized
|
||||
" ", // row 2
|
||||
" ", // row 3
|
||||
])
|
||||
@ -1637,7 +1589,7 @@ mod tests {
|
||||
None, // selection
|
||||
),
|
||||
Buffer::with_lines(vec![
|
||||
" ABCD ", // highlight_symbol and spacing are prioritized
|
||||
" AB 12", // highlight_symbol and spacing are prioritized
|
||||
" ", // row 2
|
||||
" ", // row 3
|
||||
])
|
||||
@ -1650,7 +1602,7 @@ mod tests {
|
||||
None, // selection
|
||||
),
|
||||
Buffer::with_lines(vec![
|
||||
" ABC ", // highlight_symbol and spacing are prioritized
|
||||
" AB 1", // highlight_symbol and spacing are prioritized
|
||||
" ", // row 2
|
||||
" ", // row 3
|
||||
])
|
||||
@ -1659,7 +1611,22 @@ mod tests {
|
||||
let table = Table::default()
|
||||
.rows(vec![Row::new(vec!["ABCDE", "12345"])])
|
||||
.highlight_spacing(HighlightSpacing::Always)
|
||||
.flex(Flex::Stretch)
|
||||
.flex(Flex::Legacy)
|
||||
.highlight_symbol(">>>")
|
||||
.column_spacing(1);
|
||||
let area = Rect::new(0, 0, 10, 3);
|
||||
let mut buf = Buffer::empty(area);
|
||||
Widget::render(table, area, &mut buf);
|
||||
// highlight_symbol and spacing are prioritized but columns are evenly distributed
|
||||
assert_buffer_eq!(
|
||||
buf,
|
||||
Buffer::with_lines(vec![" ABCDE 1", " ", " ",])
|
||||
);
|
||||
|
||||
let table = Table::default()
|
||||
.rows(vec![Row::new(vec!["ABCDE", "12345"])])
|
||||
.highlight_spacing(HighlightSpacing::Always)
|
||||
.flex(Flex::Start)
|
||||
.highlight_symbol(">>>")
|
||||
.column_spacing(1);
|
||||
let area = Rect::new(0, 0, 10, 3);
|
||||
@ -1693,7 +1660,7 @@ mod tests {
|
||||
Some(0), // selection
|
||||
),
|
||||
Buffer::with_lines(vec![
|
||||
">>>ABCDE 1", // row 1
|
||||
">>>ABC 123", // row 1
|
||||
" ", // row 2
|
||||
" ", // row 3
|
||||
])
|
||||
@ -1707,7 +1674,7 @@ mod tests {
|
||||
Some(0), // selection
|
||||
),
|
||||
Buffer::with_lines(vec![
|
||||
">>>ABCDE 1", // highlight column and spacing are prioritized
|
||||
">>>ABC 123", // highlight column and spacing are prioritized
|
||||
" ", // row 2
|
||||
" ", // row 3
|
||||
])
|
||||
@ -1754,7 +1721,7 @@ mod tests {
|
||||
None, // selection
|
||||
),
|
||||
Buffer::with_lines(vec![
|
||||
" ABCDE12", // highlight column and spacing are prioritized
|
||||
" ABCD123", // highlight column and spacing are prioritized
|
||||
" ", // row 2
|
||||
" ", // row 3
|
||||
])
|
||||
@ -1780,7 +1747,7 @@ mod tests {
|
||||
Some(0), // selection
|
||||
),
|
||||
Buffer::with_lines(vec![
|
||||
">>>ABCDE12", // highlight column and spacing are prioritized
|
||||
">>>ABCD123", // highlight column and spacing are prioritized
|
||||
" ", // row 2
|
||||
" ", // row 3
|
||||
])
|
||||
@ -1793,7 +1760,7 @@ mod tests {
|
||||
Some(0), // selection
|
||||
),
|
||||
Buffer::with_lines(vec![
|
||||
">>>ABCDE12", // highlight column and spacing are prioritized
|
||||
">>>ABCD123", // highlight column and spacing are prioritized
|
||||
" ", // row 2
|
||||
" ", // row 3
|
||||
])
|
||||
|
@ -97,12 +97,12 @@ fn widgets_table_column_spacing_can_be_changed() {
|
||||
7,
|
||||
Buffer::with_lines(vec![
|
||||
"┌────────────────────────────┐",
|
||||
"│Head1 Head2 Head│",
|
||||
"│Head1 Head Head3│",
|
||||
"│ │",
|
||||
"│Row11 Row12 Row1│",
|
||||
"│Row21 Row22 Row2│",
|
||||
"│Row31 Row32 Row3│",
|
||||
"│Row41 Row42 Row4│",
|
||||
"│Row11 Row1 Row13│",
|
||||
"│Row21 Row2 Row23│",
|
||||
"│Row31 Row3 Row33│",
|
||||
"│Row41 Row4 Row43│",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"└────────────────────────────┘",
|
||||
@ -408,12 +408,12 @@ fn widgets_table_columns_widths_can_use_mixed_constraints() {
|
||||
],
|
||||
Buffer::with_lines(vec![
|
||||
"┌────────────────────────────┐",
|
||||
"│Head1 Head2 │",
|
||||
"│Head1 Head2 Head3 │",
|
||||
"│ │",
|
||||
"│Row11 Row12 │",
|
||||
"│Row21 Row22 │",
|
||||
"│Row31 Row32 │",
|
||||
"│Row41 Row42 │",
|
||||
"│Row11 Row12 Row13 │",
|
||||
"│Row21 Row22 Row23 │",
|
||||
"│Row31 Row32 Row33 │",
|
||||
"│Row41 Row42 Row43 │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"└────────────────────────────┘",
|
||||
|
Loading…
x
Reference in New Issue
Block a user