diff --git a/Cargo.toml b/Cargo.toml index ce9123b7..35ce1a4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/examples/constraints.rs b/examples/constraints.rs index 8256fdfd..8c484807 100644 --- a/examples/constraints.rs +++ b/examples/constraints.rs @@ -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, diff --git a/examples/flex.rs b/examples/flex.rs index 32bab9db..8bd8f355 100644 --- a/examples/flex.rs +++ b/examples/flex.rs @@ -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, diff --git a/examples/layout.rs b/examples/layout.rs index bfff020d..41900626 100644 --- a/examples/layout.rs +++ b/examples/layout.rs @@ -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}"), } } diff --git a/src/layout.rs b/src/layout.rs index 276c0d61..5cb48d41 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -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; diff --git a/src/layout/constraint.rs b/src/layout/constraint.rs index 496e940a..55dd4cca 100644 --- a/src/layout/constraint.rs +++ b/src/layout/constraint.rs @@ -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(fixed_lengths: T) -> Vec - where - T: IntoIterator, - { - 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 = [ diff --git a/src/layout/flex.rs b/src/layout/flex.rs index b6717578..128abeff 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -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-------------------------------------> diff --git a/src/layout/layout.rs b/src/layout/layout.rs index 18bcd37e..0c7bcea8 100644 --- a/src/layout/layout.rs +++ b/src/layout/layout.rs @@ -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 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 = 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, #[case] constraints: Vec) { 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, #[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, #[case] constraints: Vec) { + #[case::previously_unstable_test(vec![25, 25, 50], vec![Length(25), Length(25), Length(25)])] + fn length_is_higher_priority( + #[case] expected: Vec, + #[case] constraints: Vec, + ) { 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, + #[case] constraints: Vec, + ) { + 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::>(); + 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::>(); + 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::>(); + 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::>(); + 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::>(); + 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, #[case] constraints: Vec) { 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, #[case] constraints: Vec) { 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, #[case] constraints: Vec) { 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, @@ -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, ) { 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, @@ -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, @@ -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, @@ -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, @@ -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::>(); 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, + #[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::>(); + assert_eq!(expected, result); + } } #[test] diff --git a/src/layout/segment_size.rs b/src/layout/segment_size.rs deleted file mode 100644 index e5c83796..00000000 --- a/src/layout/segment_size.rs +++ /dev/null @@ -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::(), - Ok(EvenDistribution) - ); - assert_eq!( - "LastTakesRemainder".parse::(), - Ok(LastTakesRemainder) - ); - assert_eq!("None".parse::(), Ok(None)); - assert_eq!("".parse::(), Err(ParseError::VariantNotFound)); - } - - fn get_x_width_with_segment_size( - segment_size: SegmentSize, - constraints: Vec, - 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)] - ); - } -} diff --git a/src/widgets/table/table.rs b/src/widgets/table/table.rs index 95f91d73..b0232488 100644 --- a/src/widgets/table/table.rs +++ b/src/widgets/table/table.rs @@ -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::::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::::new(), widths).flex(Flex::StretchLast); + /// let table = Table::new(Vec::::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 ]) diff --git a/tests/widgets_table.rs b/tests/widgets_table.rs index 4a1bde73..1d274ca2 100755 --- a/tests/widgets_table.rs +++ b/tests/widgets_table.rs @@ -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 │", "│ │", "│ │", "└────────────────────────────┘",