Skip to main content

ratatui_core/layout/
layout.rs

1use alloc::rc::Rc;
2use alloc::vec::Vec;
3use core::array::TryFromSliceError;
4use core::iter;
5#[cfg(feature = "layout-cache")]
6use core::num::NonZeroUsize;
7
8use hashbrown::HashMap;
9use itertools::Itertools;
10use kasuari::WeightedRelation::{EQ, GE, LE};
11use kasuari::{AddConstraintError, Expression, Solver, Strength, Variable};
12#[cfg(feature = "layout-cache")]
13use lru::LruCache;
14
15use self::strengths::{
16    ALL_SEGMENT_GROW, FILL_GROW, GROW, LENGTH_SIZE_EQ, MAX_SIZE_EQ, MAX_SIZE_LE, MIN_SIZE_EQ,
17    MIN_SIZE_GE, PERCENTAGE_SIZE_EQ, RATIO_SIZE_EQ, SPACE_GROW, SPACER_SIZE_EQ,
18};
19use crate::layout::{Constraint, Direction, Flex, Margin, Rect};
20
21type Rects = Rc<[Rect]>;
22type Segments = Rects;
23type Spacers = Rects;
24// The solution to a Layout solve contains two `Rects`, where `Rects` is effectively a `[Rect]`.
25//
26// 1. `[Rect]` that contains positions for the segments corresponding to user provided constraints
27// 2. `[Rect]` that contains spacers around the user provided constraints
28//
29// <------------------------------------80 px------------------------------------->
30// ┌   ┐┌──────────────────┐┌   ┐┌──────────────────┐┌   ┐┌──────────────────┐┌   ┐
31//   1  │        a         │  2  │         b        │  3  │         c        │  4
32// └   ┘└──────────────────┘└   ┘└──────────────────┘└   ┘└──────────────────┘└   ┘
33//
34// Number of spacers will always be one more than number of segments.
35// With std: cache can store Rc directly (no Send needed thanks to thread_local)
36#[cfg(all(feature = "layout-cache", feature = "std"))]
37type Cache = LruCache<(Rect, Layout), (Segments, Spacers)>;
38
39// Without std: cache stores Vec instead (Send-safe for critical_section::Mutex)
40#[cfg(all(feature = "layout-cache", not(feature = "std")))]
41type Cache = LruCache<(Rect, Layout), (Vec<Rect>, Vec<Rect>)>;
42
43// Multiplier that decides floating point precision when rounding.
44// The number of zeros in this number is the precision for the rounding of f64 to u16 in layout
45// calculations.
46const FLOAT_PRECISION_MULTIPLIER: f64 = 100.0;
47
48#[cfg(all(feature = "layout-cache", feature = "std"))]
49std::thread_local! {
50    static LAYOUT_CACHE: core::cell::RefCell<Cache> = core::cell::RefCell::new(Cache::new(
51        NonZeroUsize::new(Layout::DEFAULT_CACHE_SIZE).unwrap(),
52    ));
53}
54
55#[cfg(all(feature = "layout-cache", not(feature = "std")))]
56static LAYOUT_CACHE: critical_section::Mutex<core::cell::RefCell<Option<Cache>>> =
57    critical_section::Mutex::new(core::cell::RefCell::new(None));
58
59/// Represents the spacing between segments in a layout.
60///
61/// The `Spacing` enum is used to define the spacing between segments in a layout. It can represent
62/// either positive spacing (space between segments) or negative spacing (overlap between segments).
63///
64/// # Variants
65///
66/// - `Space(u16)`: Represents positive spacing between segments. The value indicates the number of
67///   cells.
68/// - `Overlap(u16)`: Represents negative spacing, causing overlap between segments. The value
69///   indicates the number of overlapping cells.
70///
71/// # Default
72///
73/// The default value for `Spacing` is `Space(0)`, which means no spacing or no overlap between
74/// segments.
75///
76/// # Conversions
77///
78/// The `Spacing` enum can be created from different integer types:
79///
80/// - From `u16`: Directly converts the value to `Spacing::Space`.
81/// - From `i16`: Converts negative values to `Spacing::Overlap` and non-negative values to
82///   `Spacing::Space`.
83/// - From `i32`: Clamps the value to the range of `i16` and converts negative values to
84///   `Spacing::Overlap` and non-negative values to `Spacing::Space`.
85///
86/// See the [`Layout::spacing`] method for details on how to use this enum.
87#[derive(Debug, Clone, Eq, PartialEq, Hash)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89pub enum Spacing {
90    Space(u16),
91    Overlap(u16),
92}
93
94impl Default for Spacing {
95    fn default() -> Self {
96        Self::Space(0)
97    }
98}
99
100impl From<i32> for Spacing {
101    fn from(value: i32) -> Self {
102        Self::from(value.clamp(i32::from(i16::MIN), i32::from(i16::MAX)) as i16)
103    }
104}
105
106impl From<u16> for Spacing {
107    fn from(value: u16) -> Self {
108        Self::Space(value)
109    }
110}
111
112impl From<i16> for Spacing {
113    fn from(value: i16) -> Self {
114        if value < 0 {
115            Self::Overlap(value.unsigned_abs())
116        } else {
117            Self::Space(value.unsigned_abs())
118        }
119    }
120}
121
122/// The primary layout engine for dividing terminal space using constraints and direction.
123///
124/// A layout is a set of constraints that can be applied to a given area to split it into smaller
125/// rectangular areas. This is the core building block for creating structured user interfaces in
126/// terminal applications.
127///
128/// A layout is composed of:
129/// - a direction (horizontal or vertical)
130/// - a set of constraints (length, ratio, percentage, fill, min, max)
131/// - a margin (horizontal and vertical), the space between the edge of the main area and the split
132///   areas
133/// - a flex option that controls space distribution
134/// - a spacing option that controls gaps between segments
135///
136/// The algorithm used to compute the layout is based on the [`kasuari`] solver, a linear constraint
137/// solver that computes positions and sizes to satisfy as many constraints as possible in order of
138/// their priorities.
139///
140/// When the layout is computed, the result is cached in a thread-local cache, so that subsequent
141/// calls with the same parameters are faster. The cache is a `LruCache`, and the size of the cache
142/// can be configured using [`Layout::init_cache()`] when the `layout-cache` feature is enabled.
143///
144/// # Construction
145///
146/// - [`default`](Default::default) - Create a layout with default values (vertical direction, no
147///   constraints, no margin)
148/// - [`new`](Self::new) - Create a new layout with a given direction and constraints
149/// - [`vertical`](Self::vertical) - Create a new vertical layout with the given constraints
150/// - [`horizontal`](Self::horizontal) - Create a new horizontal layout with the given constraints
151///
152/// # Configuration
153///
154/// - [`direction`](Self::direction) - Set the direction of the layout
155/// - [`constraints`](Self::constraints) - Set the constraints of the layout
156/// - [`margin`](Self::margin) - Set uniform margin on all sides
157/// - [`horizontal_margin`](Self::horizontal_margin) - Set the horizontal margin of the layout
158/// - [`vertical_margin`](Self::vertical_margin) - Set the vertical margin of the layout
159/// - [`flex`](Self::flex) - Set the way space is distributed when constraints are satisfied
160/// - [`spacing`](Self::spacing) - Set the gap between the constraints of the layout
161///
162/// # Layout Operations
163///
164/// - [`areas`](Self::areas) - Split area into fixed number of rectangles (compile-time known)
165/// - [`spacers`](Self::spacers) - Get spacer rectangles between layout areas
166/// - [`split`](Self::split) - Split area into rectangles (runtime determined count)
167/// - [`split_with_spacers`](Self::split_with_spacers) - Split area and return both areas and
168///   spacers
169///
170/// # Cache Management
171///
172/// - [`init_cache`](Self::init_cache) - Initialize layout cache with custom size
173///
174/// # Example
175///
176/// ```rust
177/// use ratatui_core::buffer::Buffer;
178/// use ratatui_core::layout::{Constraint, Direction, Layout, Rect};
179/// use ratatui_core::text::Text;
180/// use ratatui_core::widgets::Widget;
181///
182/// fn render(area: Rect, buf: &mut Buffer) {
183///     let layout = Layout::vertical([Constraint::Length(5), Constraint::Fill(1)]);
184///     let [top, bottom] = layout.areas(area);
185///     Text::from("foo").render(top, buf);
186///     Text::from("bar").render(bottom, buf);
187/// }
188/// ```
189///
190/// See the `layout`, `flex`, and `constraints` examples in the [Examples] folder for more details
191/// about how to use layouts.
192///
193/// For comprehensive layout documentation and examples, see the [`layout`](crate::layout) module.
194///
195/// ![layout
196/// example](https://camo.githubusercontent.com/77d22f3313b782a81e5e033ef82814bb48d786d2598699c27f8e757ccee62021/68747470733a2f2f7668732e636861726d2e73682f7668732d315a4e6f4e4c4e6c4c746b4a58706767396e435635652e676966)
197///
198/// [`kasuari`]: https://crates.io/crates/kasuari
199/// [Examples]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
200#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
201#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
202pub struct Layout {
203    direction: Direction,
204    constraints: Vec<Constraint>,
205    margin: Margin,
206    flex: Flex,
207    spacing: Spacing,
208}
209
210impl Layout {
211    /// This is a somewhat arbitrary size for the layout cache based on adding the columns and rows
212    /// on my laptop's terminal (171+51 = 222) and doubling it for good measure and then adding a
213    /// bit more to make it a round number. This gives enough entries to store a layout for every
214    /// row and every column, twice over, which should be enough for most apps. For those that need
215    /// more, the cache size can be set with `Layout::init_cache()` (requires the `layout-cache`
216    /// feature).
217    #[cfg(feature = "layout-cache")]
218    pub const DEFAULT_CACHE_SIZE: usize = 500;
219
220    /// Creates a new layout with default values.
221    ///
222    /// The `constraints` parameter accepts any type that implements `IntoIterator<Item =
223    /// Into<Constraint>>`. This includes arrays, slices, vectors, iterators. `Into<Constraint>` is
224    /// implemented on `u16`, so you can pass an array, `Vec`, etc. of `u16` to this function to
225    /// create a layout with fixed size chunks.
226    ///
227    /// Default values for the other fields are:
228    ///
229    /// - `margin`: 0, 0
230    /// - `flex`: [`Flex::Start`]
231    /// - `spacing`: 0
232    ///
233    /// # Examples
234    ///
235    /// ```rust
236    /// use ratatui_core::layout::{Constraint, Direction, Layout};
237    ///
238    /// Layout::new(
239    ///     Direction::Horizontal,
240    ///     [Constraint::Length(5), Constraint::Fill(1)],
241    /// );
242    ///
243    /// Layout::new(
244    ///     Direction::Vertical,
245    ///     [1, 2, 3].iter().map(|&c| Constraint::Length(c)),
246    /// );
247    ///
248    /// Layout::new(Direction::Horizontal, vec![1, 2]);
249    /// ```
250    pub fn new<I>(direction: Direction, constraints: I) -> Self
251    where
252        I: IntoIterator,
253        I::Item: Into<Constraint>,
254    {
255        Self {
256            direction,
257            constraints: constraints.into_iter().map(Into::into).collect(),
258            ..Self::default()
259        }
260    }
261
262    /// Creates a new vertical layout with default values.
263    ///
264    /// The `constraints` parameter accepts any type that implements `IntoIterator<Item =
265    /// Into<Constraint>>`. This includes arrays, slices, vectors, iterators, etc.
266    ///
267    /// # Examples
268    ///
269    /// ```rust
270    /// use ratatui_core::layout::{Constraint, Layout};
271    ///
272    /// let layout = Layout::vertical([Constraint::Length(5), Constraint::Fill(1)]);
273    /// ```
274    pub fn vertical<I>(constraints: I) -> Self
275    where
276        I: IntoIterator,
277        I::Item: Into<Constraint>,
278    {
279        Self::new(Direction::Vertical, constraints.into_iter().map(Into::into))
280    }
281
282    /// Creates a new horizontal layout with default values.
283    ///
284    /// The `constraints` parameter accepts any type that implements `IntoIterator<Item =
285    /// Into<Constraint>>`. This includes arrays, slices, vectors, iterators, etc.
286    ///
287    /// # Examples
288    ///
289    /// ```rust
290    /// use ratatui_core::layout::{Constraint, Layout};
291    ///
292    /// let layout = Layout::horizontal([Constraint::Length(5), Constraint::Fill(1)]);
293    /// ```
294    pub fn horizontal<I>(constraints: I) -> Self
295    where
296        I: IntoIterator,
297        I::Item: Into<Constraint>,
298    {
299        Self::new(
300            Direction::Horizontal,
301            constraints.into_iter().map(Into::into),
302        )
303    }
304
305    /// Initialize an empty cache with a custom size. The cache is keyed on the layout and area, so
306    /// that subsequent calls with the same parameters are faster. The cache is a `LruCache`, and
307    /// grows until `cache_size` is reached.
308    ///
309    /// By default, the cache size is [`Self::DEFAULT_CACHE_SIZE`].
310    #[cfg(feature = "layout-cache")]
311    pub fn init_cache(cache_size: NonZeroUsize) {
312        Self::resize_cache(cache_size);
313    }
314
315    #[cfg(all(feature = "layout-cache", feature = "std"))]
316    fn resize_cache(cache_size: NonZeroUsize) {
317        LAYOUT_CACHE.with_borrow_mut(|cache| cache.resize(cache_size));
318    }
319
320    #[cfg(all(feature = "layout-cache", not(feature = "std")))]
321    fn resize_cache(cache_size: NonZeroUsize) {
322        critical_section::with(|cs| {
323            let mut cache = LAYOUT_CACHE.borrow(cs).borrow_mut();
324            match cache.as_mut() {
325                Some(c) => c.resize(cache_size),
326                None => *cache = Some(Cache::new(cache_size)),
327            }
328        });
329    }
330
331    /// Set the direction of the layout.
332    ///
333    /// # Examples
334    ///
335    /// ```rust
336    /// use ratatui_core::layout::{Constraint, Direction, Layout, Rect};
337    ///
338    /// let layout = Layout::default()
339    ///     .direction(Direction::Horizontal)
340    ///     .constraints([Constraint::Length(5), Constraint::Fill(1)])
341    ///     .split(Rect::new(0, 0, 10, 10));
342    /// assert_eq!(layout[..], [Rect::new(0, 0, 5, 10), Rect::new(5, 0, 5, 10)]);
343    ///
344    /// let layout = Layout::default()
345    ///     .direction(Direction::Vertical)
346    ///     .constraints([Constraint::Length(5), Constraint::Fill(1)])
347    ///     .split(Rect::new(0, 0, 10, 10));
348    /// assert_eq!(layout[..], [Rect::new(0, 0, 10, 5), Rect::new(0, 5, 10, 5)]);
349    /// ```
350    #[must_use = "method moves the value of self and returns the modified value"]
351    pub const fn direction(mut self, direction: Direction) -> Self {
352        self.direction = direction;
353        self
354    }
355
356    /// Sets the constraints of the layout.
357    ///
358    /// The `constraints` parameter accepts any type that implements `IntoIterator<Item =
359    /// Into<Constraint>>`. This includes arrays, slices, vectors, iterators. `Into<Constraint>` is
360    /// implemented on u16, so you can pass an array or vec of u16 to this function to create a
361    /// layout with fixed size chunks.
362    ///
363    /// Note that the constraints are applied to the whole area that is to be split, so using
364    /// percentages and ratios with the other constraints may not have the desired effect of
365    /// splitting the area up. (e.g. splitting 100 into [min 20, 50%, 50%], may not result in [20,
366    /// 40, 40] but rather an indeterminate result between [20, 50, 30] and [20, 30, 50]).
367    ///
368    /// # Examples
369    ///
370    /// ```rust
371    /// use ratatui_core::layout::{Constraint, Layout, Rect};
372    ///
373    /// let layout = Layout::default()
374    ///     .constraints([
375    ///         Constraint::Percentage(20),
376    ///         Constraint::Ratio(1, 5),
377    ///         Constraint::Length(2),
378    ///         Constraint::Min(2),
379    ///         Constraint::Max(2),
380    ///     ])
381    ///     .split(Rect::new(0, 0, 10, 10));
382    /// assert_eq!(
383    ///     layout[..],
384    ///     [
385    ///         Rect::new(0, 0, 10, 2),
386    ///         Rect::new(0, 2, 10, 2),
387    ///         Rect::new(0, 4, 10, 2),
388    ///         Rect::new(0, 6, 10, 2),
389    ///         Rect::new(0, 8, 10, 2),
390    ///     ]
391    /// );
392    ///
393    /// Layout::default().constraints([Constraint::Fill(1)]);
394    /// Layout::default().constraints(&[Constraint::Fill(1)]);
395    /// Layout::default().constraints(vec![Constraint::Fill(1)]);
396    /// Layout::default().constraints([Constraint::Fill(1)].iter().filter(|_| true));
397    /// Layout::default().constraints([1, 2, 3].iter().map(|&c| Constraint::Length(c)));
398    /// Layout::default().constraints([1, 2, 3]);
399    /// Layout::default().constraints(vec![1, 2, 3]);
400    /// ```
401    #[must_use = "method moves the value of self and returns the modified value"]
402    pub fn constraints<I>(mut self, constraints: I) -> Self
403    where
404        I: IntoIterator,
405        I::Item: Into<Constraint>,
406    {
407        self.constraints = constraints.into_iter().map(Into::into).collect();
408        self
409    }
410
411    /// Set the margin of the layout.
412    ///
413    /// # Examples
414    ///
415    /// ```rust
416    /// use ratatui_core::layout::{Constraint, Layout, Rect};
417    ///
418    /// let layout = Layout::default()
419    ///     .constraints([Constraint::Fill(1)])
420    ///     .margin(2)
421    ///     .split(Rect::new(0, 0, 10, 10));
422    /// assert_eq!(layout[..], [Rect::new(2, 2, 6, 6)]);
423    /// ```
424    #[must_use = "method moves the value of self and returns the modified value"]
425    pub const fn margin(mut self, margin: u16) -> Self {
426        self.margin = Margin {
427            horizontal: margin,
428            vertical: margin,
429        };
430        self
431    }
432
433    /// Set the horizontal margin of the layout.
434    ///
435    /// # Examples
436    ///
437    /// ```rust
438    /// use ratatui_core::layout::{Constraint, Layout, Rect};
439    ///
440    /// let layout = Layout::default()
441    ///     .constraints([Constraint::Fill(1)])
442    ///     .horizontal_margin(2)
443    ///     .split(Rect::new(0, 0, 10, 10));
444    /// assert_eq!(layout[..], [Rect::new(2, 0, 6, 10)]);
445    /// ```
446    #[must_use = "method moves the value of self and returns the modified value"]
447    pub const fn horizontal_margin(mut self, horizontal: u16) -> Self {
448        self.margin.horizontal = horizontal;
449        self
450    }
451
452    /// Set the vertical margin of the layout.
453    ///
454    /// # Examples
455    ///
456    /// ```rust
457    /// use ratatui_core::layout::{Constraint, Layout, Rect};
458    ///
459    /// let layout = Layout::default()
460    ///     .constraints([Constraint::Fill(1)])
461    ///     .vertical_margin(2)
462    ///     .split(Rect::new(0, 0, 10, 10));
463    /// assert_eq!(layout[..], [Rect::new(0, 2, 10, 6)]);
464    /// ```
465    #[must_use = "method moves the value of self and returns the modified value"]
466    pub const fn vertical_margin(mut self, vertical: u16) -> Self {
467        self.margin.vertical = vertical;
468        self
469    }
470
471    /// The `flex` method  allows you to specify the flex behavior of the layout.
472    ///
473    /// # Arguments
474    ///
475    /// * `flex`: A [`Flex`] enum value that represents the flex behavior of the layout. It can be
476    ///   one of the following:
477    ///   - [`Flex::Legacy`]: The last item is stretched to fill the excess space.
478    ///   - [`Flex::Start`]: The items are aligned to the start of the layout.
479    ///   - [`Flex::Center`]: The items are aligned to the center of the layout.
480    ///   - [`Flex::End`]: The items are aligned to the end of the layout.
481    ///   - [`Flex::SpaceBetween`]: The items are evenly distributed with equal space between them.
482    ///   - [`Flex::SpaceAround`]: The items are evenly distributed with equal space around them,
483    ///     except the first and last items, which have half the space on their sides.
484    ///   - [`Flex::SpaceEvenly`]: The items are evenly distributed with equal space around them.
485    ///
486    /// # Examples
487    ///
488    /// In this example, the items in the layout will be aligned to the start.
489    ///
490    /// ```rust
491    /// use ratatui_core::layout::Constraint::*;
492    /// use ratatui_core::layout::{Flex, Layout};
493    ///
494    /// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).flex(Flex::Start);
495    /// ```
496    ///
497    /// In this example, the items in the layout will be stretched equally to fill the available
498    /// space.
499    ///
500    /// ```rust
501    /// use ratatui_core::layout::Constraint::*;
502    /// use ratatui_core::layout::{Flex, Layout};
503    ///
504    /// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).flex(Flex::Legacy);
505    /// ```
506    #[must_use = "method moves the value of self and returns the modified value"]
507    pub const fn flex(mut self, flex: Flex) -> Self {
508        self.flex = flex;
509        self
510    }
511
512    /// Sets the spacing between items in the layout.
513    ///
514    /// The `spacing` method sets the spacing between items in the layout. The spacing is applied
515    /// evenly between all segments. The spacing value represents the number of cells between each
516    /// item.
517    ///
518    /// Spacing can be positive integers, representing gaps between segments; or negative integers
519    /// representing overlaps. Additionally, one of the variants of the [`Spacing`] enum can be
520    /// passed to this function. See the documentation of the [`Spacing`] enum for more information.
521    ///
522    /// Note that if the layout has only one segment, the spacing will not be applied.
523    /// Also, spacing will not be applied for [`Flex::SpaceAround`], [`Flex::SpaceEvenly`] and
524    /// [`Flex::SpaceBetween`]
525    ///
526    /// # Examples
527    ///
528    /// In this example, the spacing between each item in the layout is set to 2 cells.
529    ///
530    /// ```rust
531    /// use ratatui_core::layout::Constraint::*;
532    /// use ratatui_core::layout::Layout;
533    ///
534    /// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).spacing(2);
535    /// ```
536    ///
537    /// In this example, the spacing between each item in the layout is set to -1 cells, i.e. the
538    /// three segments will have an overlapping border.
539    ///
540    /// ```rust
541    /// use ratatui_core::layout::Constraint::*;
542    /// use ratatui_core::layout::Layout;
543    /// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).spacing(-1);
544    /// ```
545    #[must_use = "method moves the value of self and returns the modified value"]
546    pub fn spacing<T>(mut self, spacing: T) -> Self
547    where
548        T: Into<Spacing>,
549    {
550        self.spacing = spacing.into();
551        self
552    }
553
554    /// Split the rect into a number of sub-rects according to the given [`Layout`].
555    ///
556    /// An ergonomic wrapper around [`Layout::split`] that returns an array of `Rect`s instead of
557    /// `Rc<[Rect]>`.
558    ///
559    /// This method requires the number of constraints to be known at compile time. If you don't
560    /// know the number of constraints at compile time, use [`Layout::split`] instead.
561    ///
562    /// # Panics
563    ///
564    /// Panics if the number of constraints is not equal to the length of the returned array.
565    ///
566    /// # Examples
567    ///
568    /// ```rust
569    /// use ratatui_core::layout::{Constraint, Layout, Rect};
570    ///
571    /// let area = Rect::new(0, 0, 10, 10);
572    /// let layout = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]);
573    /// let [top, main] = layout.areas(area);
574    ///
575    /// // or explicitly specify the number of constraints:
576    /// let areas = layout.areas::<2>(area);
577    /// ```
578    pub fn areas<const N: usize>(&self, area: Rect) -> [Rect; N] {
579        let areas = self.split(area);
580        areas.as_ref().try_into().unwrap_or_else(|_| {
581            panic!(
582                "invalid number of rects: expected {N}, found {}",
583                areas.len()
584            )
585        })
586    }
587
588    /// Split the rect into a number of sub-rects according to the given [`Layout`].
589    ///
590    /// An ergonomic wrapper around [`Layout::split`] that returns an array of `Rect`s instead of
591    /// `Rc<[Rect]>`.
592    ///
593    /// This method requires the number of constraints to be known at compile time. If you don't
594    /// know the number of constraints at compile time, use [`Layout::split`] instead.
595    ///
596    /// # Errors
597    ///
598    /// Returns an error if the number of constraints is not equal to the length of the returned
599    /// array.
600    ///
601    /// # Examples
602    ///
603    /// ```rust
604    /// use ratatui_core::layout::{Constraint, Layout, Rect};
605    ///
606    /// let area = Rect::new(0, 0, 10, 10);
607    /// let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
608    /// let [top, main] = layout.try_areas(area)?;
609    ///
610    /// // or explicitly specify the number of constraints:
611    /// let areas = layout.try_areas::<2>(area)?;
612    /// # Ok::<(), core::array::TryFromSliceError>(())
613    /// ```
614    pub fn try_areas<const N: usize>(&self, area: Rect) -> Result<[Rect; N], TryFromSliceError> {
615        self.split(area).as_ref().try_into()
616    }
617
618    /// Split the rect into a number of sub-rects according to the given [`Layout`] and return just
619    /// the spacers between the areas.
620    ///
621    /// This method requires the number of constraints to be known at compile time. If you don't
622    /// know the number of constraints at compile time, use [`Layout::split_with_spacers`] instead.
623    ///
624    /// This method is similar to [`Layout::areas`], and can be called with the same parameters, but
625    /// it returns just the spacers between the areas. The result of calling the `areas` method is
626    /// cached, so this will generally not re-run the solver, but will just return the cached
627    /// result.
628    ///
629    /// # Panics
630    ///
631    /// Panics if the number of constraints + 1 is not equal to the length of the returned array.
632    ///
633    /// # Examples
634    ///
635    /// ```rust
636    /// use ratatui_core::layout::{Constraint, Layout, Rect};
637    ///
638    /// let area = Rect::new(0, 0, 10, 10);
639    /// let layout = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]);
640    /// let [top, main] = layout.areas(area);
641    /// let [before, inbetween, after] = layout.spacers(area);
642    ///
643    /// // or explicitly specify the number of constraints:
644    /// let spacers = layout.spacers::<3>(area);
645    /// ```
646    pub fn spacers<const N: usize>(&self, area: Rect) -> [Rect; N] {
647        let (_, spacers) = self.split_with_spacers(area);
648        spacers
649            .as_ref()
650            .try_into()
651            .expect("invalid number of rects")
652    }
653
654    /// Wrapper function around the [`kasuari`] solver to be able to split a given area into
655    /// smaller ones based on the preferred widths or heights and the direction.
656    ///
657    /// Note that the constraints are applied to the whole area that is to be split, so using
658    /// percentages and ratios with the other constraints may not have the desired effect of
659    /// splitting the area up. (e.g. splitting 100 into [min 20, 50%, 50%], may not result in [20,
660    /// 40, 40] but rather an indeterminate result between [20, 50, 30] and [20, 30, 50]).
661    ///
662    /// This method stores the result of the computation in a thread-local cache keyed on the layout
663    /// and area, so that subsequent calls with the same parameters are faster. The cache is a
664    /// `LruCache`, and grows until [`Self::DEFAULT_CACHE_SIZE`] is reached by default. If the cache
665    /// is initialized with [`Layout::init_cache()`], it grows until the initialized cache size.
666    ///
667    /// There is a helper method that can be used to split the whole area into smaller ones based on
668    /// the layout: [`Layout::areas()`]. That method is a shortcut for calling this method. It
669    /// allows you to destructure the result directly into variables, which is useful when you know
670    /// at compile time the number of areas that will be created.
671    ///
672    /// # Examples
673    ///
674    /// ```
675    /// use ratatui_core::layout::{Constraint, Direction, Layout, Rect};
676    /// let layout = Layout::default()
677    ///     .direction(Direction::Vertical)
678    ///     .constraints([Constraint::Length(5), Constraint::Fill(1)])
679    ///     .split(Rect::new(2, 2, 10, 10));
680    /// assert_eq!(layout[..], [Rect::new(2, 2, 10, 5), Rect::new(2, 7, 10, 5)]);
681    ///
682    /// let layout = Layout::default()
683    ///     .direction(Direction::Horizontal)
684    ///     .constraints([Constraint::Ratio(1, 3), Constraint::Ratio(2, 3)])
685    ///     .split(Rect::new(0, 0, 9, 2));
686    /// assert_eq!(layout[..], [Rect::new(0, 0, 3, 2), Rect::new(3, 0, 6, 2)]);
687    /// ```
688    pub fn split(&self, area: Rect) -> Rects {
689        self.split_with_spacers(area).0
690    }
691
692    /// Wrapper function around the [`kasuari`] solver that splits the given area into smaller ones
693    /// based on the preferred widths or heights and the direction, with the ability to include
694    /// spacers between the areas.
695    ///
696    /// This method is similar to `split`, but it returns two sets of rectangles: one for the areas
697    /// and one for the spacers.
698    ///
699    /// This method stores the result of the computation in a thread-local cache keyed on the layout
700    /// and area, so that subsequent calls with the same parameters are faster. The cache is a
701    /// `LruCache`, and grows until [`Self::DEFAULT_CACHE_SIZE`] is reached by default. If the cache
702    /// is initialized with [`Layout::init_cache()`], it grows until the initialized cache size.
703    ///
704    /// # Examples
705    ///
706    /// ```
707    /// use ratatui_core::layout::{Constraint, Direction, Layout, Rect};
708    ///
709    /// let (areas, spacers) = Layout::default()
710    ///     .direction(Direction::Vertical)
711    ///     .constraints([Constraint::Length(5), Constraint::Fill(1)])
712    ///     .split_with_spacers(Rect::new(2, 2, 10, 10));
713    /// assert_eq!(areas[..], [Rect::new(2, 2, 10, 5), Rect::new(2, 7, 10, 5)]);
714    /// assert_eq!(
715    ///     spacers[..],
716    ///     [
717    ///         Rect::new(2, 2, 10, 0),
718    ///         Rect::new(2, 7, 10, 0),
719    ///         Rect::new(2, 12, 10, 0)
720    ///     ]
721    /// );
722    ///
723    /// let (areas, spacers) = Layout::default()
724    ///     .direction(Direction::Horizontal)
725    ///     .spacing(1)
726    ///     .constraints([Constraint::Ratio(1, 3), Constraint::Ratio(2, 3)])
727    ///     .split_with_spacers(Rect::new(0, 0, 10, 2));
728    /// assert_eq!(areas[..], [Rect::new(0, 0, 3, 2), Rect::new(4, 0, 6, 2)]);
729    /// assert_eq!(
730    ///     spacers[..],
731    ///     [
732    ///         Rect::new(0, 0, 0, 2),
733    ///         Rect::new(3, 0, 1, 2),
734    ///         Rect::new(10, 0, 0, 2)
735    ///     ]
736    /// );
737    /// ```
738    pub fn split_with_spacers(&self, area: Rect) -> (Segments, Spacers) {
739        #[cfg(feature = "layout-cache")]
740        {
741            self.cached_split(area)
742        }
743
744        #[cfg(not(feature = "layout-cache"))]
745        {
746            self.split_layout(area)
747        }
748    }
749
750    // std builds: use a thread-local cache with cheap Rc cloning and no locking.
751    #[cfg(all(feature = "layout-cache", feature = "std"))]
752    fn cached_split(&self, area: Rect) -> (Segments, Spacers) {
753        LAYOUT_CACHE.with_borrow_mut(|cache| {
754            let key = (area, self.clone());
755            cache.get_or_insert(key, || self.split_layout(area)).clone()
756        })
757    }
758
759    // no_std builds: use a critical-section cache and compute layouts outside locks.
760    #[cfg(all(feature = "layout-cache", not(feature = "std")))]
761    fn cached_split(&self, area: Rect) -> (Segments, Spacers) {
762        // Check cache inside critical section, but compute outside to avoid
763        // blocking interrupts during the (expensive) constraint solver.
764        let cached = critical_section::with(|cs| {
765            let mut cache = LAYOUT_CACHE.borrow(cs).borrow_mut();
766            let cache = cache.get_or_insert_with(|| {
767                Cache::new(NonZeroUsize::new(Self::DEFAULT_CACHE_SIZE).unwrap())
768            });
769            let key = (area, self.clone());
770            cache
771                .get(&key)
772                .map(|(s, sp)| (Rc::from(s.as_slice()), Rc::from(sp.as_slice())))
773        });
774
775        match cached {
776            Some(result) => result,
777            None => {
778                let result = self.split_layout(area);
779
780                critical_section::with(|cs| {
781                    let mut cache = LAYOUT_CACHE.borrow(cs).borrow_mut();
782                    if let Some(cache) = cache.as_mut() {
783                        let key = (area, self.clone());
784                        cache.put(key, (result.0.to_vec(), result.1.to_vec()));
785                    }
786                });
787
788                result
789            }
790        }
791    }
792
793    fn split_layout(&self, area: Rect) -> (Segments, Spacers) {
794        self.try_split(area).expect("failed to split")
795    }
796
797    fn try_split(&self, area: Rect) -> Result<(Segments, Spacers), AddConstraintError> {
798        // To take advantage of all of [`kasuari`] features, we would want to store the `Solver` in
799        // one of the fields of the Layout struct. And we would want to set it up such that we could
800        // add or remove constraints as and when needed.
801        // The advantage of doing it as described above is that it would allow users to
802        // incrementally add and remove constraints efficiently.
803        // Solves will just one constraint different would not need to resolve the entire layout.
804        //
805        // The disadvantage of this approach is that it requires tracking which constraints were
806        // added, and which variables they correspond to.
807        // This will also require introducing and maintaining the API for users to do so.
808        //
809        // Currently we don't support that use case and do not intend to support it in the future,
810        // and instead we require that the user re-solve the layout every time they call `split`.
811        // To minimize the time it takes to solve the same problem over and over again, we
812        // cache the `Layout` struct along with the results.
813        //
814        // `try_split` is the inner method in `split` that is called only when the LRU cache doesn't
815        // match the key. So inside `try_split`, we create a new instance of the solver.
816        //
817        // This is equivalent to storing the solver in `Layout` and calling `solver.reset()` here.
818        let mut solver = Solver::new();
819
820        let inner_area = area.inner(self.margin);
821        let (area_start, area_end) = match self.direction {
822            Direction::Horizontal => (
823                f64::from(inner_area.x) * FLOAT_PRECISION_MULTIPLIER,
824                f64::from(inner_area.right()) * FLOAT_PRECISION_MULTIPLIER,
825            ),
826            Direction::Vertical => (
827                f64::from(inner_area.y) * FLOAT_PRECISION_MULTIPLIER,
828                f64::from(inner_area.bottom()) * FLOAT_PRECISION_MULTIPLIER,
829            ),
830        };
831
832        // ```plain
833        // <───────────────────────────────────area_size──────────────────────────────────>
834        // ┌─area_start                                                          area_end─┐
835        // V                                                                              V
836        // ┌────┬───────────────────┬────┬─────variables─────┬────┬───────────────────┬────┐
837        // │    │                   │    │                   │    │                   │    │
838        // V    V                   V    V                   V    V                   V    V
839        // ┌   ┐┌──────────────────┐┌   ┐┌──────────────────┐┌   ┐┌──────────────────┐┌   ┐
840        //      │     Max(20)      │     │      Max(20)     │     │      Max(20)     │
841        // └   ┘└──────────────────┘└   ┘└──────────────────┘└   ┘└──────────────────┘└   ┘
842        // ^    ^                   ^    ^                   ^    ^                   ^    ^
843        // │    │                   │    │                   │    │                   │    │
844        // └─┬──┶━━━━━━━━━┳━━━━━━━━━┵─┬──┶━━━━━━━━━┳━━━━━━━━━┵─┬──┶━━━━━━━━━┳━━━━━━━━━┵─┬──┘
845        //   │            ┃           │            ┃           │            ┃           │
846        //   └────────────╂───────────┴────────────╂───────────┴────────────╂──Spacers──┘
847        //                ┃                        ┃                        ┃
848        //                ┗━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━Segments━━━━━━━━┛
849        // ```
850
851        let variable_count = self.constraints.len() * 2 + 2;
852        let variables = iter::repeat_with(Variable::new)
853            .take(variable_count)
854            .collect_vec();
855        let spacers = variables
856            .iter()
857            .tuples()
858            .map(|(a, b)| Element::from((*a, *b)))
859            .collect_vec();
860        let segments = variables
861            .iter()
862            .skip(1)
863            .tuples()
864            .map(|(a, b)| Element::from((*a, *b)))
865            .collect_vec();
866
867        let flex = self.flex;
868
869        let spacing = match self.spacing {
870            Spacing::Space(x) => x as i16,
871            Spacing::Overlap(x) => -(x as i16),
872        };
873
874        let constraints = &self.constraints;
875
876        let area_size = Element::from((*variables.first().unwrap(), *variables.last().unwrap()));
877        configure_area(&mut solver, area_size, area_start, area_end)?;
878        configure_variable_in_area_constraints(&mut solver, &variables, area_size)?;
879        configure_variable_constraints(&mut solver, &variables)?;
880        configure_flex_constraints(&mut solver, area_size, &spacers, flex, spacing)?;
881        configure_constraints(&mut solver, area_size, &segments, constraints, flex)?;
882        configure_fill_constraints(&mut solver, &segments, constraints, flex)?;
883
884        if !flex.is_legacy() {
885            for (left, right) in segments.iter().tuple_windows() {
886                solver.add_constraint(left.has_size(right, ALL_SEGMENT_GROW))?;
887            }
888        }
889
890        // `solver.fetch_changes()` can only be called once per solve
891        let changes: HashMap<Variable, f64> = solver.fetch_changes().iter().copied().collect();
892        // debug_elements(&segments, &changes);
893        // debug_elements(&spacers, &changes);
894
895        let segment_rects = changes_to_rects(&changes, &segments, inner_area, self.direction);
896        let spacer_rects = changes_to_rects(&changes, &spacers, inner_area, self.direction);
897
898        Ok((segment_rects, spacer_rects))
899    }
900}
901
902fn configure_area(
903    solver: &mut Solver,
904    area: Element,
905    area_start: f64,
906    area_end: f64,
907) -> Result<(), AddConstraintError> {
908    solver.add_constraint(area.start | EQ(Strength::REQUIRED) | area_start)?;
909    solver.add_constraint(area.end | EQ(Strength::REQUIRED) | area_end)?;
910    Ok(())
911}
912
913fn configure_variable_in_area_constraints(
914    solver: &mut Solver,
915    variables: &[Variable],
916    area: Element,
917) -> Result<(), AddConstraintError> {
918    // all variables are in the range [area.start, area.end]
919    for &variable in variables {
920        solver.add_constraint(variable | GE(Strength::REQUIRED) | area.start)?;
921        solver.add_constraint(variable | LE(Strength::REQUIRED) | area.end)?;
922    }
923
924    Ok(())
925}
926
927fn configure_variable_constraints(
928    solver: &mut Solver,
929    variables: &[Variable],
930) -> Result<(), AddConstraintError> {
931    // ┌────┬───────────────────┬────┬─────variables─────┬────┬───────────────────┬────┐
932    // │    │                   │    │                   │    │                   │    │
933    // v    v                   v    v                   v    v                   v    v
934    // ┌   ┐┌──────────────────┐┌   ┐┌──────────────────┐┌   ┐┌──────────────────┐┌   ┐
935    //      │     Max(20)      │     │      Max(20)     │     │      Max(20)     │
936    // └   ┘└──────────────────┘└   ┘└──────────────────┘└   ┘└──────────────────┘└   ┘
937    // ^    ^                   ^    ^                   ^    ^                   ^    ^
938    // └v0  └v1                 └v2  └v3                 └v4  └v5                 └v6  └v7
939
940    for (&left, &right) in variables.iter().skip(1).tuples() {
941        solver.add_constraint(left | LE(Strength::REQUIRED) | right)?;
942    }
943    Ok(())
944}
945
946fn configure_constraints(
947    solver: &mut Solver,
948    area: Element,
949    segments: &[Element],
950    constraints: &[Constraint],
951    flex: Flex,
952) -> Result<(), AddConstraintError> {
953    for (&constraint, &segment) in constraints.iter().zip(segments.iter()) {
954        match constraint {
955            Constraint::Max(max) => {
956                solver.add_constraint(segment.has_max_size(max, MAX_SIZE_LE))?;
957                solver.add_constraint(segment.has_int_size(max, MAX_SIZE_EQ))?;
958            }
959            Constraint::Min(min) => {
960                solver.add_constraint(segment.has_min_size(min as i16, MIN_SIZE_GE))?;
961                if flex.is_legacy() {
962                    solver.add_constraint(segment.has_int_size(min, MIN_SIZE_EQ))?;
963                } else {
964                    solver.add_constraint(segment.has_size(area, FILL_GROW))?;
965                }
966            }
967            Constraint::Length(length) => {
968                solver.add_constraint(segment.has_int_size(length, LENGTH_SIZE_EQ))?;
969            }
970            Constraint::Percentage(p) => {
971                let size = area.size() * f64::from(p) / 100.00;
972                solver.add_constraint(segment.has_size(size, PERCENTAGE_SIZE_EQ))?;
973            }
974            Constraint::Ratio(num, den) => {
975                // avoid division by zero by using 1 when denominator is 0
976                let size = area.size() * f64::from(num) / f64::from(den.max(1));
977                solver.add_constraint(segment.has_size(size, RATIO_SIZE_EQ))?;
978            }
979            Constraint::Fill(_) => {
980                // given no other constraints, this segment will grow as much as possible.
981                solver.add_constraint(segment.has_size(area, FILL_GROW))?;
982            }
983        }
984    }
985    Ok(())
986}
987
988fn configure_flex_constraints(
989    solver: &mut Solver,
990    area: Element,
991    spacers: &[Element],
992    flex: Flex,
993    spacing: i16,
994) -> Result<(), AddConstraintError> {
995    let spacers_except_first_and_last = spacers.get(1..spacers.len() - 1).unwrap_or(&[]);
996    let spacing_f64 = f64::from(spacing) * FLOAT_PRECISION_MULTIPLIER;
997    match flex {
998        Flex::Legacy => {
999            for spacer in spacers_except_first_and_last {
1000                solver.add_constraint(spacer.has_size(spacing_f64, SPACER_SIZE_EQ))?;
1001            }
1002            if let (Some(first), Some(last)) = (spacers.first(), spacers.last()) {
1003                solver.add_constraint(first.is_empty())?;
1004                solver.add_constraint(last.is_empty())?;
1005            }
1006        }
1007
1008        // All spacers excluding first and last are the same size and will grow to fill
1009        // any remaining space after the constraints are satisfied.
1010        // All spacers excluding first and last are also twice the size of the first and last
1011        // spacers
1012        Flex::SpaceAround => {
1013            if spacers.len() <= 2 {
1014                // If there are two or less spacers, fallback to Flex::SpaceEvenly
1015                for (left, right) in spacers.iter().tuple_combinations() {
1016                    solver.add_constraint(left.has_size(right, SPACER_SIZE_EQ))?;
1017                }
1018                for spacer in spacers {
1019                    solver.add_constraint(spacer.has_min_size(spacing, SPACER_SIZE_EQ))?;
1020                    solver.add_constraint(spacer.has_size(area, SPACE_GROW))?;
1021                }
1022            } else {
1023                // Separate the first and last spacer from the middle ones
1024                let (first, rest) = spacers.split_first().unwrap();
1025                let (last, middle) = rest.split_last().unwrap();
1026
1027                // All middle spacers should be equal in size
1028                for (left, right) in middle.iter().tuple_combinations() {
1029                    solver.add_constraint(left.has_size(right, SPACER_SIZE_EQ))?;
1030                }
1031
1032                // First and last spacers should be half the size of any middle spacer
1033                if let Some(first_middle) = middle.first() {
1034                    solver.add_constraint(first_middle.has_double_size(first, SPACER_SIZE_EQ))?;
1035                    solver.add_constraint(first_middle.has_double_size(last, SPACER_SIZE_EQ))?;
1036                }
1037
1038                // Apply minimum size and growth constraints
1039                for spacer in spacers {
1040                    solver.add_constraint(spacer.has_min_size(spacing, SPACER_SIZE_EQ))?;
1041                    solver.add_constraint(spacer.has_size(area, SPACE_GROW))?;
1042                }
1043            }
1044        }
1045
1046        // All spacers are the same size and will grow to fill any remaining space after the
1047        // constraints are satisfied
1048        Flex::SpaceEvenly => {
1049            for (left, right) in spacers.iter().tuple_combinations() {
1050                solver.add_constraint(left.has_size(right, SPACER_SIZE_EQ))?;
1051            }
1052            for spacer in spacers {
1053                solver.add_constraint(spacer.has_min_size(spacing, SPACER_SIZE_EQ))?;
1054                solver.add_constraint(spacer.has_size(area, SPACE_GROW))?;
1055            }
1056        }
1057
1058        // All spacers excluding first and last are the same size and will grow to fill
1059        // any remaining space after the constraints are satisfied.
1060        // The first and last spacers are zero size.
1061        Flex::SpaceBetween => {
1062            for (left, right) in spacers_except_first_and_last.iter().tuple_combinations() {
1063                solver.add_constraint(left.has_size(right.size(), SPACER_SIZE_EQ))?;
1064            }
1065            for spacer in spacers_except_first_and_last {
1066                solver.add_constraint(spacer.has_min_size(spacing, SPACER_SIZE_EQ))?;
1067                solver.add_constraint(spacer.has_size(area, SPACE_GROW))?;
1068            }
1069            if let (Some(first), Some(last)) = (spacers.first(), spacers.last()) {
1070                solver.add_constraint(first.is_empty())?;
1071                solver.add_constraint(last.is_empty())?;
1072            }
1073        }
1074
1075        Flex::Start => {
1076            for spacer in spacers_except_first_and_last {
1077                solver.add_constraint(spacer.has_size(spacing_f64, SPACER_SIZE_EQ))?;
1078            }
1079            if let (Some(first), Some(last)) = (spacers.first(), spacers.last()) {
1080                solver.add_constraint(first.is_empty())?;
1081                solver.add_constraint(last.has_size(area, GROW))?;
1082            }
1083        }
1084        Flex::Center => {
1085            for spacer in spacers_except_first_and_last {
1086                solver.add_constraint(spacer.has_size(spacing_f64, SPACER_SIZE_EQ))?;
1087            }
1088            if let (Some(first), Some(last)) = (spacers.first(), spacers.last()) {
1089                solver.add_constraint(first.has_size(area, GROW))?;
1090                solver.add_constraint(last.has_size(area, GROW))?;
1091                solver.add_constraint(first.has_size(last, SPACER_SIZE_EQ))?;
1092            }
1093        }
1094        Flex::End => {
1095            for spacer in spacers_except_first_and_last {
1096                solver.add_constraint(spacer.has_size(spacing_f64, SPACER_SIZE_EQ))?;
1097            }
1098            if let (Some(first), Some(last)) = (spacers.first(), spacers.last()) {
1099                solver.add_constraint(last.is_empty())?;
1100                solver.add_constraint(first.has_size(area, GROW))?;
1101            }
1102        }
1103    }
1104    Ok(())
1105}
1106
1107/// Make every `Fill` constraint proportionally equal to each other
1108/// This will make it fill up empty spaces equally
1109///
1110/// [Fill(1), Fill(1)]
1111/// ┌──────┐┌──────┐
1112/// │abcdef││abcdef│
1113/// └──────┘└──────┘
1114///
1115/// [Fill(1), Fill(2)]
1116/// ┌──────┐┌────────────┐
1117/// │abcdef││abcdefabcdef│
1118/// └──────┘└────────────┘
1119///
1120/// `size == base_element * scaling_factor`
1121fn configure_fill_constraints(
1122    solver: &mut Solver,
1123    segments: &[Element],
1124    constraints: &[Constraint],
1125    flex: Flex,
1126) -> Result<(), AddConstraintError> {
1127    for ((&left_constraint, &left_segment), (&right_constraint, &right_segment)) in constraints
1128        .iter()
1129        .zip(segments.iter())
1130        .filter(|(c, _)| c.is_fill() || (!flex.is_legacy() && c.is_min()))
1131        .tuple_combinations()
1132    {
1133        let left_scaling_factor = match left_constraint {
1134            Constraint::Fill(scale) => f64::from(scale).max(1e-6),
1135            Constraint::Min(_) => 1.0,
1136            _ => unreachable!(),
1137        };
1138        let right_scaling_factor = match right_constraint {
1139            Constraint::Fill(scale) => f64::from(scale).max(1e-6),
1140            Constraint::Min(_) => 1.0,
1141            _ => unreachable!(),
1142        };
1143        solver.add_constraint(
1144            (right_scaling_factor * left_segment.size())
1145                | EQ(GROW)
1146                | (left_scaling_factor * right_segment.size()),
1147        )?;
1148    }
1149    Ok(())
1150}
1151
1152// Used instead of `f64::round` directly, to provide fallback for `no_std`.
1153#[cfg(feature = "std")]
1154#[inline]
1155fn round(value: f64) -> f64 {
1156    value.round()
1157}
1158
1159// A rounding fallback for `no_std` in pure rust.
1160#[cfg(not(feature = "std"))]
1161#[inline]
1162fn round(value: f64) -> f64 {
1163    (value + 0.5f64.copysign(value)) as i64 as f64
1164}
1165
1166fn changes_to_rects(
1167    changes: &HashMap<Variable, f64>,
1168    elements: &[Element],
1169    area: Rect,
1170    direction: Direction,
1171) -> Rects {
1172    // convert to Rects
1173    elements
1174        .iter()
1175        .map(|element| {
1176            let start = changes.get(&element.start).unwrap_or(&0.0);
1177            let end = changes.get(&element.end).unwrap_or(&0.0);
1178            let start = round(round(*start) / FLOAT_PRECISION_MULTIPLIER) as u16;
1179            let end = round(round(*end) / FLOAT_PRECISION_MULTIPLIER) as u16;
1180            let size = end.saturating_sub(start);
1181            match direction {
1182                Direction::Horizontal => Rect {
1183                    x: start,
1184                    y: area.y,
1185                    width: size,
1186                    height: area.height,
1187                },
1188                Direction::Vertical => Rect {
1189                    x: area.x,
1190                    y: start,
1191                    width: area.width,
1192                    height: size,
1193                },
1194            }
1195        })
1196        .collect::<Rects>()
1197}
1198
1199/// please leave this here as it's useful for debugging unit tests when we make any changes to
1200/// layout code - we should replace this with tracing in the future.
1201#[expect(dead_code)]
1202#[cfg(feature = "std")]
1203fn debug_elements(elements: &[Element], changes: &HashMap<Variable, f64>) {
1204    let variables = alloc::format!(
1205        "{:?}",
1206        elements
1207            .iter()
1208            .map(|e| (
1209                changes.get(&e.start).unwrap_or(&0.0) / FLOAT_PRECISION_MULTIPLIER,
1210                changes.get(&e.end).unwrap_or(&0.0) / FLOAT_PRECISION_MULTIPLIER,
1211            ))
1212            .collect::<Vec<(f64, f64)>>()
1213    );
1214    std::dbg!(variables);
1215}
1216
1217/// A container used by the solver inside split
1218#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
1219struct Element {
1220    start: Variable,
1221    end: Variable,
1222}
1223
1224impl From<(Variable, Variable)> for Element {
1225    fn from((start, end): (Variable, Variable)) -> Self {
1226        Self { start, end }
1227    }
1228}
1229
1230impl Element {
1231    #[expect(dead_code)]
1232    fn new() -> Self {
1233        Self {
1234            start: Variable::new(),
1235            end: Variable::new(),
1236        }
1237    }
1238
1239    fn size(&self) -> Expression {
1240        self.end - self.start
1241    }
1242
1243    fn has_max_size(&self, size: u16, strength: Strength) -> kasuari::Constraint {
1244        self.size() | LE(strength) | (f64::from(size) * FLOAT_PRECISION_MULTIPLIER)
1245    }
1246
1247    fn has_min_size(&self, size: i16, strength: Strength) -> kasuari::Constraint {
1248        self.size() | GE(strength) | (f64::from(size) * FLOAT_PRECISION_MULTIPLIER)
1249    }
1250
1251    fn has_int_size(&self, size: u16, strength: Strength) -> kasuari::Constraint {
1252        self.size() | EQ(strength) | (f64::from(size) * FLOAT_PRECISION_MULTIPLIER)
1253    }
1254
1255    fn has_size<E: Into<Expression>>(&self, size: E, strength: Strength) -> kasuari::Constraint {
1256        self.size() | EQ(strength) | size.into()
1257    }
1258
1259    fn has_double_size<E: Into<Expression>>(
1260        &self,
1261        size: E,
1262        strength: Strength,
1263    ) -> kasuari::Constraint {
1264        self.size() | EQ(strength) | (size.into() * 2.0)
1265    }
1266
1267    fn is_empty(&self) -> kasuari::Constraint {
1268        self.size() | EQ(Strength::REQUIRED - Strength::WEAK) | 0.0
1269    }
1270}
1271
1272/// allow the element to represent its own size in expressions
1273impl From<Element> for Expression {
1274    fn from(element: Element) -> Self {
1275        element.size()
1276    }
1277}
1278
1279/// allow the element to represent its own size in expressions
1280impl From<&Element> for Expression {
1281    fn from(element: &Element) -> Self {
1282        element.size()
1283    }
1284}
1285
1286mod strengths {
1287    use kasuari::Strength;
1288
1289    /// The strength to apply to Spacers to ensure that their sizes are equal.
1290    ///
1291    /// ┌     ┐┌───┐┌     ┐┌───┐┌     ┐
1292    ///   ==x  │   │  ==x  │   │  ==x
1293    /// └     ┘└───┘└     ┘└───┘└     ┘
1294    pub const SPACER_SIZE_EQ: Strength = Strength::REQUIRED.div_f64(10.0);
1295
1296    /// The strength to apply to Min inequality constraints.
1297    ///
1298    /// ┌────────┐
1299    /// │Min(>=x)│
1300    /// └────────┘
1301    pub const MIN_SIZE_GE: Strength = Strength::STRONG.mul_f64(100.0);
1302
1303    /// The strength to apply to Max inequality constraints.
1304    ///
1305    /// ┌────────┐
1306    /// │Max(<=x)│
1307    /// └────────┘
1308    pub const MAX_SIZE_LE: Strength = Strength::STRONG.mul_f64(100.0);
1309
1310    /// The strength to apply to Length constraints.
1311    ///
1312    /// ┌───────────┐
1313    /// │Length(==x)│
1314    /// └───────────┘
1315    pub const LENGTH_SIZE_EQ: Strength = Strength::STRONG.mul_f64(10.0);
1316
1317    /// The strength to apply to Percentage constraints.
1318    ///
1319    /// ┌───────────────┐
1320    /// │Percentage(==x)│
1321    /// └───────────────┘
1322    pub const PERCENTAGE_SIZE_EQ: Strength = Strength::STRONG;
1323
1324    /// The strength to apply to Ratio constraints.
1325    ///
1326    /// ┌────────────┐
1327    /// │Ratio(==x,y)│
1328    /// └────────────┘
1329    pub const RATIO_SIZE_EQ: Strength = Strength::STRONG.div_f64(10.0);
1330
1331    /// The strength to apply to Min equality constraints.
1332    ///
1333    /// ┌────────┐
1334    /// │Min(==x)│
1335    /// └────────┘
1336    pub const MIN_SIZE_EQ: Strength = Strength::MEDIUM.mul_f64(10.0);
1337
1338    /// The strength to apply to Max equality constraints.
1339    ///
1340    /// ┌────────┐
1341    /// │Max(==x)│
1342    /// └────────┘
1343    pub const MAX_SIZE_EQ: Strength = Strength::MEDIUM.mul_f64(10.0);
1344
1345    /// The strength to apply to Fill growing constraints.
1346    ///
1347    /// ┌─────────────────────┐
1348    /// │<=     Fill(x)     =>│
1349    /// └─────────────────────┘
1350    pub const FILL_GROW: Strength = Strength::MEDIUM;
1351
1352    /// The strength to apply to growing constraints.
1353    ///
1354    /// ┌────────────┐
1355    /// │<= Min(x) =>│
1356    /// └────────────┘
1357    pub const GROW: Strength = Strength::MEDIUM.div_f64(10.0);
1358
1359    /// The strength to apply to Spacer growing constraints.
1360    ///
1361    /// ┌       ┐
1362    ///  <= x =>
1363    /// └       ┘
1364    pub const SPACE_GROW: Strength = Strength::WEAK.mul_f64(10.0);
1365
1366    /// The strength to apply to growing the size of all segments equally.
1367    ///
1368    /// ┌───────┐
1369    /// │<= x =>│
1370    /// └───────┘
1371    pub const ALL_SEGMENT_GROW: Strength = Strength::WEAK;
1372}
1373
1374#[cfg(test)]
1375mod tests {
1376    use alloc::borrow::ToOwned;
1377    use alloc::vec;
1378    use alloc::vec::Vec;
1379
1380    use super::*;
1381
1382    #[test]
1383    // The compiler will optimize out the comparisons, but this ensures that the constants are
1384    // defined in the correct order of priority.
1385    pub fn strength_is_valid() {
1386        use strengths::*;
1387        assert!(SPACER_SIZE_EQ > MAX_SIZE_LE);
1388        assert!(MAX_SIZE_LE > MAX_SIZE_EQ);
1389        assert_eq!(MIN_SIZE_GE, MAX_SIZE_LE);
1390        assert!(MAX_SIZE_LE > LENGTH_SIZE_EQ);
1391        assert!(LENGTH_SIZE_EQ > PERCENTAGE_SIZE_EQ);
1392        assert!(PERCENTAGE_SIZE_EQ > RATIO_SIZE_EQ);
1393        assert!(RATIO_SIZE_EQ > MAX_SIZE_EQ);
1394        assert!(MIN_SIZE_GE > FILL_GROW);
1395        assert!(FILL_GROW > GROW);
1396        assert!(GROW > SPACE_GROW);
1397        assert!(SPACE_GROW > ALL_SEGMENT_GROW);
1398    }
1399
1400    #[test]
1401    #[cfg(all(feature = "layout-cache", feature = "std"))]
1402    fn cache_size() {
1403        LAYOUT_CACHE.with_borrow(|cache| {
1404            assert_eq!(cache.cap().get(), Layout::DEFAULT_CACHE_SIZE);
1405        });
1406
1407        Layout::init_cache(NonZeroUsize::new(10).unwrap());
1408        LAYOUT_CACHE.with_borrow(|cache| {
1409            assert_eq!(cache.cap().get(), 10);
1410        });
1411    }
1412
1413    #[test]
1414    #[cfg(all(feature = "layout-cache", not(feature = "std")))]
1415    fn cache_size() {
1416        Layout::init_cache(NonZeroUsize::new(Layout::DEFAULT_CACHE_SIZE).unwrap());
1417        critical_section::with(|cs| {
1418            let cache = LAYOUT_CACHE.borrow(cs).borrow();
1419            assert_eq!(
1420                cache.as_ref().unwrap().cap().get(),
1421                Layout::DEFAULT_CACHE_SIZE
1422            );
1423        });
1424
1425        Layout::init_cache(NonZeroUsize::new(10).unwrap());
1426        critical_section::with(|cs| {
1427            let cache = LAYOUT_CACHE.borrow(cs).borrow();
1428            assert_eq!(cache.as_ref().unwrap().cap().get(), 10);
1429        });
1430    }
1431
1432    #[test]
1433    fn default() {
1434        assert_eq!(
1435            Layout::default(),
1436            Layout {
1437                direction: Direction::Vertical,
1438                margin: Margin::new(0, 0),
1439                constraints: vec![],
1440                flex: Flex::default(),
1441                spacing: Spacing::default(),
1442            }
1443        );
1444    }
1445
1446    #[test]
1447    fn new() {
1448        // array
1449        let fixed_size_array = [Constraint::Min(0)];
1450        let layout = Layout::new(Direction::Horizontal, fixed_size_array);
1451        assert_eq!(layout.direction, Direction::Horizontal);
1452        assert_eq!(layout.constraints, [Constraint::Min(0)]);
1453
1454        // array_ref
1455        #[expect(clippy::needless_borrows_for_generic_args)] // backwards compatibility test
1456        let layout = Layout::new(Direction::Horizontal, &[Constraint::Min(0)]);
1457        assert_eq!(layout.direction, Direction::Horizontal);
1458        assert_eq!(layout.constraints, [Constraint::Min(0)]);
1459
1460        // vec
1461        let layout = Layout::new(Direction::Horizontal, vec![Constraint::Min(0)]);
1462        assert_eq!(layout.direction, Direction::Horizontal);
1463        assert_eq!(layout.constraints, [Constraint::Min(0)]);
1464
1465        // vec_ref
1466        #[expect(clippy::needless_borrows_for_generic_args)] // backwards compatibility test
1467        let layout = Layout::new(Direction::Horizontal, &(vec![Constraint::Min(0)]));
1468        assert_eq!(layout.direction, Direction::Horizontal);
1469        assert_eq!(layout.constraints, [Constraint::Min(0)]);
1470
1471        // iterator
1472        let layout = Layout::new(Direction::Horizontal, iter::once(Constraint::Min(0)));
1473        assert_eq!(layout.direction, Direction::Horizontal);
1474        assert_eq!(layout.constraints, [Constraint::Min(0)]);
1475    }
1476
1477    #[test]
1478    fn vertical() {
1479        assert_eq!(
1480            Layout::vertical([Constraint::Min(0)]),
1481            Layout {
1482                direction: Direction::Vertical,
1483                margin: Margin::new(0, 0),
1484                constraints: vec![Constraint::Min(0)],
1485                flex: Flex::default(),
1486                spacing: Spacing::default(),
1487            }
1488        );
1489    }
1490
1491    #[test]
1492    fn horizontal() {
1493        assert_eq!(
1494            Layout::horizontal([Constraint::Min(0)]),
1495            Layout {
1496                direction: Direction::Horizontal,
1497                margin: Margin::new(0, 0),
1498                constraints: vec![Constraint::Min(0)],
1499                flex: Flex::default(),
1500                spacing: Spacing::default(),
1501            }
1502        );
1503    }
1504
1505    /// The purpose of this test is to ensure that layout can be constructed with any type that
1506    /// implements `IntoIterator<Item = AsRef<Constraint>>`.
1507    #[test]
1508    fn constraints() {
1509        const CONSTRAINTS: [Constraint; 2] = [Constraint::Min(0), Constraint::Max(10)];
1510        let fixed_size_array = CONSTRAINTS;
1511        assert_eq!(
1512            Layout::default().constraints(fixed_size_array).constraints,
1513            CONSTRAINTS,
1514            "constraints should be settable with an array"
1515        );
1516
1517        let slice_of_fixed_size_array = &CONSTRAINTS;
1518        assert_eq!(
1519            Layout::default()
1520                .constraints(slice_of_fixed_size_array)
1521                .constraints,
1522            CONSTRAINTS,
1523            "constraints should be settable with a slice"
1524        );
1525
1526        let vec = CONSTRAINTS.to_vec();
1527        let slice_of_vec = vec.as_slice();
1528        assert_eq!(
1529            Layout::default().constraints(slice_of_vec).constraints,
1530            CONSTRAINTS,
1531            "constraints should be settable with a slice"
1532        );
1533
1534        assert_eq!(
1535            Layout::default().constraints(vec).constraints,
1536            CONSTRAINTS,
1537            "constraints should be settable with a Vec"
1538        );
1539
1540        let iter = CONSTRAINTS.iter();
1541        assert_eq!(
1542            Layout::default().constraints(iter).constraints,
1543            CONSTRAINTS,
1544            "constraints should be settable with an iter"
1545        );
1546
1547        let iterator = CONSTRAINTS.iter().map(ToOwned::to_owned);
1548        assert_eq!(
1549            Layout::default().constraints(iterator).constraints,
1550            CONSTRAINTS,
1551            "constraints should be settable with an iterator"
1552        );
1553
1554        let iterator_ref = CONSTRAINTS.iter().map(AsRef::as_ref);
1555        assert_eq!(
1556            Layout::default().constraints(iterator_ref).constraints,
1557            CONSTRAINTS,
1558            "constraints should be settable with an iterator of refs"
1559        );
1560    }
1561
1562    #[test]
1563    fn direction() {
1564        assert_eq!(
1565            Layout::default().direction(Direction::Horizontal).direction,
1566            Direction::Horizontal
1567        );
1568        assert_eq!(
1569            Layout::default().direction(Direction::Vertical).direction,
1570            Direction::Vertical
1571        );
1572    }
1573
1574    #[test]
1575    fn margins() {
1576        assert_eq!(Layout::default().margin(10).margin, Margin::new(10, 10));
1577        assert_eq!(
1578            Layout::default().horizontal_margin(10).margin,
1579            Margin::new(10, 0)
1580        );
1581        assert_eq!(
1582            Layout::default().vertical_margin(10).margin,
1583            Margin::new(0, 10)
1584        );
1585        assert_eq!(
1586            Layout::default()
1587                .horizontal_margin(10)
1588                .vertical_margin(20)
1589                .margin,
1590            Margin::new(10, 20)
1591        );
1592    }
1593
1594    #[test]
1595    fn flex() {
1596        assert_eq!(Layout::default().flex, Flex::Start);
1597        assert_eq!(Layout::default().flex(Flex::Center).flex, Flex::Center);
1598    }
1599
1600    #[test]
1601    fn spacing() {
1602        assert_eq!(Layout::default().spacing(10).spacing, Spacing::Space(10));
1603        assert_eq!(Layout::default().spacing(0).spacing, Spacing::Space(0));
1604        assert_eq!(Layout::default().spacing(-10).spacing, Spacing::Overlap(10));
1605    }
1606
1607    /// Tests for the `Layout::split()` function.
1608    ///
1609    /// There are many tests in this as the number of edge cases that are caused by the interaction
1610    /// between the constraints is quite large. The tests are split into sections based on the type
1611    /// of constraints that are used.
1612    ///
1613    /// These tests are characterization tests. This means that they are testing the way the code
1614    /// currently works, and not the way it should work. This is because the current behavior is not
1615    /// well defined, and it is not clear what the correct behavior should be. This means that if
1616    /// the behavior changes, these tests should be updated to match the new behavior.
1617    ///
1618    ///  EOL comments in each test are intended to communicate the purpose of each test and to make
1619    ///  it easy to see that the tests are as exhaustive as feasible:
1620    /// - zero: constraint is zero
1621    /// - exact: constraint is equal to the space
1622    /// - underflow: constraint is for less than the full space
1623    /// - overflow: constraint is for more than the full space
1624    mod split {
1625        use alloc::string::ToString;
1626        use core::ops::Range;
1627
1628        use itertools::Itertools;
1629        use pretty_assertions::assert_eq;
1630        use rstest::rstest;
1631
1632        use super::*;
1633        use crate::buffer::Buffer;
1634        use crate::layout::Constraint::{self, *};
1635        use crate::layout::{Direction, Flex, Layout, Rect};
1636        use crate::text::Text;
1637        use crate::widgets::Widget;
1638
1639        /// Test that the given constraints applied to the given area result in the expected layout.
1640        /// Each chunk is filled with a letter repeated as many times as the width of the chunk. The
1641        /// resulting buffer is compared to the expected string.
1642        ///
1643        /// This approach is used rather than testing the resulting rects directly because it is
1644        /// easier to visualize the result, and it leads to more concise tests that are easier to
1645        /// compare against each other. E.g. `"abc"` is much more concise than `[Rect::new(0, 0, 1,
1646        /// 1), Rect::new(1, 0, 1, 1), Rect::new(2, 0, 1, 1)]`.
1647        #[track_caller]
1648        fn letters(flex: Flex, constraints: &[Constraint], width: u16, expected: &str) {
1649            let area = Rect::new(0, 0, width, 1);
1650            let layout = Layout::default()
1651                .direction(Direction::Horizontal)
1652                .constraints(constraints)
1653                .flex(flex)
1654                .split(area);
1655            let mut buffer = Buffer::empty(area);
1656            for (c, &area) in ('a'..='z').take(constraints.len()).zip(layout.iter()) {
1657                let s = c.to_string().repeat(area.width as usize);
1658                Text::from(s).render(area, &mut buffer);
1659            }
1660            assert_eq!(buffer, Buffer::with_lines([expected]));
1661        }
1662
1663        #[rstest]
1664        // flex, width, lengths, expected
1665        #[case(Flex::Legacy, 1, &[Length(0)], "a")] // zero
1666        #[case(Flex::Legacy, 1, &[Length(1)], "a")] // exact
1667        #[case(Flex::Legacy, 1, &[Length(2)], "a")] // overflow
1668        #[case(Flex::Legacy, 2, &[Length(0)], "aa")] // zero
1669        #[case(Flex::Legacy, 2, &[Length(1)], "aa")] // underflow
1670        #[case(Flex::Legacy, 2, &[Length(2)], "aa")] // exact
1671        #[case(Flex::Legacy, 2, &[Length(3)], "aa")] // overflow
1672        #[case(Flex::Legacy, 1, &[Length(0), Length(0)], "b")] // zero, zero
1673        #[case(Flex::Legacy, 1, &[Length(0), Length(1)], "b")] // zero, exact
1674        #[case(Flex::Legacy, 1, &[Length(0), Length(2)], "b")] // zero, overflow
1675        #[case(Flex::Legacy, 1, &[Length(1), Length(0)], "a")] // exact, zero
1676        #[case(Flex::Legacy, 1, &[Length(1), Length(1)], "a")] // exact, exact
1677        #[case(Flex::Legacy, 1, &[Length(1), Length(2)], "a")] // exact, overflow
1678        #[case(Flex::Legacy, 1, &[Length(2), Length(0)], "a")] // overflow, zero
1679        #[case(Flex::Legacy, 1, &[Length(2), Length(1)], "a")] // overflow, exact
1680        #[case(Flex::Legacy, 1, &[Length(2), Length(2)], "a")] // overflow, overflow
1681        #[case(Flex::Legacy, 2, &[Length(0), Length(0)], "bb")] // zero, zero
1682        #[case(Flex::Legacy, 2, &[Length(0), Length(1)], "bb")] // zero, underflow
1683        #[case(Flex::Legacy, 2, &[Length(0), Length(2)], "bb")] // zero, exact
1684        #[case(Flex::Legacy, 2, &[Length(0), Length(3)], "bb")] // zero, overflow
1685        #[case(Flex::Legacy, 2, &[Length(1), Length(0)], "ab")] // underflow, zero
1686        #[case(Flex::Legacy, 2, &[Length(1), Length(1)], "ab")] // underflow, underflow
1687        #[case(Flex::Legacy, 2, &[Length(1), Length(2)], "ab")] // underflow, exact
1688        #[case(Flex::Legacy, 2, &[Length(1), Length(3)], "ab")] // underflow, overflow
1689        #[case(Flex::Legacy, 2, &[Length(2), Length(0)], "aa")] // exact, zero
1690        #[case(Flex::Legacy, 2, &[Length(2), Length(1)], "aa")] // exact, underflow
1691        #[case(Flex::Legacy, 2, &[Length(2), Length(2)], "aa")] // exact, exact
1692        #[case(Flex::Legacy, 2, &[Length(2), Length(3)], "aa")] // exact, overflow
1693        #[case(Flex::Legacy, 2, &[Length(3), Length(0)], "aa")] // overflow, zero
1694        #[case(Flex::Legacy, 2, &[Length(3), Length(1)], "aa")] // overflow, underflow
1695        #[case(Flex::Legacy, 2, &[Length(3), Length(2)], "aa")] // overflow, exact
1696        #[case(Flex::Legacy, 2, &[Length(3), Length(3)], "aa")] // overflow, overflow
1697        #[case(Flex::Legacy, 3, &[Length(2), Length(2)], "aab")] // with stretchlast
1698        fn length(
1699            #[case] flex: Flex,
1700            #[case] width: u16,
1701            #[case] constraints: &[Constraint],
1702            #[case] expected: &str,
1703        ) {
1704            letters(flex, constraints, width, expected);
1705        }
1706
1707        #[rstest]
1708        #[case(Flex::Legacy, 1, &[Max(0)], "a")] // zero
1709        #[case(Flex::Legacy, 1, &[Max(1)], "a")] // exact
1710        #[case(Flex::Legacy, 1, &[Max(2)], "a")] // overflow
1711        #[case(Flex::Legacy, 2, &[Max(0)], "aa")] // zero
1712        #[case(Flex::Legacy, 2, &[Max(1)], "aa")] // underflow
1713        #[case(Flex::Legacy, 2, &[Max(2)], "aa")] // exact
1714        #[case(Flex::Legacy, 2, &[Max(3)], "aa")] // overflow
1715        #[case(Flex::Legacy, 1, &[Max(0), Max(0)], "b")] // zero, zero
1716        #[case(Flex::Legacy, 1, &[Max(0), Max(1)], "b")] // zero, exact
1717        #[case(Flex::Legacy, 1, &[Max(0), Max(2)], "b")] // zero, overflow
1718        #[case(Flex::Legacy, 1, &[Max(1), Max(0)], "a")] // exact, zero
1719        #[case(Flex::Legacy, 1, &[Max(1), Max(1)], "a")] // exact, exact
1720        #[case(Flex::Legacy, 1, &[Max(1), Max(2)], "a")] // exact, overflow
1721        #[case(Flex::Legacy, 1, &[Max(2), Max(0)], "a")] // overflow, zero
1722        #[case(Flex::Legacy, 1, &[Max(2), Max(1)], "a")] // overflow, exact
1723        #[case(Flex::Legacy, 1, &[Max(2), Max(2)], "a")] // overflow, overflow
1724        #[case(Flex::Legacy, 2, &[Max(0), Max(0)], "bb")] // zero, zero
1725        #[case(Flex::Legacy, 2, &[Max(0), Max(1)], "bb")] // zero, underflow
1726        #[case(Flex::Legacy, 2, &[Max(0), Max(2)], "bb")] // zero, exact
1727        #[case(Flex::Legacy, 2, &[Max(0), Max(3)], "bb")] // zero, overflow
1728        #[case(Flex::Legacy, 2, &[Max(1), Max(0)], "ab")] // underflow, zero
1729        #[case(Flex::Legacy, 2, &[Max(1), Max(1)], "ab")] // underflow, underflow
1730        #[case(Flex::Legacy, 2, &[Max(1), Max(2)], "ab")] // underflow, exact
1731        #[case(Flex::Legacy, 2, &[Max(1), Max(3)], "ab")] // underflow, overflow
1732        #[case(Flex::Legacy, 2, &[Max(2), Max(0)], "aa")] // exact, zero
1733        #[case(Flex::Legacy, 2, &[Max(2), Max(1)], "aa")] // exact, underflow
1734        #[case(Flex::Legacy, 2, &[Max(2), Max(2)], "aa")] // exact, exact
1735        #[case(Flex::Legacy, 2, &[Max(2), Max(3)], "aa")] // exact, overflow
1736        #[case(Flex::Legacy, 2, &[Max(3), Max(0)], "aa")] // overflow, zero
1737        #[case(Flex::Legacy, 2, &[Max(3), Max(1)], "aa")] // overflow, underflow
1738        #[case(Flex::Legacy, 2, &[Max(3), Max(2)], "aa")] // overflow, exact
1739        #[case(Flex::Legacy, 2, &[Max(3), Max(3)], "aa")] // overflow, overflow
1740        #[case(Flex::Legacy, 3, &[Max(2), Max(2)], "aab")]
1741        fn max(
1742            #[case] flex: Flex,
1743            #[case] width: u16,
1744            #[case] constraints: &[Constraint],
1745            #[case] expected: &str,
1746        ) {
1747            letters(flex, constraints, width, expected);
1748        }
1749
1750        #[rstest]
1751        #[case(Flex::Legacy, 1, &[Min(0), Min(0)], "b")] // zero, zero
1752        #[case(Flex::Legacy, 1, &[Min(0), Min(1)], "b")] // zero, exact
1753        #[case(Flex::Legacy, 1, &[Min(0), Min(2)], "b")] // zero, overflow
1754        #[case(Flex::Legacy, 1, &[Min(1), Min(0)], "a")] // exact, zero
1755        #[case(Flex::Legacy, 1, &[Min(1), Min(1)], "a")] // exact, exact
1756        #[case(Flex::Legacy, 1, &[Min(1), Min(2)], "a")] // exact, overflow
1757        #[case(Flex::Legacy, 1, &[Min(2), Min(0)], "a")] // overflow, zero
1758        #[case(Flex::Legacy, 1, &[Min(2), Min(1)], "a")] // overflow, exact
1759        #[case(Flex::Legacy, 1, &[Min(2), Min(2)], "a")] // overflow, overflow
1760        #[case(Flex::Legacy, 2, &[Min(0), Min(0)], "bb")] // zero, zero
1761        #[case(Flex::Legacy, 2, &[Min(0), Min(1)], "bb")] // zero, underflow
1762        #[case(Flex::Legacy, 2, &[Min(0), Min(2)], "bb")] // zero, exact
1763        #[case(Flex::Legacy, 2, &[Min(0), Min(3)], "bb")] // zero, overflow
1764        #[case(Flex::Legacy, 2, &[Min(1), Min(0)], "ab")] // underflow, zero
1765        #[case(Flex::Legacy, 2, &[Min(1), Min(1)], "ab")] // underflow, underflow
1766        #[case(Flex::Legacy, 2, &[Min(1), Min(2)], "ab")] // underflow, exact
1767        #[case(Flex::Legacy, 2, &[Min(1), Min(3)], "ab")] // underflow, overflow
1768        #[case(Flex::Legacy, 2, &[Min(2), Min(0)], "aa")] // exact, zero
1769        #[case(Flex::Legacy, 2, &[Min(2), Min(1)], "aa")] // exact, underflow
1770        #[case(Flex::Legacy, 2, &[Min(2), Min(2)], "aa")] // exact, exact
1771        #[case(Flex::Legacy, 2, &[Min(2), Min(3)], "aa")] // exact, overflow
1772        #[case(Flex::Legacy, 2, &[Min(3), Min(0)], "aa")] // overflow, zero
1773        #[case(Flex::Legacy, 2, &[Min(3), Min(1)], "aa")] // overflow, underflow
1774        #[case(Flex::Legacy, 2, &[Min(3), Min(2)], "aa")] // overflow, exact
1775        #[case(Flex::Legacy, 2, &[Min(3), Min(3)], "aa")] // overflow, overflow
1776        #[case(Flex::Legacy, 3, &[Min(2), Min(2)], "aab")]
1777        fn min(
1778            #[case] flex: Flex,
1779            #[case] width: u16,
1780            #[case] constraints: &[Constraint],
1781            #[case] expected: &str,
1782        ) {
1783            letters(flex, constraints, width, expected);
1784        }
1785
1786        #[rstest] // flex, width, lengths, expected
1787        // One constraint will take all the space (width = 1)
1788        #[case(Flex::Legacy, 1, &[Percentage(0)],   "a")]
1789        #[case(Flex::Legacy, 1, &[Percentage(25)],  "a")]
1790        #[case(Flex::Legacy, 1, &[Percentage(50)],  "a")]
1791        #[case(Flex::Legacy, 1, &[Percentage(90)],  "a")]
1792        #[case(Flex::Legacy, 1, &[Percentage(100)], "a")]
1793        #[case(Flex::Legacy, 1, &[Percentage(200)], "a")]
1794        // One constraint will take all the space (width = 2)
1795        #[case(Flex::Legacy, 2, &[Percentage(0)],   "aa")]
1796        #[case(Flex::Legacy, 2, &[Percentage(10)],  "aa")]
1797        #[case(Flex::Legacy, 2, &[Percentage(25)],  "aa")]
1798        #[case(Flex::Legacy, 2, &[Percentage(50)],  "aa")]
1799        #[case(Flex::Legacy, 2, &[Percentage(66)],  "aa")]
1800        #[case(Flex::Legacy, 2, &[Percentage(100)], "aa")]
1801        #[case(Flex::Legacy, 2, &[Percentage(200)], "aa")]
1802        // One constraint will take all the space (width = 3)
1803        #[case(Flex::Legacy, 10, &[Percentage(0)],   "aaaaaaaaaa")]
1804        #[case(Flex::Legacy, 10, &[Percentage(10)],  "aaaaaaaaaa")]
1805        #[case(Flex::Legacy, 10, &[Percentage(25)],  "aaaaaaaaaa")]
1806        #[case(Flex::Legacy, 10, &[Percentage(50)],  "aaaaaaaaaa")]
1807        #[case(Flex::Legacy, 10, &[Percentage(66)],  "aaaaaaaaaa")]
1808        #[case(Flex::Legacy, 10, &[Percentage(100)], "aaaaaaaaaa")]
1809        #[case(Flex::Legacy, 10, &[Percentage(200)], "aaaaaaaaaa")]
1810        // 0%/any allocates all the space to the second constraint
1811        #[case(Flex::Legacy, 1, &[Percentage(0), Percentage(0)],   "b")]
1812        #[case(Flex::Legacy, 1, &[Percentage(0), Percentage(10)],  "b")]
1813        #[case(Flex::Legacy, 1, &[Percentage(0), Percentage(50)],  "b")]
1814        #[case(Flex::Legacy, 1, &[Percentage(0), Percentage(90)],  "b")]
1815        #[case(Flex::Legacy, 1, &[Percentage(0), Percentage(100)], "b")]
1816        #[case(Flex::Legacy, 1, &[Percentage(0), Percentage(200)], "b")]
1817        // 10%/any allocates all the space to the second constraint (even if it is 0)
1818        #[case(Flex::Legacy, 1, &[Percentage(10), Percentage(0)],   "b")]
1819        #[case(Flex::Legacy, 1, &[Percentage(10), Percentage(10)],  "b")]
1820        #[case(Flex::Legacy, 1, &[Percentage(10), Percentage(50)],  "b")]
1821        #[case(Flex::Legacy, 1, &[Percentage(10), Percentage(90)],  "b")]
1822        #[case(Flex::Legacy, 1, &[Percentage(10), Percentage(100)], "b")]
1823        #[case(Flex::Legacy, 1, &[Percentage(10), Percentage(200)], "b")]
1824        // 50%/any allocates all the space to the first constraint
1825        #[case(Flex::Legacy, 1, &[Percentage(50), Percentage(0)],   "a")]
1826        #[case(Flex::Legacy, 1, &[Percentage(50), Percentage(50)],  "a")]
1827        #[case(Flex::Legacy, 1, &[Percentage(50), Percentage(100)], "a")]
1828        #[case(Flex::Legacy, 1, &[Percentage(50), Percentage(200)], "a")]
1829        // 90%/any allocates all the space to the first constraint
1830        #[case(Flex::Legacy, 1, &[Percentage(90), Percentage(0)],   "a")]
1831        #[case(Flex::Legacy, 1, &[Percentage(90), Percentage(50)],  "a")]
1832        #[case(Flex::Legacy, 1, &[Percentage(90), Percentage(100)], "a")]
1833        #[case(Flex::Legacy, 1, &[Percentage(90), Percentage(200)], "a")]
1834        // 100%/any allocates all the space to the first constraint
1835        #[case(Flex::Legacy, 1, &[Percentage(100), Percentage(0)],   "a")]
1836        #[case(Flex::Legacy, 1, &[Percentage(100), Percentage(50)],  "a")]
1837        #[case(Flex::Legacy, 1, &[Percentage(100), Percentage(100)], "a")]
1838        #[case(Flex::Legacy, 1, &[Percentage(100), Percentage(200)], "a")]
1839        // 0%/any allocates all the space to the second constraint
1840        #[case(Flex::Legacy, 2, &[Percentage(0), Percentage(0)],   "bb")]
1841        #[case(Flex::Legacy, 2, &[Percentage(0), Percentage(25)],  "bb")]
1842        #[case(Flex::Legacy, 2, &[Percentage(0), Percentage(50)],  "bb")]
1843        #[case(Flex::Legacy, 2, &[Percentage(0), Percentage(100)], "bb")]
1844        #[case(Flex::Legacy, 2, &[Percentage(0), Percentage(200)], "bb")]
1845        // 10%/any allocates all the space to the second constraint
1846        #[case(Flex::Legacy, 2, &[Percentage(10), Percentage(0)],   "bb")]
1847        #[case(Flex::Legacy, 2, &[Percentage(10), Percentage(25)],  "bb")]
1848        #[case(Flex::Legacy, 2, &[Percentage(10), Percentage(50)],  "bb")]
1849        #[case(Flex::Legacy, 2, &[Percentage(10), Percentage(100)], "bb")]
1850        #[case(Flex::Legacy, 2, &[Percentage(10), Percentage(200)], "bb")]
1851        // 25% * 2 = 0.5, which rounds up to 1, so the first constraint gets 1
1852        #[case(Flex::Legacy, 2, &[Percentage(25), Percentage(0)],   "ab")]
1853        #[case(Flex::Legacy, 2, &[Percentage(25), Percentage(25)],  "ab")]
1854        #[case(Flex::Legacy, 2, &[Percentage(25), Percentage(50)],  "ab")]
1855        #[case(Flex::Legacy, 2, &[Percentage(25), Percentage(100)], "ab")]
1856        #[case(Flex::Legacy, 2, &[Percentage(25), Percentage(200)], "ab")]
1857        // 33% * 2 = 0.66, so the first constraint gets 1
1858        #[case(Flex::Legacy, 2, &[Percentage(33), Percentage(0)],   "ab")]
1859        #[case(Flex::Legacy, 2, &[Percentage(33), Percentage(25)],  "ab")]
1860        #[case(Flex::Legacy, 2, &[Percentage(33), Percentage(50)],  "ab")]
1861        #[case(Flex::Legacy, 2, &[Percentage(33), Percentage(100)], "ab")]
1862        #[case(Flex::Legacy, 2, &[Percentage(33), Percentage(200)], "ab")]
1863        // 50% * 2 = 1, so the first constraint gets 1
1864        #[case(Flex::Legacy, 2, &[Percentage(50), Percentage(0)],   "ab")]
1865        #[case(Flex::Legacy, 2, &[Percentage(50), Percentage(50)],  "ab")]
1866        #[case(Flex::Legacy, 2, &[Percentage(50), Percentage(100)], "ab")]
1867        // 100%/any allocates all the space to the first constraint
1868        // This is probably not the correct behavior, but it is the current behavior
1869        #[case(Flex::Legacy, 2, &[Percentage(100), Percentage(0)],   "aa")]
1870        #[case(Flex::Legacy, 2, &[Percentage(100), Percentage(50)],  "aa")]
1871        #[case(Flex::Legacy, 2, &[Percentage(100), Percentage(100)], "aa")]
1872        // 33%/any allocates 1 to the first constraint the rest to the second
1873        #[case(Flex::Legacy, 3, &[Percentage(33), Percentage(33)], "abb")]
1874        #[case(Flex::Legacy, 3, &[Percentage(33), Percentage(66)], "abb")]
1875        // 33%/any allocates 1.33 = 1 to the first constraint the rest to the second
1876        #[case(Flex::Legacy, 4, &[Percentage(33), Percentage(33)], "abbb")]
1877        #[case(Flex::Legacy, 4, &[Percentage(33), Percentage(66)], "abbb")]
1878        // Longer tests zero allocates everything to the second constraint
1879        #[case(Flex::Legacy, 10, &[Percentage(0),   Percentage(0)],   "bbbbbbbbbb" )]
1880        #[case(Flex::Legacy, 10, &[Percentage(0),   Percentage(25)],  "bbbbbbbbbb" )]
1881        #[case(Flex::Legacy, 10, &[Percentage(0),   Percentage(50)],  "bbbbbbbbbb" )]
1882        #[case(Flex::Legacy, 10, &[Percentage(0),   Percentage(100)], "bbbbbbbbbb" )]
1883        #[case(Flex::Legacy, 10, &[Percentage(0),   Percentage(200)], "bbbbbbbbbb" )]
1884        // 10% allocates a single character to the first constraint
1885        #[case(Flex::Legacy, 10, &[Percentage(10),  Percentage(0)],   "abbbbbbbbb" )]
1886        #[case(Flex::Legacy, 10, &[Percentage(10),  Percentage(25)],  "abbbbbbbbb" )]
1887        #[case(Flex::Legacy, 10, &[Percentage(10),  Percentage(50)],  "abbbbbbbbb" )]
1888        #[case(Flex::Legacy, 10, &[Percentage(10),  Percentage(100)], "abbbbbbbbb" )]
1889        #[case(Flex::Legacy, 10, &[Percentage(10),  Percentage(200)], "abbbbbbbbb" )]
1890        // 25% allocates 2.5 = 3 characters to the first constraint
1891        #[case(Flex::Legacy, 10, &[Percentage(25),  Percentage(0)],   "aaabbbbbbb" )]
1892        #[case(Flex::Legacy, 10, &[Percentage(25),  Percentage(25)],  "aaabbbbbbb" )]
1893        #[case(Flex::Legacy, 10, &[Percentage(25),  Percentage(50)],  "aaabbbbbbb" )]
1894        #[case(Flex::Legacy, 10, &[Percentage(25),  Percentage(100)], "aaabbbbbbb" )]
1895        #[case(Flex::Legacy, 10, &[Percentage(25),  Percentage(200)], "aaabbbbbbb" )]
1896        // 33% allocates 3.3 = 3 characters to the first constraint
1897        #[case(Flex::Legacy, 10, &[Percentage(33),  Percentage(0)],   "aaabbbbbbb" )]
1898        #[case(Flex::Legacy, 10, &[Percentage(33),  Percentage(25)],  "aaabbbbbbb" )]
1899        #[case(Flex::Legacy, 10, &[Percentage(33),  Percentage(50)],  "aaabbbbbbb" )]
1900        #[case(Flex::Legacy, 10, &[Percentage(33),  Percentage(100)], "aaabbbbbbb" )]
1901        #[case(Flex::Legacy, 10, &[Percentage(33),  Percentage(200)], "aaabbbbbbb" )]
1902        // 50% allocates 5 characters to the first constraint
1903        #[case(Flex::Legacy, 10, &[Percentage(50),  Percentage(0)],   "aaaaabbbbb" )]
1904        #[case(Flex::Legacy, 10, &[Percentage(50),  Percentage(50)],  "aaaaabbbbb" )]
1905        #[case(Flex::Legacy, 10, &[Percentage(50),  Percentage(100)], "aaaaabbbbb" )]
1906        // 100% allocates everything to the first constraint
1907        #[case(Flex::Legacy, 10, &[Percentage(100), Percentage(0)],   "aaaaaaaaaa" )]
1908        #[case(Flex::Legacy, 10, &[Percentage(100), Percentage(50)],  "aaaaaaaaaa" )]
1909        #[case(Flex::Legacy, 10, &[Percentage(100), Percentage(100)], "aaaaaaaaaa" )]
1910        fn percentage(
1911            #[case] flex: Flex,
1912            #[case] width: u16,
1913            #[case] constraints: &[Constraint],
1914            #[case] expected: &str,
1915        ) {
1916            letters(flex, constraints, width, expected);
1917        }
1918
1919        #[rstest]
1920        #[case(Flex::Start, 10, &[Percentage(0),   Percentage(0)],    "          " )]
1921        #[case(Flex::Start, 10, &[Percentage(0),   Percentage(25)],  "bbb       " )]
1922        #[case(Flex::Start, 10, &[Percentage(0),   Percentage(50)],  "bbbbb     " )]
1923        #[case(Flex::Start, 10, &[Percentage(0),   Percentage(100)], "bbbbbbbbbb" )]
1924        #[case(Flex::Start, 10, &[Percentage(0),   Percentage(200)], "bbbbbbbbbb" )]
1925        #[case(Flex::Start, 10, &[Percentage(10),  Percentage(0)],   "a         " )]
1926        #[case(Flex::Start, 10, &[Percentage(10),  Percentage(25)],  "abbb      " )]
1927        #[case(Flex::Start, 10, &[Percentage(10),  Percentage(50)],  "abbbbb    " )]
1928        #[case(Flex::Start, 10, &[Percentage(10),  Percentage(100)], "abbbbbbbbb" )]
1929        #[case(Flex::Start, 10, &[Percentage(10),  Percentage(200)], "abbbbbbbbb" )]
1930        #[case(Flex::Start, 10, &[Percentage(25),  Percentage(0)],   "aaa       " )]
1931        #[case(Flex::Start, 10, &[Percentage(25),  Percentage(25)],  "aaabb     " )]
1932        #[case(Flex::Start, 10, &[Percentage(25),  Percentage(50)],  "aaabbbbb  " )]
1933        #[case(Flex::Start, 10, &[Percentage(25),  Percentage(100)], "aaabbbbbbb" )]
1934        #[case(Flex::Start, 10, &[Percentage(25),  Percentage(200)], "aaabbbbbbb" )]
1935        #[case(Flex::Start, 10, &[Percentage(33),  Percentage(0)],   "aaa       " )]
1936        #[case(Flex::Start, 10, &[Percentage(33),  Percentage(25)],  "aaabbb    " )]
1937        #[case(Flex::Start, 10, &[Percentage(33),  Percentage(50)],  "aaabbbbb  " )]
1938        #[case(Flex::Start, 10, &[Percentage(33),  Percentage(100)], "aaabbbbbbb" )]
1939        #[case(Flex::Start, 10, &[Percentage(33),  Percentage(200)], "aaabbbbbbb" )]
1940        #[case(Flex::Start, 10, &[Percentage(50),  Percentage(0)],   "aaaaa     " )]
1941        #[case(Flex::Start, 10, &[Percentage(50),  Percentage(50)],  "aaaaabbbbb" )]
1942        #[case(Flex::Start, 10, &[Percentage(50),  Percentage(100)], "aaaaabbbbb" )]
1943        #[case(Flex::Start, 10, &[Percentage(100), Percentage(0)],   "aaaaaaaaaa" )]
1944        #[case(Flex::Start, 10, &[Percentage(100), Percentage(50)],  "aaaaabbbbb" )]
1945        #[case(Flex::Start, 10, &[Percentage(100), Percentage(100)], "aaaaabbbbb" )]
1946        #[case(Flex::Start, 10, &[Percentage(100), Percentage(200)], "aaaaabbbbb" )]
1947        fn percentage_start(
1948            #[case] flex: Flex,
1949            #[case] width: u16,
1950            #[case] constraints: &[Constraint],
1951            #[case] expected: &str,
1952        ) {
1953            letters(flex, constraints, width, expected);
1954        }
1955
1956        #[rstest]
1957        #[case(Flex::SpaceBetween, 10, &[Percentage(0),   Percentage(0)],   "          " )]
1958        #[case(Flex::SpaceBetween, 10, &[Percentage(0),   Percentage(25)],  "        bb" )]
1959        #[case(Flex::SpaceBetween, 10, &[Percentage(0),   Percentage(50)],  "     bbbbb" )]
1960        #[case(Flex::SpaceBetween, 10, &[Percentage(0),   Percentage(100)], "bbbbbbbbbb" )]
1961        #[case(Flex::SpaceBetween, 10, &[Percentage(0),   Percentage(200)], "bbbbbbbbbb" )]
1962        #[case(Flex::SpaceBetween, 10, &[Percentage(10),  Percentage(0)],   "a         " )]
1963        #[case(Flex::SpaceBetween, 10, &[Percentage(10),  Percentage(25)],  "a       bb" )]
1964        #[case(Flex::SpaceBetween, 10, &[Percentage(10),  Percentage(50)],  "a    bbbbb" )]
1965        #[case(Flex::SpaceBetween, 10, &[Percentage(10),  Percentage(100)], "abbbbbbbbb" )]
1966        #[case(Flex::SpaceBetween, 10, &[Percentage(10),  Percentage(200)], "abbbbbbbbb" )]
1967        #[case(Flex::SpaceBetween, 10, &[Percentage(25),  Percentage(0)],   "aaa       " )]
1968        #[case(Flex::SpaceBetween, 10, &[Percentage(25),  Percentage(25)],  "aaa     bb" )]
1969        #[case(Flex::SpaceBetween, 10, &[Percentage(25),  Percentage(50)],  "aaa  bbbbb" )]
1970        #[case(Flex::SpaceBetween, 10, &[Percentage(25),  Percentage(100)], "aaabbbbbbb" )]
1971        #[case(Flex::SpaceBetween, 10, &[Percentage(25),  Percentage(200)], "aaabbbbbbb" )]
1972        #[case(Flex::SpaceBetween, 10, &[Percentage(33),  Percentage(0)],   "aaa       " )]
1973        #[case(Flex::SpaceBetween, 10, &[Percentage(33),  Percentage(25)],  "aaa     bb" )]
1974        #[case(Flex::SpaceBetween, 10, &[Percentage(33),  Percentage(50)],  "aaa  bbbbb" )]
1975        #[case(Flex::SpaceBetween, 10, &[Percentage(33),  Percentage(100)], "aaabbbbbbb" )]
1976        #[case(Flex::SpaceBetween, 10, &[Percentage(33),  Percentage(200)], "aaabbbbbbb" )]
1977        #[case(Flex::SpaceBetween, 10, &[Percentage(50),  Percentage(0)],   "aaaaa     " )]
1978        #[case(Flex::SpaceBetween, 10, &[Percentage(50),  Percentage(50)],  "aaaaabbbbb" )]
1979        #[case(Flex::SpaceBetween, 10, &[Percentage(50),  Percentage(100)], "aaaaabbbbb" )]
1980        #[case(Flex::SpaceBetween, 10, &[Percentage(100), Percentage(0)],   "aaaaaaaaaa" )]
1981        #[case(Flex::SpaceBetween, 10, &[Percentage(100), Percentage(50)],  "aaaaabbbbb" )]
1982        #[case(Flex::SpaceBetween, 10, &[Percentage(100), Percentage(100)], "aaaaabbbbb" )]
1983        #[case(Flex::SpaceBetween, 10, &[Percentage(100), Percentage(200)], "aaaaabbbbb" )]
1984        fn percentage_spacebetween(
1985            #[case] flex: Flex,
1986            #[case] width: u16,
1987            #[case] constraints: &[Constraint],
1988            #[case] expected: &str,
1989        ) {
1990            letters(flex, constraints, width, expected);
1991        }
1992
1993        #[rstest]
1994        // flex, width, ratios, expected
1995        // Just one ratio takes up the whole space
1996        #[case(Flex::Legacy, 1, &[Ratio(0, 1)], "a")]
1997        #[case(Flex::Legacy, 1, &[Ratio(1, 4)], "a")]
1998        #[case(Flex::Legacy, 1, &[Ratio(1, 2)], "a")]
1999        #[case(Flex::Legacy, 1, &[Ratio(9, 10)], "a")]
2000        #[case(Flex::Legacy, 1, &[Ratio(1, 1)], "a")]
2001        #[case(Flex::Legacy, 1, &[Ratio(2, 1)], "a")]
2002        #[case(Flex::Legacy, 2, &[Ratio(0, 1)], "aa")]
2003        #[case(Flex::Legacy, 2, &[Ratio(1, 10)], "aa")]
2004        #[case(Flex::Legacy, 2, &[Ratio(1, 4)], "aa")]
2005        #[case(Flex::Legacy, 2, &[Ratio(1, 2)], "aa")]
2006        #[case(Flex::Legacy, 2, &[Ratio(2, 3)], "aa")]
2007        #[case(Flex::Legacy, 2, &[Ratio(1, 1)], "aa")]
2008        #[case(Flex::Legacy, 2, &[Ratio(2, 1)], "aa")]
2009        #[case(Flex::Legacy, 1, &[Ratio(0, 1), Ratio(0, 1)], "b")]
2010        #[case(Flex::Legacy, 1, &[Ratio(0, 1), Ratio(1, 10)], "b")]
2011        #[case(Flex::Legacy, 1, &[Ratio(0, 1), Ratio(1, 2)], "b")]
2012        #[case(Flex::Legacy, 1, &[Ratio(0, 1), Ratio(9, 10)], "b")]
2013        #[case(Flex::Legacy, 1, &[Ratio(0, 1), Ratio(1, 1)], "b")]
2014        #[case(Flex::Legacy, 1, &[Ratio(0, 1), Ratio(2, 1)], "b")]
2015        #[case(Flex::Legacy, 1, &[Ratio(1, 10), Ratio(0, 1)], "b")]
2016        #[case(Flex::Legacy, 1, &[Ratio(1, 10), Ratio(1, 10)], "b")]
2017        #[case(Flex::Legacy, 1, &[Ratio(1, 10), Ratio(1, 2)], "b")]
2018        #[case(Flex::Legacy, 1, &[Ratio(1, 10), Ratio(9, 10)], "b")]
2019        #[case(Flex::Legacy, 1, &[Ratio(1, 10), Ratio(1, 1)], "b")]
2020        #[case(Flex::Legacy, 1, &[Ratio(1, 10), Ratio(2, 1)], "b")]
2021        #[case(Flex::Legacy, 1, &[Ratio(1, 2), Ratio(0, 1)], "a")]
2022        #[case(Flex::Legacy, 1, &[Ratio(1, 2), Ratio(1, 2)], "a")]
2023        #[case(Flex::Legacy, 1, &[Ratio(1, 2), Ratio(1, 1)], "a")]
2024        #[case(Flex::Legacy, 1, &[Ratio(1, 2), Ratio(2, 1)], "a")]
2025        #[case(Flex::Legacy, 1, &[Ratio(9, 10), Ratio(0, 1)], "a")]
2026        #[case(Flex::Legacy, 1, &[Ratio(9, 10), Ratio(1, 2)], "a")]
2027        #[case(Flex::Legacy, 1, &[Ratio(9, 10), Ratio(1, 1)], "a")]
2028        #[case(Flex::Legacy, 1, &[Ratio(9, 10), Ratio(2, 1)], "a")]
2029        #[case(Flex::Legacy, 1, &[Ratio(1, 1), Ratio(0, 1)], "a")]
2030        #[case(Flex::Legacy, 1, &[Ratio(1, 1), Ratio(1, 2)], "a")]
2031        #[case(Flex::Legacy, 1, &[Ratio(1, 1), Ratio(1, 1)], "a")]
2032        #[case(Flex::Legacy, 1, &[Ratio(1, 1), Ratio(2, 1)], "a")]
2033        #[case(Flex::Legacy, 2, &[Ratio(0, 1), Ratio(0, 1)], "bb")]
2034        #[case(Flex::Legacy, 2, &[Ratio(0, 1), Ratio(1, 4)], "bb")]
2035        #[case(Flex::Legacy, 2, &[Ratio(0, 1), Ratio(1, 2)], "bb")]
2036        #[case(Flex::Legacy, 2, &[Ratio(0, 1), Ratio(1, 1)], "bb")]
2037        #[case(Flex::Legacy, 2, &[Ratio(0, 1), Ratio(2, 1)], "bb")]
2038        #[case(Flex::Legacy, 2, &[Ratio(1, 10), Ratio(0, 1)], "bb")]
2039        #[case(Flex::Legacy, 2, &[Ratio(1, 10), Ratio(1, 4)], "bb")]
2040        #[case(Flex::Legacy, 2, &[Ratio(1, 10), Ratio(1, 2)], "bb")]
2041        #[case(Flex::Legacy, 2, &[Ratio(1, 10), Ratio(1, 1)], "bb")]
2042        #[case(Flex::Legacy, 2, &[Ratio(1, 10), Ratio(2, 1)], "bb")]
2043        #[case(Flex::Legacy, 2, &[Ratio(1, 4), Ratio(0, 1)], "ab")]
2044        #[case(Flex::Legacy, 2, &[Ratio(1, 4), Ratio(1, 4)], "ab")]
2045        #[case(Flex::Legacy, 2, &[Ratio(1, 4), Ratio(1, 2)], "ab")]
2046        #[case(Flex::Legacy, 2, &[Ratio(1, 4), Ratio(1, 1)], "ab")]
2047        #[case(Flex::Legacy, 2, &[Ratio(1, 4), Ratio(2, 1)], "ab")]
2048        #[case(Flex::Legacy, 2, &[Ratio(1, 3), Ratio(0, 1)], "ab")]
2049        #[case(Flex::Legacy, 2, &[Ratio(1, 3), Ratio(1, 4)], "ab")]
2050        #[case(Flex::Legacy, 2, &[Ratio(1, 3), Ratio(1, 2)], "ab")]
2051        #[case(Flex::Legacy, 2, &[Ratio(1, 3), Ratio(1, 1)], "ab")]
2052        #[case(Flex::Legacy, 2, &[Ratio(1, 3), Ratio(2, 1)], "ab")]
2053        #[case(Flex::Legacy, 2, &[Ratio(1, 2), Ratio(0, 1)], "ab")]
2054        #[case(Flex::Legacy, 2, &[Ratio(1, 2), Ratio(1, 2)], "ab")]
2055        #[case(Flex::Legacy, 2, &[Ratio(1, 2), Ratio(1, 1)], "ab")]
2056        #[case(Flex::Legacy, 2, &[Ratio(1, 1), Ratio(0, 1)], "aa")]
2057        #[case(Flex::Legacy, 2, &[Ratio(1, 1), Ratio(1, 2)], "aa")]
2058        #[case(Flex::Legacy, 2, &[Ratio(1, 1), Ratio(1, 1)], "aa")]
2059        #[case(Flex::Legacy, 3, &[Ratio(1, 3), Ratio(1, 3)], "abb")]
2060        #[case(Flex::Legacy, 3, &[Ratio(1, 3), Ratio(2,3)], "abb")]
2061        #[case(Flex::Legacy, 10, &[Ratio(0, 1), Ratio(0, 1)],  "bbbbbbbbbb" )]
2062        #[case(Flex::Legacy, 10, &[Ratio(0, 1), Ratio(1, 4)],  "bbbbbbbbbb" )]
2063        #[case(Flex::Legacy, 10, &[Ratio(0, 1), Ratio(1, 2)],  "bbbbbbbbbb" )]
2064        #[case(Flex::Legacy, 10, &[Ratio(0, 1), Ratio(1, 1)],  "bbbbbbbbbb" )]
2065        #[case(Flex::Legacy, 10, &[Ratio(0, 1), Ratio(2, 1)],  "bbbbbbbbbb" )]
2066        #[case(Flex::Legacy, 10, &[Ratio(1, 10), Ratio(0, 1)], "abbbbbbbbb" )]
2067        #[case(Flex::Legacy, 10, &[Ratio(1, 10), Ratio(1, 4)], "abbbbbbbbb" )]
2068        #[case(Flex::Legacy, 10, &[Ratio(1, 10), Ratio(1, 2)], "abbbbbbbbb" )]
2069        #[case(Flex::Legacy, 10, &[Ratio(1, 10), Ratio(1, 1)], "abbbbbbbbb" )]
2070        #[case(Flex::Legacy, 10, &[Ratio(1, 10), Ratio(2, 1)], "abbbbbbbbb" )]
2071        #[case(Flex::Legacy, 10, &[Ratio(1, 4), Ratio(0, 1)],  "aaabbbbbbb" )]
2072        #[case(Flex::Legacy, 10, &[Ratio(1, 4), Ratio(1, 4)],  "aaabbbbbbb" )]
2073        #[case(Flex::Legacy, 10, &[Ratio(1, 4), Ratio(1, 2)],  "aaabbbbbbb" )]
2074        #[case(Flex::Legacy, 10, &[Ratio(1, 4), Ratio(1, 1)],  "aaabbbbbbb" )]
2075        #[case(Flex::Legacy, 10, &[Ratio(1, 4), Ratio(2, 1)],  "aaabbbbbbb" )]
2076        #[case(Flex::Legacy, 10, &[Ratio(1, 3), Ratio(0, 1)],  "aaabbbbbbb" )]
2077        #[case(Flex::Legacy, 10, &[Ratio(1, 3), Ratio(1, 4)],  "aaabbbbbbb" )]
2078        #[case(Flex::Legacy, 10, &[Ratio(1, 3), Ratio(1, 2)],  "aaabbbbbbb" )]
2079        #[case(Flex::Legacy, 10, &[Ratio(1, 3), Ratio(1, 1)],  "aaabbbbbbb" )]
2080        #[case(Flex::Legacy, 10, &[Ratio(1, 3), Ratio(2, 1)],  "aaabbbbbbb" )]
2081        #[case(Flex::Legacy, 10, &[Ratio(1, 2), Ratio(0, 1)],  "aaaaabbbbb" )]
2082        #[case(Flex::Legacy, 10, &[Ratio(1, 2), Ratio(1, 2)],  "aaaaabbbbb" )]
2083        #[case(Flex::Legacy, 10, &[Ratio(1, 2), Ratio(1, 1)],  "aaaaabbbbb" )]
2084        #[case(Flex::Legacy, 10, &[Ratio(1, 1), Ratio(0, 1)],  "aaaaaaaaaa" )]
2085        #[case(Flex::Legacy, 10, &[Ratio(1, 1), Ratio(1, 2)],  "aaaaaaaaaa" )]
2086        #[case(Flex::Legacy, 10, &[Ratio(1, 1), Ratio(1, 1)],  "aaaaaaaaaa" )]
2087        fn ratio(
2088            #[case] flex: Flex,
2089            #[case] width: u16,
2090            #[case] constraints: &[Constraint],
2091            #[case] expected: &str,
2092        ) {
2093            letters(flex, constraints, width, expected);
2094        }
2095
2096        #[rstest]
2097        #[case(Flex::Start, 10, &[Ratio(0, 1), Ratio(0, 1)],   "          " )]
2098        #[case(Flex::Start, 10, &[Ratio(0, 1), Ratio(1, 4)],  "bbb       " )]
2099        #[case(Flex::Start, 10, &[Ratio(0, 1), Ratio(1, 2)],  "bbbbb     " )]
2100        #[case(Flex::Start, 10, &[Ratio(0, 1), Ratio(1, 1)],  "bbbbbbbbbb" )]
2101        #[case(Flex::Start, 10, &[Ratio(0, 1), Ratio(2, 1)],  "bbbbbbbbbb" )]
2102        #[case(Flex::Start, 10, &[Ratio(1, 10), Ratio(0, 1)], "a         " )]
2103        #[case(Flex::Start, 10, &[Ratio(1, 10), Ratio(1, 4)], "abbb      " )]
2104        #[case(Flex::Start, 10, &[Ratio(1, 10), Ratio(1, 2)], "abbbbb    " )]
2105        #[case(Flex::Start, 10, &[Ratio(1, 10), Ratio(1, 1)], "abbbbbbbbb" )]
2106        #[case(Flex::Start, 10, &[Ratio(1, 10), Ratio(2, 1)], "abbbbbbbbb" )]
2107        #[case(Flex::Start, 10, &[Ratio(1, 4), Ratio(0, 1)],  "aaa       " )]
2108        #[case(Flex::Start, 10, &[Ratio(1, 4), Ratio(1, 4)],  "aaabb     " )]
2109        #[case(Flex::Start, 10, &[Ratio(1, 4), Ratio(1, 2)],  "aaabbbbb  " )]
2110        #[case(Flex::Start, 10, &[Ratio(1, 4), Ratio(1, 1)],  "aaabbbbbbb" )]
2111        #[case(Flex::Start, 10, &[Ratio(1, 4), Ratio(2, 1)],  "aaabbbbbbb" )]
2112        #[case(Flex::Start, 10, &[Ratio(1, 3), Ratio(0, 1)],  "aaa       " )]
2113        #[case(Flex::Start, 10, &[Ratio(1, 3), Ratio(1, 4)],  "aaabbb    " )]
2114        #[case(Flex::Start, 10, &[Ratio(1, 3), Ratio(1, 2)],  "aaabbbbb  " )]
2115        #[case(Flex::Start, 10, &[Ratio(1, 3), Ratio(1, 1)],  "aaabbbbbbb" )]
2116        #[case(Flex::Start, 10, &[Ratio(1, 3), Ratio(2, 1)],  "aaabbbbbbb" )]
2117        #[case(Flex::Start, 10, &[Ratio(1, 2), Ratio(0, 1)],  "aaaaa     " )]
2118        #[case(Flex::Start, 10, &[Ratio(1, 2), Ratio(1, 2)],  "aaaaabbbbb" )]
2119        #[case(Flex::Start, 10, &[Ratio(1, 2), Ratio(1, 1)],  "aaaaabbbbb" )]
2120        #[case(Flex::Start, 10, &[Ratio(1, 1), Ratio(0, 1)],  "aaaaaaaaaa" )]
2121        #[case(Flex::Start, 10, &[Ratio(1, 1), Ratio(1, 2)],  "aaaaabbbbb" )]
2122        #[case(Flex::Start, 10, &[Ratio(1, 1), Ratio(1, 1)],  "aaaaabbbbb" )]
2123        #[case(Flex::Start, 10, &[Ratio(1, 1), Ratio(2, 1)],  "aaaaabbbbb" )]
2124        fn ratio_start(
2125            #[case] flex: Flex,
2126            #[case] width: u16,
2127            #[case] constraints: &[Constraint],
2128            #[case] expected: &str,
2129        ) {
2130            letters(flex, constraints, width, expected);
2131        }
2132
2133        #[rstest]
2134        #[case(Flex::SpaceBetween, 10, &[Ratio(0, 1), Ratio(0, 1)],  "          " )]
2135        #[case(Flex::SpaceBetween, 10, &[Ratio(0, 1), Ratio(1, 4)],  "        bb" )]
2136        #[case(Flex::SpaceBetween, 10, &[Ratio(0, 1), Ratio(1, 2)],  "     bbbbb" )]
2137        #[case(Flex::SpaceBetween, 10, &[Ratio(0, 1), Ratio(1, 1)],  "bbbbbbbbbb" )]
2138        #[case(Flex::SpaceBetween, 10, &[Ratio(0, 1), Ratio(2, 1)],  "bbbbbbbbbb" )]
2139        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 10), Ratio(0, 1)], "a         " )]
2140        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 10), Ratio(1, 4)], "a       bb" )]
2141        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 10), Ratio(1, 2)], "a    bbbbb" )]
2142        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 10), Ratio(1, 1)], "abbbbbbbbb" )]
2143        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 10), Ratio(2, 1)], "abbbbbbbbb" )]
2144        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 4), Ratio(0, 1)],  "aaa       " )]
2145        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 4), Ratio(1, 4)],  "aaa     bb" )]
2146        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 4), Ratio(1, 2)],  "aaa  bbbbb" )]
2147        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 4), Ratio(1, 1)],  "aaabbbbbbb" )]
2148        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 4), Ratio(2, 1)],  "aaabbbbbbb" )]
2149        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 3), Ratio(0, 1)],  "aaa       " )]
2150        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 3), Ratio(1, 4)],  "aaa     bb" )]
2151        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 3), Ratio(1, 2)],  "aaa  bbbbb" )]
2152        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 3), Ratio(1, 1)],  "aaabbbbbbb" )]
2153        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 3), Ratio(2, 1)],  "aaabbbbbbb" )]
2154        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 2), Ratio(0, 1)],  "aaaaa     " )]
2155        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 2), Ratio(1, 2)],  "aaaaabbbbb" )]
2156        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 2), Ratio(1, 1)],  "aaaaabbbbb" )]
2157        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 1), Ratio(0, 1)],  "aaaaaaaaaa" )]
2158        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 1), Ratio(1, 2)],  "aaaaabbbbb" )]
2159        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 1), Ratio(1, 1)],  "aaaaabbbbb" )]
2160        #[case(Flex::SpaceBetween, 10, &[Ratio(1, 1), Ratio(2, 1)],  "aaaaabbbbb" )]
2161        fn ratio_spacebetween(
2162            #[case] flex: Flex,
2163            #[case] width: u16,
2164            #[case] constraints: &[Constraint],
2165            #[case] expected: &str,
2166        ) {
2167            letters(flex, constraints, width, expected);
2168        }
2169
2170        #[test]
2171        fn vertical_split_by_height() {
2172            let target = Rect {
2173                x: 2,
2174                y: 2,
2175                width: 10,
2176                height: 10,
2177            };
2178
2179            let chunks = Layout::default()
2180                .direction(Direction::Vertical)
2181                .constraints([
2182                    Constraint::Percentage(10),
2183                    Constraint::Max(5),
2184                    Constraint::Min(1),
2185                ])
2186                .split(target);
2187
2188            assert_eq!(chunks.iter().map(|r| r.height).sum::<u16>(), target.height);
2189            chunks.windows(2).for_each(|w| assert!(w[0].y <= w[1].y));
2190        }
2191
2192        #[test]
2193        fn edge_cases() {
2194            // stretches into last
2195            let layout = Layout::default()
2196                .constraints([
2197                    Constraint::Percentage(50),
2198                    Constraint::Percentage(50),
2199                    Constraint::Min(0),
2200                ])
2201                .split(Rect::new(0, 0, 1, 1));
2202            assert_eq!(
2203                layout[..],
2204                [
2205                    Rect::new(0, 0, 1, 1),
2206                    Rect::new(0, 1, 1, 0),
2207                    Rect::new(0, 1, 1, 0)
2208                ]
2209            );
2210
2211            // stretches into last
2212            let layout = Layout::default()
2213                .constraints([
2214                    Constraint::Max(1),
2215                    Constraint::Percentage(99),
2216                    Constraint::Min(0),
2217                ])
2218                .split(Rect::new(0, 0, 1, 1));
2219            assert_eq!(
2220                layout[..],
2221                [
2222                    Rect::new(0, 0, 1, 0),
2223                    Rect::new(0, 0, 1, 1),
2224                    Rect::new(0, 1, 1, 0)
2225                ]
2226            );
2227
2228            // minimal bug from
2229            // https://github.com/ratatui/ratatui/pull/404#issuecomment-1681850644
2230            // TODO: check if this bug is now resolved?
2231            let layout = Layout::default()
2232                .constraints([Min(1), Length(0), Min(1)])
2233                .direction(Direction::Horizontal)
2234                .split(Rect::new(0, 0, 1, 1));
2235            assert_eq!(
2236                layout[..],
2237                [
2238                    Rect::new(0, 0, 1, 1),
2239                    Rect::new(1, 0, 0, 1),
2240                    Rect::new(1, 0, 0, 1),
2241                ]
2242            );
2243
2244            // This stretches the 2nd last length instead of the last min based on ranking
2245            let layout = Layout::default()
2246                .constraints([Length(3), Min(4), Length(1), Min(4)])
2247                .direction(Direction::Horizontal)
2248                .split(Rect::new(0, 0, 7, 1));
2249            assert_eq!(
2250                layout[..],
2251                [
2252                    Rect::new(0, 0, 0, 1),
2253                    Rect::new(0, 0, 4, 1),
2254                    Rect::new(4, 0, 0, 1),
2255                    Rect::new(4, 0, 3, 1),
2256                ]
2257            );
2258        }
2259
2260        #[rstest]
2261        #[case::len_min1(vec![Length(25), Min(100)], vec![0..0,  0..100])]
2262        #[case::len_min2(vec![Length(25), Min(0)], vec![0..25, 25..100])]
2263        #[case::len_max1(vec![Length(25), Max(0)], vec![0..100, 100..100])]
2264        #[case::len_max2(vec![Length(25), Max(100)], vec![0..25, 25..100])]
2265        #[case::len_perc(vec![Length(25), Percentage(25)], vec![0..25, 25..100])]
2266        #[case::perc_len(vec![Percentage(25), Length(25)], vec![0..75, 75..100])]
2267        #[case::len_ratio(vec![Length(25), Ratio(1, 4)], vec![0..25, 25..100])]
2268        #[case::ratio_len(vec![Ratio(1, 4), Length(25)], vec![0..75, 75..100])]
2269        #[case::len_len(vec![Length(25), Length(25)], vec![0..25, 25..100])]
2270        #[case::len1(vec![Length(25), Length(25), Length(25)], vec![0..25, 25..50, 50..100])]
2271        #[case::len2(vec![Length(15), Length(35), Length(25)], vec![0..15, 15..50, 50..100])]
2272        #[case::len3(vec![Length(25), Length(25), Length(25)], vec![0..25, 25..50, 50..100])]
2273        fn constraint_length(
2274            #[case] constraints: Vec<Constraint>,
2275            #[case] expected: Vec<Range<u16>>,
2276        ) {
2277            let rect = Rect::new(0, 0, 100, 1);
2278            let ranges = Layout::horizontal(constraints)
2279                .flex(Flex::Legacy)
2280                .split(rect)
2281                .iter()
2282                .map(|r| r.left()..r.right())
2283                .collect_vec();
2284            assert_eq!(ranges, expected);
2285        }
2286
2287        #[rstest]
2288        #[case(7, vec![Length(4), Length(4)], vec![0..3, 4..7])]
2289        #[case(4, vec![Length(4), Length(4)], vec![0..2, 3..4])]
2290        fn table_length(
2291            #[case] width: u16,
2292            #[case] constraints: Vec<Constraint>,
2293            #[case] expected: Vec<Range<u16>>,
2294        ) {
2295            let rect = Rect::new(0, 0, width, 1);
2296            let ranges = Layout::horizontal(constraints)
2297                .spacing(1)
2298                .flex(Flex::Start)
2299                .split(rect)
2300                .iter()
2301                .map(|r| r.left()..r.right())
2302                .collect::<Vec<Range<u16>>>();
2303            assert_eq!(ranges, expected);
2304        }
2305
2306        #[rstest]
2307        #[case::min_len_max(vec![Min(25), Length(25), Max(25)], vec![0..50, 50..75, 75..100])]
2308        #[case::max_len_min(vec![Max(25), Length(25), Min(25)], vec![0..25, 25..50, 50..100])]
2309        #[case::len_len_len(vec![Length(33), Length(33), Length(33)], vec![0..33, 33..66, 66..100])]
2310        #[case::len_len_len_25(vec![Length(25), Length(25), Length(25)], vec![0..25, 25..50, 50..100])]
2311        #[case::perc_len_ratio(vec![Percentage(25), Length(25), Ratio(1, 4)], vec![0..25, 25..50, 50..100])]
2312        #[case::len_ratio_perc(vec![Length(25), Ratio(1, 4), Percentage(25)], vec![0..25, 25..75, 75..100])]
2313        #[case::ratio_len_perc(vec![Ratio(1, 4), Length(25), Percentage(25)], vec![0..50, 50..75, 75..100])]
2314        #[case::ratio_perc_len(vec![Ratio(1, 4), Percentage(25), Length(25)], vec![0..50, 50..75, 75..100])]
2315        #[case::len_len_min(vec![Length(100), Length(1), Min(20)], vec![0..80, 80..80, 80..100])]
2316        #[case::min_len_len(vec![Min(20), Length(1), Length(100)], vec![0..20, 20..21, 21..100])]
2317        #[case::fill_len_fill(vec![Fill(1), Length(10), Fill(1)], vec![0..45, 45..55, 55..100])]
2318        #[case::fill_len_fill_2(vec![Fill(1), Length(10), Fill(2)], vec![0..30, 30..40, 40..100])]
2319        #[case::fill_len_fill_4(vec![Fill(1), Length(10), Fill(4)], vec![0..18, 18..28, 28..100])]
2320        #[case::fill_len_fill_5(vec![Fill(1), Length(10), Fill(5)], vec![0..15, 15..25, 25..100])]
2321        #[case::len_len_len_25(vec![Length(25), Length(25), Length(25)], vec![0..25, 25..50, 50..100])]
2322        #[case::unstable_test(vec![Length(25), Length(25), Length(25)], vec![0..25, 25..50, 50..100])]
2323        fn length_is_higher_priority(
2324            #[case] constraints: Vec<Constraint>,
2325            #[case] expected: Vec<Range<u16>>,
2326        ) {
2327            let rect = Rect::new(0, 0, 100, 1);
2328            let ranges = Layout::horizontal(constraints)
2329                .flex(Flex::Legacy)
2330                .split(rect)
2331                .iter()
2332                .map(|r| r.left()..r.right())
2333                .collect_vec();
2334            assert_eq!(ranges, expected);
2335        }
2336
2337        #[rstest]
2338        #[case::min_len_max(vec![Min(25), Length(25), Max(25)], vec![50, 25, 25])]
2339        #[case::max_len_min(vec![Max(25), Length(25), Min(25)], vec![25, 25, 50])]
2340        #[case::len_len_len1(vec![Length(33), Length(33), Length(33)], vec![33, 33, 33])]
2341        #[case::len_len_len2(vec![Length(25), Length(25), Length(25)], vec![25, 25, 25])]
2342        #[case::perc_len_ratio(vec![Percentage(25), Length(25), Ratio(1, 4)], vec![25, 25, 25])]
2343        #[case::len_ratio_perc(vec![Length(25), Ratio(1, 4), Percentage(25)], vec![25, 25, 25])]
2344        #[case::ratio_len_perc(vec![Ratio(1, 4), Length(25), Percentage(25)], vec![25, 25, 25])]
2345        #[case::ratio_perc_len(vec![Ratio(1, 4), Percentage(25), Length(25)], vec![25, 25, 25])]
2346        #[case::len_len_min(vec![Length(100), Length(1), Min(20)], vec![79, 1, 20])]
2347        #[case::min_len_len(vec![Min(20), Length(1), Length(100)], vec![20, 1, 79])]
2348        #[case::fill_len_fill1(vec![Fill(1), Length(10), Fill(1)], vec![45, 10, 45])]
2349        #[case::fill_len_fill2(vec![Fill(1), Length(10), Fill(2)], vec![30, 10, 60])]
2350        #[case::fill_len_fill4(vec![Fill(1), Length(10), Fill(4)], vec![18, 10, 72])]
2351        #[case::fill_len_fill5(vec![Fill(1), Length(10), Fill(5)], vec![15, 10, 75])]
2352        #[case::len_len_len3(vec![Length(25), Length(25), Length(25)], vec![25, 25, 25])]
2353        fn length_is_higher_priority_in_flex(
2354            #[case] constraints: Vec<Constraint>,
2355            #[case] expected: Vec<u16>,
2356        ) {
2357            let rect = Rect::new(0, 0, 100, 1);
2358            for flex in [
2359                Flex::Start,
2360                Flex::End,
2361                Flex::Center,
2362                Flex::SpaceAround,
2363                Flex::SpaceEvenly,
2364                Flex::SpaceBetween,
2365            ] {
2366                let widths = Layout::horizontal(&constraints)
2367                    .flex(flex)
2368                    .split(rect)
2369                    .iter()
2370                    .map(|r| r.width)
2371                    .collect_vec();
2372                assert_eq!(widths, expected);
2373            }
2374        }
2375
2376        #[rstest]
2377        #[case::fill_len_fill(vec![Fill(1), Length(10), Fill(2)], vec![0..13, 13..23, 23..50])]
2378        #[case::len_fill_fill(vec![Length(10), Fill(2), Fill(1)], vec![0..10, 10..37, 37..50])] // might be unstable?
2379        fn fixed_with_50_width(
2380            #[case] constraints: Vec<Constraint>,
2381            #[case] expected: Vec<Range<u16>>,
2382        ) {
2383            let rect = Rect::new(0, 0, 50, 1);
2384            let ranges = Layout::horizontal(constraints)
2385                .flex(Flex::Legacy)
2386                .split(rect)
2387                .iter()
2388                .map(|r| r.left()..r.right())
2389                .collect_vec();
2390            assert_eq!(ranges, expected);
2391        }
2392
2393        #[rstest]
2394        #[case::same_fill(vec![Fill(1), Fill(2), Fill(1), Fill(1)], vec![0..20, 20..60, 60..80, 80..100])]
2395        #[case::inc_fill(vec![Fill(1), Fill(2), Fill(3), Fill(4)], vec![0..10, 10..30, 30..60, 60..100])]
2396        #[case::dec_fill(vec![Fill(4), Fill(3), Fill(2), Fill(1)], vec![0..40, 40..70, 70..90, 90..100])]
2397        #[case::rand_fill1(vec![Fill(1), Fill(3), Fill(2), Fill(4)], vec![0..10, 10..40, 40..60, 60..100])]
2398        #[case::rand_fill2(vec![Fill(1), Fill(3), Length(50), Fill(2), Fill(4)], vec![0..5, 5..20, 20..70, 70..80, 80..100])]
2399        #[case::rand_fill3(vec![Fill(1), Fill(3), Percentage(50), Fill(2), Fill(4)], vec![0..5, 5..20, 20..70, 70..80, 80..100])]
2400        #[case::rand_fill4(vec![Fill(1), Fill(3), Min(50), Fill(2), Fill(4)], vec![0..5, 5..20, 20..70, 70..80, 80..100])]
2401        #[case::rand_fill5(vec![Fill(1), Fill(3), Max(50), Fill(2), Fill(4)], vec![0..5, 5..20, 20..70, 70..80, 80..100])]
2402        #[case::zero_fill1(vec![Fill(0), Fill(1), Fill(0)], vec![0..0, 0..100, 100..100])]
2403        #[case::zero_fill2(vec![Fill(0), Length(1), Fill(0)], vec![0..50, 50..51, 51..100])]
2404        #[case::zero_fill3(vec![Fill(0), Percentage(1), Fill(0)], vec![0..50, 50..51, 51..100])]
2405        #[case::zero_fill4(vec![Fill(0), Min(1), Fill(0)], vec![0..50, 50..51, 51..100])]
2406        #[case::zero_fill5(vec![Fill(0), Max(1), Fill(0)], vec![0..50, 50..51, 51..100])]
2407        #[case::zero_fill6(vec![Fill(0), Fill(2), Fill(0), Fill(1)], vec![0..0, 0..67, 67..67, 67..100])]
2408        #[case::space_fill1(vec![Fill(0), Fill(2), Percentage(20)], vec![0..0, 0..80, 80..100])]
2409        #[case::space_fill2(vec![Fill(0), Fill(0), Percentage(20)], vec![0..40, 40..80, 80..100])]
2410        #[case::space_fill3(vec![Fill(0), Ratio(1, 5)], vec![0..80, 80..100])]
2411        #[case::space_fill4(vec![Fill(0), Fill(u16::MAX)], vec![0..0, 0..100])]
2412        #[case::space_fill5(vec![Fill(u16::MAX), Fill(0)], vec![0..100, 100..100])]
2413        #[case::space_fill6(vec![Fill(0), Percentage(20)], vec![0..80, 80..100])]
2414        #[case::space_fill7(vec![Fill(1), Percentage(20)], vec![0..80, 80..100])]
2415        #[case::space_fill8(vec![Fill(u16::MAX), Percentage(20)], vec![0..80, 80..100])]
2416        #[case::space_fill9(vec![Fill(u16::MAX), Fill(0), Percentage(20)], vec![0..80, 80..80, 80..100])]
2417        #[case::space_fill10(vec![Fill(0), Length(20)], vec![0..80, 80..100])]
2418        #[case::space_fill11(vec![Fill(0), Min(20)], vec![0..80, 80..100])]
2419        #[case::space_fill12(vec![Fill(0), Max(20)], vec![0..80, 80..100])]
2420        #[case::fill_collapse1(vec![Fill(1), Fill(1), Fill(1), Min(30), Length(50)], vec![0..7, 7..13, 13..20, 20..50, 50..100])]
2421        #[case::fill_collapse2(vec![Fill(1), Fill(1), Fill(1), Length(50), Length(50)], vec![0..0, 0..0, 0..0, 0..50, 50..100])]
2422        #[case::fill_collapse3(vec![Fill(1), Fill(1), Fill(1), Length(75), Length(50)], vec![0..0, 0..0, 0..0, 0..75, 75..100])]
2423        #[case::fill_collapse4(vec![Fill(1), Fill(1), Fill(1), Min(50), Max(50)], vec![0..0, 0..0, 0..0, 0..50, 50..100])]
2424        #[case::fill_collapse5(vec![Fill(1), Fill(1), Fill(1), Ratio(1, 1)], vec![0..0, 0..0, 0..0, 0..100])]
2425        #[case::fill_collapse6(vec![Fill(1), Fill(1), Fill(1), Percentage(100)], vec![0..0, 0..0, 0..0, 0..100])]
2426        fn fill(#[case] constraints: Vec<Constraint>, #[case] expected: Vec<Range<u16>>) {
2427            let rect = Rect::new(0, 0, 100, 1);
2428            let ranges = Layout::horizontal(constraints)
2429                .flex(Flex::Legacy)
2430                .split(rect)
2431                .iter()
2432                .map(|r| r.left()..r.right())
2433                .collect_vec();
2434            assert_eq!(ranges, expected);
2435        }
2436
2437        #[rstest]
2438        #[case::min_percentage(vec![Min(0), Percentage(20)], vec![0..80, 80..100])]
2439        #[case::max_percentage(vec![Max(0), Percentage(20)], vec![0..0, 0..100])]
2440        fn percentage_parameterized(
2441            #[case] constraints: Vec<Constraint>,
2442            #[case] expected: Vec<Range<u16>>,
2443        ) {
2444            let rect = Rect::new(0, 0, 100, 1);
2445            let ranges = Layout::horizontal(constraints)
2446                .flex(Flex::Legacy)
2447                .split(rect)
2448                .iter()
2449                .map(|r| r.left()..r.right())
2450                .collect_vec();
2451            assert_eq!(ranges, expected);
2452        }
2453
2454        #[rstest]
2455        #[case::max_min(vec![Max(100), Min(0)], vec![0..100, 100..100])]
2456        #[case::min_max(vec![Min(0), Max(100)], vec![0..0, 0..100])]
2457        #[case::length_min(vec![Length(u16::MAX), Min(10)], vec![0..90, 90..100])]
2458        #[case::min_length(vec![Min(10), Length(u16::MAX)], vec![0..10, 10..100])]
2459        #[case::length_max(vec![Length(0), Max(10)], vec![0..90, 90..100])]
2460        #[case::max_length(vec![Max(10), Length(0)], vec![0..10, 10..100])]
2461        fn min_max(#[case] constraints: Vec<Constraint>, #[case] expected: Vec<Range<u16>>) {
2462            let rect = Rect::new(0, 0, 100, 1);
2463            let ranges = Layout::horizontal(constraints)
2464                .flex(Flex::Legacy)
2465                .split(rect)
2466                .iter()
2467                .map(|r| r.left()..r.right())
2468                .collect_vec();
2469            assert_eq!(ranges, expected);
2470        }
2471
2472        #[rstest]
2473        #[case::length_legacy(vec![Length(50)], vec![0..100], Flex::Legacy)]
2474        #[case::length_start(vec![Length(50)], vec![0..50], Flex::Start)]
2475        #[case::length_end(vec![Length(50)], vec![50..100], Flex::End)]
2476        #[case::length_center(vec![Length(50)], vec![25..75], Flex::Center)]
2477        #[case::ratio_legacy(vec![Ratio(1, 2)], vec![0..100], Flex::Legacy)]
2478        #[case::ratio_start(vec![Ratio(1, 2)], vec![0..50], Flex::Start)]
2479        #[case::ratio_end(vec![Ratio(1, 2)], vec![50..100], Flex::End)]
2480        #[case::ratio_center(vec![Ratio(1, 2)], vec![25..75], Flex::Center)]
2481        #[case::percent_legacy(vec![Percentage(50)], vec![0..100], Flex::Legacy)]
2482        #[case::percent_start(vec![Percentage(50)], vec![0..50], Flex::Start)]
2483        #[case::percent_end(vec![Percentage(50)], vec![50..100], Flex::End)]
2484        #[case::percent_center(vec![Percentage(50)], vec![25..75], Flex::Center)]
2485        #[case::min_legacy(vec![Min(50)], vec![0..100], Flex::Legacy)]
2486        #[case::min_start(vec![Min(50)], vec![0..100], Flex::Start)]
2487        #[case::min_end(vec![Min(50)], vec![0..100], Flex::End)]
2488        #[case::min_center(vec![Min(50)], vec![0..100], Flex::Center)]
2489        #[case::max_legacy(vec![Max(50)], vec![0..100], Flex::Legacy)]
2490        #[case::max_start(vec![Max(50)], vec![0..50], Flex::Start)]
2491        #[case::max_end(vec![Max(50)], vec![50..100], Flex::End)]
2492        #[case::max_center(vec![Max(50)], vec![25..75], Flex::Center)]
2493        #[case::spacebetween_becomes_stretch1(vec![Min(1)], vec![0..100], Flex::SpaceBetween)]
2494        #[case::spacebetween_becomes_stretch2(vec![Max(20)], vec![0..100], Flex::SpaceBetween)]
2495        #[case::spacebetween_becomes_stretch3(vec![Length(20)], vec![0..100], Flex::SpaceBetween)]
2496        #[case::length_legacy2(vec![Length(25), Length(25)], vec![0..25, 25..100], Flex::Legacy)]
2497        #[case::length_start2(vec![Length(25), Length(25)], vec![0..25, 25..50], Flex::Start)]
2498        #[case::length_center2(vec![Length(25), Length(25)], vec![25..50, 50..75], Flex::Center)]
2499        #[case::length_end2(vec![Length(25), Length(25)], vec![50..75, 75..100], Flex::End)]
2500        #[case::length_spacebetween(vec![Length(25), Length(25)], vec![0..25, 75..100], Flex::SpaceBetween)]
2501        #[case::length_spaceevenly(vec![Length(25), Length(25)], vec![17..42, 58..83], Flex::SpaceEvenly)]
2502        #[case::length_spacearound(vec![Length(25), Length(25)], vec![13..38, 63..88], Flex::SpaceAround)]
2503        #[case::percentage_legacy(vec![Percentage(25), Percentage(25)], vec![0..25, 25..100], Flex::Legacy)]
2504        #[case::percentage_start(vec![Percentage(25), Percentage(25)], vec![0..25, 25..50], Flex::Start)]
2505        #[case::percentage_center(vec![Percentage(25), Percentage(25)], vec![25..50, 50..75], Flex::Center)]
2506        #[case::percentage_end(vec![Percentage(25), Percentage(25)], vec![50..75, 75..100], Flex::End)]
2507        #[case::percentage_spacebetween(vec![Percentage(25), Percentage(25)], vec![0..25, 75..100], Flex::SpaceBetween)]
2508        #[case::percentage_spaceevenly(vec![Percentage(25), Percentage(25)], vec![17..42, 58..83], Flex::SpaceEvenly)]
2509        #[case::percentage_spacearound(vec![Percentage(25), Percentage(25)], vec![13..38, 63..88], Flex::SpaceAround)]
2510        #[case::min_legacy2(vec![Min(25), Min(25)], vec![0..25, 25..100], Flex::Legacy)]
2511        #[case::min_start2(vec![Min(25), Min(25)], vec![0..50, 50..100], Flex::Start)]
2512        #[case::min_center2(vec![Min(25), Min(25)], vec![0..50, 50..100], Flex::Center)]
2513        #[case::min_end2(vec![Min(25), Min(25)], vec![0..50, 50..100], Flex::End)]
2514        #[case::min_spacebetween(vec![Min(25), Min(25)], vec![0..50, 50..100], Flex::SpaceBetween)]
2515        #[case::min_spaceevenly(vec![Min(25), Min(25)], vec![0..50, 50..100], Flex::SpaceEvenly)]
2516        #[case::min_spacearound(vec![Min(25), Min(25)], vec![0..50, 50..100], Flex::SpaceAround)]
2517        #[case::max_legacy2(vec![Max(25), Max(25)], vec![0..25, 25..100], Flex::Legacy)]
2518        #[case::max_start2(vec![Max(25), Max(25)], vec![0..25, 25..50], Flex::Start)]
2519        #[case::max_center2(vec![Max(25), Max(25)], vec![25..50, 50..75], Flex::Center)]
2520        #[case::max_end2(vec![Max(25), Max(25)], vec![50..75, 75..100], Flex::End)]
2521        #[case::max_spacebetween(vec![Max(25), Max(25)], vec![0..25, 75..100], Flex::SpaceBetween)]
2522        #[case::max_spaceevenly(vec![Max(25), Max(25)], vec![17..42, 58..83], Flex::SpaceEvenly)]
2523        #[case::max_spacearound(vec![Max(25), Max(25)], vec![13..38, 63..88], Flex::SpaceAround)]
2524        #[case::length_spaced_around(vec![Length(25), Length(25), Length(25)], vec![0..25, 38..63, 75..100], Flex::SpaceBetween)]
2525        #[case::one_segment_legacy(vec![Length(50)], vec![0..100], Flex::Legacy)]
2526        #[case::one_segment_start(vec![Length(50)], vec![0..50], Flex::Start)]
2527        #[case::one_segment_end(vec![Length(50)], vec![50..100], Flex::End)]
2528        #[case::one_segment_center(vec![Length(50)], vec![25..75], Flex::Center)]
2529        #[case::one_segment_spacebetween(vec![Length(50)], vec![0..100], Flex::SpaceBetween)]
2530        #[case::one_segment_spaceevenly(vec![Length(50)], vec![25..75], Flex::SpaceEvenly)]
2531        #[case::one_segment_spacearound(vec![Length(50)], vec![25..75], Flex::SpaceAround)]
2532        fn flex_constraint(
2533            #[case] constraints: Vec<Constraint>,
2534            #[case] expected: Vec<Range<u16>>,
2535            #[case] flex: Flex,
2536        ) {
2537            let rect = Rect::new(0, 0, 100, 1);
2538            let ranges = Layout::horizontal(constraints)
2539                .flex(flex)
2540                .split(rect)
2541                .iter()
2542                .map(|r| r.left()..r.right())
2543                .collect_vec();
2544            assert_eq!(ranges, expected);
2545        }
2546
2547        #[rstest]
2548        #[case::length_overlap1(vec![(0  , 20) , (20 , 20) , (40 , 20)] , vec![Length(20) , Length(20) , Length(20)] , Flex::Start        , 0)]
2549        #[case::length_overlap2(vec![(0  , 20) , (19 , 20) , (38 , 20)] , vec![Length(20) , Length(20) , Length(20)] , Flex::Start        , -1)]
2550        #[case::length_overlap3(vec![(21 , 20) , (40 , 20) , (59 , 20)] , vec![Length(20) , Length(20) , Length(20)] , Flex::Center       , -1)]
2551        #[case::length_overlap4(vec![(42 , 20) , (61 , 20) , (80 , 20)] , vec![Length(20) , Length(20) , Length(20)] , Flex::End          , -1)]
2552        #[case::length_overlap5(vec![(0  , 20) , (19 , 20) , (38 , 62)] , vec![Length(20) , Length(20) , Length(20)] , Flex::Legacy       , -1)]
2553        #[case::length_overlap6(vec![(0  , 20) , (40 , 20) , (80 , 20)] , vec![Length(20) , Length(20) , Length(20)] , Flex::SpaceBetween , -1)]
2554        #[case::length_overlap7(vec![(10 , 20) , (40 , 20) , (70 , 20)] , vec![Length(20) , Length(20) , Length(20)] , Flex::SpaceEvenly  , -1)]
2555        #[case::length_overlap7(vec![(7  , 20) , (40 , 20) , (73 , 20)] , vec![Length(20) , Length(20) , Length(20)] , Flex::SpaceAround  , -1)]
2556        fn flex_overlap(
2557            #[case] expected: Vec<(u16, u16)>,
2558            #[case] constraints: Vec<Constraint>,
2559            #[case] flex: Flex,
2560            #[case] spacing: i16,
2561        ) {
2562            let rect = Rect::new(0, 0, 100, 1);
2563            let r = Layout::horizontal(constraints)
2564                .flex(flex)
2565                .spacing(spacing)
2566                .split(rect);
2567            let result = r
2568                .iter()
2569                .map(|r| (r.x, r.width))
2570                .collect::<Vec<(u16, u16)>>();
2571
2572            assert_eq!(result, expected);
2573        }
2574
2575        #[rstest]
2576        #[case::length_spacing(vec![(0 , 20), (20, 20) , (40, 20)], vec![Length(20), Length(20), Length(20)], Flex::Start      , 0)]
2577        #[case::length_spacing(vec![(0 , 20), (22, 20) , (44, 20)], vec![Length(20), Length(20), Length(20)], Flex::Start      , 2)]
2578        #[case::length_spacing(vec![(18, 20), (40, 20) , (62, 20)], vec![Length(20), Length(20), Length(20)], Flex::Center     , 2)]
2579        #[case::length_spacing(vec![(36, 20), (58, 20) , (80, 20)], vec![Length(20), Length(20), Length(20)], Flex::End        , 2)]
2580        #[case::length_spacing(vec![(0 , 20), (22, 20) , (44, 56)], vec![Length(20), Length(20), Length(20)], Flex::Legacy     , 2)]
2581        #[case::length_spacing(vec![(0 , 20), (40, 20) , (80, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceBetween, 2)]
2582        #[case::length_spacing(vec![(10, 20), (40, 20) , (70, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceEvenly, 2)]
2583        #[case::length_spacing(vec![(7, 20), (40, 20) , (73, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceAround, 2)]
2584        fn flex_spacing(
2585            #[case] expected: Vec<(u16, u16)>,
2586            #[case] constraints: Vec<Constraint>,
2587            #[case] flex: Flex,
2588            #[case] spacing: i16,
2589        ) {
2590            let rect = Rect::new(0, 0, 100, 1);
2591            let r = Layout::horizontal(constraints)
2592                .flex(flex)
2593                .spacing(spacing)
2594                .split(rect);
2595            let result = r
2596                .iter()
2597                .map(|r| (r.x, r.width))
2598                .collect::<Vec<(u16, u16)>>();
2599            assert_eq!(result, expected);
2600        }
2601
2602        #[rstest]
2603        #[case::a(vec![(0, 25), (25, 75)], vec![Length(25), Length(25)])]
2604        #[case::b(vec![(0, 25), (25, 75)], vec![Length(25), Percentage(25)])]
2605        #[case::c(vec![(0, 75), (75, 25)], vec![Percentage(25), Length(25)])]
2606        #[case::d(vec![(0, 75), (75, 25)], vec![Min(25), Percentage(25)])]
2607        #[case::e(vec![(0, 25), (25, 75)], vec![Percentage(25), Min(25)])]
2608        #[case::f(vec![(0, 25), (25, 75)], vec![Min(25), Percentage(100)])]
2609        #[case::g(vec![(0, 75), (75, 25)], vec![Percentage(100), Min(25)])]
2610        #[case::h(vec![(0, 25), (25, 75)], vec![Max(75), Percentage(75)])]
2611        #[case::i(vec![(0, 75), (75, 25)], vec![Percentage(75), Max(75)])]
2612        #[case::j(vec![(0, 25), (25, 75)], vec![Max(25), Percentage(25)])]
2613        #[case::k(vec![(0, 75), (75, 25)], vec![Percentage(25), Max(25)])]
2614        #[case::l(vec![(0, 25), (25, 75)], vec![Length(25), Ratio(1, 4)])]
2615        #[case::m(vec![(0, 75), (75, 25)], vec![Ratio(1, 4), Length(25)])]
2616        #[case::n(vec![(0, 25), (25, 75)], vec![Percentage(25), Ratio(1, 4)])]
2617        #[case::o(vec![(0, 75), (75, 25)], vec![Ratio(1, 4), Percentage(25)])]
2618        #[case::p(vec![(0, 25), (25, 75)], vec![Ratio(1, 4), Fill(25)])]
2619        #[case::q(vec![(0, 75), (75, 25)], vec![Fill(25), Ratio(1, 4)])]
2620        fn constraint_specification_tests_for_priority(
2621            #[case] expected: Vec<(u16, u16)>,
2622            #[case] constraints: Vec<Constraint>,
2623        ) {
2624            let rect = Rect::new(0, 0, 100, 1);
2625            let r = Layout::horizontal(constraints)
2626                .flex(Flex::Legacy)
2627                .split(rect)
2628                .iter()
2629                .map(|r| (r.x, r.width))
2630                .collect::<Vec<(u16, u16)>>();
2631            assert_eq!(r, expected);
2632        }
2633
2634        #[rstest]
2635        #[case::a(vec![(0, 20), (20, 20), (40, 20)], vec![Length(20), Length(20), Length(20)], Flex::Start, 0)]
2636        #[case::b(vec![(18, 20), (40, 20), (62, 20)], vec![Length(20), Length(20), Length(20)], Flex::Center, 2)]
2637        #[case::c(vec![(36, 20), (58, 20), (80, 20)], vec![Length(20), Length(20), Length(20)], Flex::End, 2)]
2638        #[case::d(vec![(0, 20), (22, 20), (44, 56)], vec![Length(20), Length(20), Length(20)], Flex::Legacy, 2)]
2639        #[case::e(vec![(0, 20), (22, 20), (44, 56)], vec![Length(20), Length(20), Length(20)], Flex::Legacy, 2)]
2640        #[case::f(vec![(10, 20), (40, 20), (70, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceEvenly, 2)]
2641        #[case::f(vec![(7, 20), (40, 20), (73, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceAround, 2)]
2642        fn constraint_specification_tests_for_priority_with_spacing(
2643            #[case] expected: Vec<(u16, u16)>,
2644            #[case] constraints: Vec<Constraint>,
2645            #[case] flex: Flex,
2646            #[case] spacing: i16,
2647        ) {
2648            let rect = Rect::new(0, 0, 100, 1);
2649            let r = Layout::horizontal(constraints)
2650                .spacing(spacing)
2651                .flex(flex)
2652                .split(rect)
2653                .iter()
2654                .map(|r| (r.x, r.width))
2655                .collect::<Vec<(u16, u16)>>();
2656            assert_eq!(r, expected);
2657        }
2658
2659        #[rstest]
2660        #[case::prop(vec![(0 , 10), (10, 80), (90 , 10)] , vec![Length(10), Fill(1), Length(10)], Flex::Legacy)]
2661        #[case::flex(vec![(0 , 10), (90 , 10)] , vec![Length(10), Length(10)], Flex::SpaceBetween)]
2662        #[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)]
2663        #[case::flex(vec![(27 , 10), (63, 10)] , vec![Length(10), Length(10)], Flex::SpaceEvenly)]
2664        #[case::prop(vec![(0 , 10), (10, 10), (20 , 80)] , vec![Length(10), Length(10), Fill(1)], Flex::Legacy)]
2665        #[case::flex(vec![(0 , 10), (10, 10)] , vec![Length(10), Length(10)], Flex::Start)]
2666        #[case::prop(vec![(0 , 80), (80 , 10), (90, 10)] , vec![Fill(1), Length(10), Length(10)], Flex::Legacy)]
2667        #[case::flex(vec![(80 , 10), (90, 10)] , vec![Length(10), Length(10)], Flex::End)]
2668        #[case::prop(vec![(0 , 40), (40, 10), (50, 10), (60, 40)] , vec![Fill(1), Length(10), Length(10), Fill(1)], Flex::Legacy)]
2669        #[case::flex(vec![(40 , 10), (50, 10)] , vec![Length(10), Length(10)], Flex::Center)]
2670        #[case::flex(vec![(20 , 10), (70, 10)] , vec![Length(10), Length(10)], Flex::SpaceAround)]
2671        fn fill_vs_flex(
2672            #[case] expected: Vec<(u16, u16)>,
2673            #[case] constraints: Vec<Constraint>,
2674            #[case] flex: Flex,
2675        ) {
2676            let rect = Rect::new(0, 0, 100, 1);
2677            let r = Layout::horizontal(constraints).flex(flex).split(rect);
2678            let result = r
2679                .iter()
2680                .map(|r| (r.x, r.width))
2681                .collect::<Vec<(u16, u16)>>();
2682            assert_eq!(result, expected);
2683        }
2684
2685        #[rstest]
2686        #[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Legacy , 0)]
2687        #[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceEvenly , 0)]
2688        #[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceBetween , 0)]
2689        #[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceAround , 0)]
2690        #[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Start , 0)]
2691        #[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Center , 0)]
2692        #[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::End , 0)]
2693        #[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::Legacy , 10)]
2694        #[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::Start , 10)]
2695        #[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::Center , 10)]
2696        #[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::End , 10)]
2697        #[case::flex10(vec![(10 , 35), (55 , 35)] , vec![Fill(1), Fill(1)], Flex::SpaceEvenly , 10)]
2698        #[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::SpaceBetween , 10)]
2699        #[case::flex10(vec![(10 , 30), (60 , 30)] , vec![Fill(1), Fill(1)], Flex::SpaceAround , 10)]
2700        #[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::Legacy , 0)]
2701        #[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceEvenly , 0)]
2702        #[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceBetween , 0)]
2703        #[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceAround , 0)]
2704        #[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::Start , 0)]
2705        #[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::Center , 0)]
2706        #[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::End , 0)]
2707        #[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::Legacy , 10)]
2708        #[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::Start , 10)]
2709        #[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::Center , 10)]
2710        #[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::End , 10)]
2711        #[case::flex_length10(vec![(10 , 25), (45, 10), (65 , 25)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceEvenly , 10)]
2712        #[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceBetween , 10)]
2713        #[case::flex_length10(vec![(10 , 15), (45, 10), (75 , 15)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceAround , 10)]
2714        fn fill_spacing(
2715            #[case] expected: Vec<(u16, u16)>,
2716            #[case] constraints: Vec<Constraint>,
2717            #[case] flex: Flex,
2718            #[case] spacing: i16,
2719        ) {
2720            let rect = Rect::new(0, 0, 100, 1);
2721            let r = Layout::horizontal(constraints)
2722                .flex(flex)
2723                .spacing(spacing)
2724                .split(rect);
2725            let result = r
2726                .iter()
2727                .map(|r| (r.x, r.width))
2728                .collect::<Vec<(u16, u16)>>();
2729            assert_eq!(expected, result);
2730        }
2731
2732        #[rstest]
2733        #[case::flex0_1(vec![(0 , 55), (45 , 55)] , vec![Fill(1), Fill(1)], Flex::Legacy , -10)]
2734        #[case::flex0_2(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceAround , -10)]
2735        #[case::flex0_3(vec![(0 , 55), (45 , 55)] , vec![Fill(1), Fill(1)], Flex::SpaceBetween , -10)]
2736        #[case::flex0_4(vec![(0 , 55), (45 , 55)] , vec![Fill(1), Fill(1)], Flex::Start , -10)]
2737        #[case::flex0_5(vec![(0 , 55), (45 , 55)] , vec![Fill(1), Fill(1)], Flex::Center , -10)]
2738        #[case::flex0_6(vec![(0 , 55), (45 , 55)] , vec![Fill(1), Fill(1)], Flex::End , -10)]
2739        #[case::flex0_7(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceEvenly , -10)]
2740        #[case::flex10_1(vec![(0 , 51), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Legacy , -1)]
2741        #[case::flex10_2(vec![(0 , 51), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Start , -1)]
2742        #[case::flex10_3(vec![(0 , 51), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Center , -1)]
2743        #[case::flex10_4(vec![(0 , 51), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::End , -1)]
2744        #[case::flex10_5(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceAround , -1)]
2745        #[case::flex10_6(vec![(0 , 51), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceBetween , -1)]
2746        #[case::flex10_7(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceEvenly , -1)]
2747        #[case::flex_length0_1(vec![(0 , 55), (45, 10), (45 , 55)] , vec![Fill(1), Length(10), Fill(1)], Flex::Legacy , -10)]
2748        #[case::flex_length0_2(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceAround , -10)]
2749        #[case::flex_length0_3(vec![(0 , 55), (45, 10), (45 , 55)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceBetween , -10)]
2750        #[case::flex_length0_4(vec![(0 , 55), (45, 10), (45 , 55)] , vec![Fill(1), Length(10), Fill(1)], Flex::Start , -10)]
2751        #[case::flex_length0_5(vec![(0 , 55), (45, 10), (45 , 55)] , vec![Fill(1), Length(10), Fill(1)], Flex::Center , -10)]
2752        #[case::flex_length0_6(vec![(0 , 55), (45, 10), (45 , 55)] , vec![Fill(1), Length(10), Fill(1)], Flex::End , -10)]
2753        #[case::flex_length0_7(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceEvenly , -10)]
2754        #[case::flex_length10_1(vec![(0 , 46), (45, 10), (54 , 46)] , vec![Fill(1), Length(10), Fill(1)], Flex::Legacy , -1)]
2755        #[case::flex_length10_2(vec![(0 , 46), (45, 10), (54 , 46)] , vec![Fill(1), Length(10), Fill(1)], Flex::Start , -1)]
2756        #[case::flex_length10_3(vec![(0 , 46), (45, 10), (54 , 46)] , vec![Fill(1), Length(10), Fill(1)], Flex::Center , -1)]
2757        #[case::flex_length10_4(vec![(0 , 46), (45, 10), (54 , 46)] , vec![Fill(1), Length(10), Fill(1)], Flex::End , -1)]
2758        #[case::flex_length10_5(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceAround , -1)]
2759        #[case::flex_length10_6(vec![(0 , 46), (45, 10), (54 , 46)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceBetween , -1)]
2760        #[case::flex_length10_7(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceEvenly , -1)]
2761        fn fill_overlap(
2762            #[case] expected: Vec<(u16, u16)>,
2763            #[case] constraints: Vec<Constraint>,
2764            #[case] flex: Flex,
2765            #[case] spacing: i16,
2766        ) {
2767            let rect = Rect::new(0, 0, 100, 1);
2768            let r = Layout::horizontal(constraints)
2769                .flex(flex)
2770                .spacing(spacing)
2771                .split(rect);
2772            let result = r
2773                .iter()
2774                .map(|r| (r.x, r.width))
2775                .collect::<Vec<(u16, u16)>>();
2776            assert_eq!(result, expected);
2777        }
2778
2779        #[rstest]
2780        #[case::flex_length10(vec![(0, 10), (90, 10)], vec![Length(10), Length(10)], Flex::Center, 80)]
2781        fn flex_spacing_lower_priority_than_user_spacing(
2782            #[case] expected: Vec<(u16, u16)>,
2783            #[case] constraints: Vec<Constraint>,
2784            #[case] flex: Flex,
2785            #[case] spacing: i16,
2786        ) {
2787            let rect = Rect::new(0, 0, 100, 1);
2788            let r = Layout::horizontal(constraints)
2789                .flex(flex)
2790                .spacing(spacing)
2791                .split(rect);
2792            let result = r
2793                .iter()
2794                .map(|r| (r.x, r.width))
2795                .collect::<Vec<(u16, u16)>>();
2796            assert_eq!(result, expected);
2797        }
2798
2799        #[rstest]
2800        #[case::spacers(vec![(0, 0), (10, 0), (100, 0)], vec![Length(10), Length(10)], Flex::Legacy)]
2801        #[case::spacers(vec![(0, 0), (10, 80), (100, 0)], vec![Length(10), Length(10)], Flex::SpaceBetween)]
2802        #[case::spacers(vec![(0, 27), (37, 26), (73, 27)], vec![Length(10), Length(10)], Flex::SpaceEvenly)]
2803        #[case::spacers(vec![(0, 20), (30, 40), (80, 20)], vec![Length(10), Length(10)], Flex::SpaceAround)]
2804        #[case::spacers(vec![(0, 0), (10, 0), (20, 80)], vec![Length(10), Length(10)], Flex::Start)]
2805        #[case::spacers(vec![(0, 40), (50, 0), (60, 40)], vec![Length(10), Length(10)], Flex::Center)]
2806        #[case::spacers(vec![(0, 80), (90, 0), (100, 0)], vec![Length(10), Length(10)], Flex::End)]
2807        fn split_with_spacers_no_spacing(
2808            #[case] expected: Vec<(u16, u16)>,
2809            #[case] constraints: Vec<Constraint>,
2810            #[case] flex: Flex,
2811        ) {
2812            let rect = Rect::new(0, 0, 100, 1);
2813            let (_, s) = Layout::horizontal(&constraints)
2814                .flex(flex)
2815                .split_with_spacers(rect);
2816            assert_eq!(s.len(), constraints.len() + 1);
2817            let result = s
2818                .iter()
2819                .map(|r| (r.x, r.width))
2820                .collect::<Vec<(u16, u16)>>();
2821            assert_eq!(result, expected);
2822        }
2823
2824        #[rstest]
2825        #[case::spacers(vec![(0, 0), (10, 5), (100, 0)], vec![Length(10), Length(10)], Flex::Legacy, 5)]
2826        #[case::spacers(vec![(0, 0), (10, 80), (100, 0)], vec![Length(10), Length(10)], Flex::SpaceBetween, 5)]
2827        #[case::spacers(vec![(0, 27), (37, 26), (73, 27)], vec![Length(10), Length(10)], Flex::SpaceEvenly, 5)]
2828        #[case::spacers(vec![(0, 20), (30, 40), (80, 20)], vec![Length(10), Length(10)], Flex::SpaceAround, 5)]
2829        #[case::spacers(vec![(0, 0), (10, 5), (25, 75)], vec![Length(10), Length(10)], Flex::Start, 5)]
2830        #[case::spacers(vec![(0, 38), (48, 5), (63, 37)], vec![Length(10), Length(10)], Flex::Center, 5)]
2831        #[case::spacers(vec![(0, 75), (85, 5), (100, 0)], vec![Length(10), Length(10)], Flex::End, 5)]
2832        fn split_with_spacers_and_spacing(
2833            #[case] expected: Vec<(u16, u16)>,
2834            #[case] constraints: Vec<Constraint>,
2835            #[case] flex: Flex,
2836            #[case] spacing: i16,
2837        ) {
2838            let rect = Rect::new(0, 0, 100, 1);
2839            let (_, s) = Layout::horizontal(&constraints)
2840                .flex(flex)
2841                .spacing(spacing)
2842                .split_with_spacers(rect);
2843            assert_eq!(s.len(), constraints.len() + 1);
2844            let result = s
2845                .iter()
2846                .map(|r| (r.x, r.width))
2847                .collect::<Vec<(u16, u16)>>();
2848            assert_eq!(expected, result);
2849        }
2850
2851        #[rstest]
2852        #[case::spacers_1(vec![(0, 0), (10, 0), (100, 0)], vec![Length(10), Length(10)], Flex::Legacy, -1)]
2853        #[case::spacers_2(vec![(0, 0), (10, 80), (100, 0)], vec![Length(10), Length(10)], Flex::SpaceBetween, -1)]
2854        #[case::spacers_3(vec![(0, 27), (37, 26), (73, 27)], vec![Length(10), Length(10)], Flex::SpaceEvenly, -1)]
2855        #[case::spacers_3(vec![(0, 20), (30, 40), (80, 20)], vec![Length(10), Length(10)], Flex::SpaceAround, -1)]
2856        #[case::spacers_4(vec![(0, 0), (10, 0), (19, 81)], vec![Length(10), Length(10)], Flex::Start, -1)]
2857        #[case::spacers_5(vec![(0, 41), (51, 0), (60, 40)], vec![Length(10), Length(10)], Flex::Center, -1)]
2858        #[case::spacers_6(vec![(0, 81), (91, 0), (100, 0)], vec![Length(10), Length(10)], Flex::End, -1)]
2859        fn split_with_spacers_and_overlap(
2860            #[case] expected: Vec<(u16, u16)>,
2861            #[case] constraints: Vec<Constraint>,
2862            #[case] flex: Flex,
2863            #[case] spacing: i16,
2864        ) {
2865            let rect = Rect::new(0, 0, 100, 1);
2866            let (_, s) = Layout::horizontal(&constraints)
2867                .flex(flex)
2868                .spacing(spacing)
2869                .split_with_spacers(rect);
2870            assert_eq!(s.len(), constraints.len() + 1);
2871            let result = s
2872                .iter()
2873                .map(|r| (r.x, r.width))
2874                .collect::<Vec<(u16, u16)>>();
2875            assert_eq!(result, expected);
2876        }
2877
2878        #[rstest]
2879        #[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::Legacy, 200)]
2880        #[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::SpaceBetween, 200)]
2881        #[case::spacers(vec![(0, 33), (33, 34), (67, 33)], vec![Length(10), Length(10)], Flex::SpaceEvenly, 200)]
2882        #[case::spacers(vec![(0, 25), (25, 50), (75, 25)], vec![Length(10), Length(10)], Flex::SpaceAround, 200)]
2883        #[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::Start, 200)]
2884        #[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::Center, 200)]
2885        #[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::End, 200)]
2886        fn split_with_spacers_and_too_much_spacing(
2887            #[case] expected: Vec<(u16, u16)>,
2888            #[case] constraints: Vec<Constraint>,
2889            #[case] flex: Flex,
2890            #[case] spacing: i16,
2891        ) {
2892            let rect = Rect::new(0, 0, 100, 1);
2893            let (_, s) = Layout::horizontal(&constraints)
2894                .flex(flex)
2895                .spacing(spacing)
2896                .split_with_spacers(rect);
2897            assert_eq!(s.len(), constraints.len() + 1);
2898            let result = s
2899                .iter()
2900                .map(|r| (r.x, r.width))
2901                .collect::<Vec<(u16, u16)>>();
2902            assert_eq!(result, expected);
2903        }
2904
2905        #[rstest]
2906        #[case::compare(vec![(0, 90), (90, 10)], vec![Min(10), Length(10)], Flex::Legacy)]
2907        #[case::compare(vec![(0, 90), (90, 10)], vec![Min(10), Length(10)], Flex::Start)]
2908        #[case::compare(vec![(0, 10), (10, 90)], vec![Min(10), Percentage(100)], Flex::Legacy)]
2909        #[case::compare(vec![(0, 10), (10, 90)], vec![Min(10), Percentage(100)], Flex::Start)]
2910        #[case::compare(vec![(0, 50), (50, 50)], vec![Percentage(50), Percentage(50)], Flex::Legacy)]
2911        #[case::compare(vec![(0, 50), (50, 50)], vec![Percentage(50), Percentage(50)], Flex::Start)]
2912        fn legacy_vs_default(
2913            #[case] expected: Vec<(u16, u16)>,
2914            #[case] constraints: Vec<Constraint>,
2915            #[case] flex: Flex,
2916        ) {
2917            let rect = Rect::new(0, 0, 100, 1);
2918            let r = Layout::horizontal(constraints).flex(flex).split(rect);
2919            let result = r
2920                .iter()
2921                .map(|r| (r.x, r.width))
2922                .collect::<Vec<(u16, u16)>>();
2923            assert_eq!(result, expected);
2924        }
2925    }
2926}