ratatui/layout/
layout.rs

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