ratatui_core/layout/
layout.rs

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