ratatui_core/layout/
constraint.rs

1use alloc::vec::Vec;
2use core::fmt;
3
4use strum::EnumIs;
5
6/// A constraint that defines the size of a layout element.
7///
8/// Constraints are the core mechanism for defining how space should be allocated within a
9/// [`Layout`](crate::layout::Layout). They can specify fixed sizes (length), proportional sizes
10/// (percentage, ratio), size limits (min, max), or proportional fill values for layout elements.
11/// Relative constraints (percentage, ratio) are calculated relative to the entire space being
12/// divided, rather than the space available after applying more fixed constraints (min, max,
13/// length).
14///
15/// Constraints are prioritized in the following order:
16///
17/// 1. [`Constraint::Min`]
18/// 2. [`Constraint::Max`]
19/// 3. [`Constraint::Length`]
20/// 4. [`Constraint::Percentage`]
21/// 5. [`Constraint::Ratio`]
22/// 6. [`Constraint::Fill`]
23///
24/// # Size Calculation
25///
26/// - [`apply`](Self::apply) - Apply the constraint to a length and return the resulting size
27///
28/// # Collection Creation
29///
30/// - [`from_lengths`](Self::from_lengths) - Create a collection of length constraints
31/// - [`from_ratios`](Self::from_ratios) - Create a collection of ratio constraints
32/// - [`from_percentages`](Self::from_percentages) - Create a collection of percentage constraints
33/// - [`from_maxes`](Self::from_maxes) - Create a collection of maximum constraints
34/// - [`from_mins`](Self::from_mins) - Create a collection of minimum constraints
35/// - [`from_fills`](Self::from_fills) - Create a collection of fill constraints
36///
37/// # Conversion and Construction
38///
39/// - [`from(u16)`](Self::from) - Create a [`Length`](Self::Length) constraint from `u16`
40/// - [`from(&Constraint)`](Self::from) - Create from `&Constraint` (copy)
41/// - [`as_ref()`](Self::as_ref) - Get a reference to self
42/// - [`default()`](Self::default) - Create default constraint
43///   ([`Percentage(100)`](Self::Percentage))
44///
45/// # Examples
46///
47/// `Constraint` provides helper methods to create lists of constraints from various input formats.
48///
49/// ```rust
50/// use ratatui_core::layout::Constraint;
51///
52/// // Create a layout with specified lengths for each element
53/// let constraints = Constraint::from_lengths([10, 20, 10]);
54///
55/// // Create a centered layout using ratio or percentage constraints
56/// let constraints = Constraint::from_ratios([(1, 4), (1, 2), (1, 4)]);
57/// let constraints = Constraint::from_percentages([25, 50, 25]);
58///
59/// // Create a centered layout with a minimum size constraint for specific elements
60/// let constraints = Constraint::from_mins([0, 100, 0]);
61///
62/// // Create a sidebar layout specifying maximum sizes for the columns
63/// let constraints = Constraint::from_maxes([30, 170]);
64///
65/// // Create a layout with fill proportional sizes for each element
66/// let constraints = Constraint::from_fills([1, 2, 1]);
67/// ```
68///
69/// For comprehensive layout documentation and examples, see the [`layout`](crate::layout) module.
70#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumIs)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72pub enum Constraint {
73    /// Applies a minimum size constraint to the element
74    ///
75    /// The element size is set to at least the specified amount.
76    ///
77    /// # Examples
78    ///
79    /// `[Percentage(100), Min(20)]`
80    ///
81    /// ```plain
82    /// ┌────────────────────────────┐┌──────────────────┐
83    /// │            30 px           ││       20 px      │
84    /// └────────────────────────────┘└──────────────────┘
85    /// ```
86    ///
87    /// `[Percentage(100), Min(10)]`
88    ///
89    /// ```plain
90    /// ┌──────────────────────────────────────┐┌────────┐
91    /// │                 40 px                ││  10 px │
92    /// └──────────────────────────────────────┘└────────┘
93    /// ```
94    Min(u16),
95
96    /// Applies a maximum size constraint to the element
97    ///
98    /// The element size is set to at most the specified amount.
99    ///
100    /// # Examples
101    ///
102    /// `[Percentage(0), Max(20)]`
103    ///
104    /// ```plain
105    /// ┌────────────────────────────┐┌──────────────────┐
106    /// │            30 px           ││       20 px      │
107    /// └────────────────────────────┘└──────────────────┘
108    /// ```
109    ///
110    /// `[Percentage(0), Max(10)]`
111    ///
112    /// ```plain
113    /// ┌──────────────────────────────────────┐┌────────┐
114    /// │                 40 px                ││  10 px │
115    /// └──────────────────────────────────────┘└────────┘
116    /// ```
117    Max(u16),
118
119    /// Applies a length constraint to the element
120    ///
121    /// The element size is set to the specified amount.
122    ///
123    /// # Examples
124    ///
125    /// `[Length(20), Length(20)]`
126    ///
127    /// ```plain
128    /// ┌──────────────────┐┌──────────────────┐
129    /// │       20 px      ││       20 px      │
130    /// └──────────────────┘└──────────────────┘
131    /// ```
132    ///
133    /// `[Length(20), Length(30)]`
134    ///
135    /// ```plain
136    /// ┌──────────────────┐┌────────────────────────────┐
137    /// │       20 px      ││            30 px           │
138    /// └──────────────────┘└────────────────────────────┘
139    /// ```
140    Length(u16),
141
142    /// Applies a percentage of the available space to the element
143    ///
144    /// Converts the given percentage to a floating-point value and multiplies that with area. This
145    /// value is rounded back to a integer as part of the layout split calculation.
146    ///
147    /// **Note**: As this value only accepts a `u16`, certain percentages that cannot be
148    /// represented exactly (e.g. 1/3) are not possible. You might want to use
149    /// [`Constraint::Ratio`] or [`Constraint::Fill`] in such cases.
150    ///
151    /// # Examples
152    ///
153    /// `[Percentage(75), Fill(1)]`
154    ///
155    /// ```plain
156    /// ┌────────────────────────────────────┐┌──────────┐
157    /// │                38 px               ││   12 px  │
158    /// └────────────────────────────────────┘└──────────┘
159    /// ```
160    ///
161    /// `[Percentage(50), Fill(1)]`
162    ///
163    /// ```plain
164    /// ┌───────────────────────┐┌───────────────────────┐
165    /// │         25 px         ││         25 px         │
166    /// └───────────────────────┘└───────────────────────┘
167    /// ```
168    Percentage(u16),
169
170    /// Applies a ratio of the available space to the element
171    ///
172    /// Converts the given ratio to a floating-point value and multiplies that with area.
173    /// This value is rounded back to a integer as part of the layout split calculation.
174    ///
175    /// # Examples
176    ///
177    /// `[Ratio(1, 2) ; 2]`
178    ///
179    /// ```plain
180    /// ┌───────────────────────┐┌───────────────────────┐
181    /// │         25 px         ││         25 px         │
182    /// └───────────────────────┘└───────────────────────┘
183    /// ```
184    ///
185    /// `[Ratio(1, 4) ; 4]`
186    ///
187    /// ```plain
188    /// ┌───────────┐┌──────────┐┌───────────┐┌──────────┐
189    /// │   13 px   ││   12 px  ││   13 px   ││   12 px  │
190    /// └───────────┘└──────────┘└───────────┘└──────────┘
191    /// ```
192    Ratio(u32, u32),
193
194    /// Applies the scaling factor proportional to all other [`Constraint::Fill`] elements
195    /// to fill excess space
196    ///
197    /// The element will only expand or fill into excess available space, proportionally matching
198    /// other [`Constraint::Fill`] elements while satisfying all other constraints.
199    ///
200    /// # Examples
201    ///
202    ///
203    /// `[Fill(1), Fill(2), Fill(3)]`
204    ///
205    /// ```plain
206    /// ┌──────┐┌───────────────┐┌───────────────────────┐
207    /// │ 8 px ││     17 px     ││         25 px         │
208    /// └──────┘└───────────────┘└───────────────────────┘
209    /// ```
210    ///
211    /// `[Fill(1), Percentage(50), Fill(1)]`
212    ///
213    /// ```plain
214    /// ┌───────────┐┌───────────────────────┐┌──────────┐
215    /// │   13 px   ││         25 px         ││   12 px  │
216    /// └───────────┘└───────────────────────┘└──────────┘
217    /// ```
218    Fill(u16),
219}
220
221impl Constraint {
222    #[deprecated(
223        since = "0.26.0",
224        note = "This field will be hidden in the next minor version."
225    )]
226    pub fn apply(&self, length: u16) -> u16 {
227        match *self {
228            Self::Percentage(p) => {
229                let p = f32::from(p) / 100.0;
230                let length = f32::from(length);
231                (p * length).min(length) as u16
232            }
233            Self::Ratio(numerator, denominator) => {
234                // avoid division by zero by using 1 when denominator is 0
235                // this results in 0/0 -> 0 and x/0 -> x for x != 0
236                let percentage = numerator as f32 / denominator.max(1) as f32;
237                let length = f32::from(length);
238                (percentage * length).min(length) as u16
239            }
240            Self::Length(l) | Self::Fill(l) => length.min(l),
241            Self::Max(m) => length.min(m),
242            Self::Min(m) => length.max(m),
243        }
244    }
245
246    /// Convert an iterator of lengths into a vector of constraints
247    ///
248    /// # Examples
249    ///
250    /// ```rust
251    /// use ratatui_core::layout::{Constraint, Layout, Rect};
252    ///
253    /// # let area = Rect::default();
254    /// let constraints = Constraint::from_lengths([1, 2, 3]);
255    /// let layout = Layout::default().constraints(constraints).split(area);
256    /// ```
257    pub fn from_lengths<T>(lengths: T) -> Vec<Self>
258    where
259        T: IntoIterator<Item = u16>,
260    {
261        lengths.into_iter().map(Self::Length).collect()
262    }
263
264    /// Convert an iterator of ratios into a vector of constraints
265    ///
266    /// # Examples
267    ///
268    /// ```rust
269    /// use ratatui_core::layout::{Constraint, Layout, Rect};
270    ///
271    /// # let area = Rect::default();
272    /// let constraints = Constraint::from_ratios([(1, 4), (1, 2), (1, 4)]);
273    /// let layout = Layout::default().constraints(constraints).split(area);
274    /// ```
275    pub fn from_ratios<T>(ratios: T) -> Vec<Self>
276    where
277        T: IntoIterator<Item = (u32, u32)>,
278    {
279        ratios.into_iter().map(|(n, d)| Self::Ratio(n, d)).collect()
280    }
281
282    /// Convert an iterator of percentages into a vector of constraints
283    ///
284    /// # Examples
285    ///
286    /// ```rust
287    /// use ratatui_core::layout::{Constraint, Layout, Rect};
288    ///
289    /// # let area = Rect::default();
290    /// let constraints = Constraint::from_percentages([25, 50, 25]);
291    /// let layout = Layout::default().constraints(constraints).split(area);
292    /// ```
293    pub fn from_percentages<T>(percentages: T) -> Vec<Self>
294    where
295        T: IntoIterator<Item = u16>,
296    {
297        percentages.into_iter().map(Self::Percentage).collect()
298    }
299
300    /// Convert an iterator of maxes into a vector of constraints
301    ///
302    /// # Examples
303    ///
304    /// ```rust
305    /// use ratatui_core::layout::{Constraint, Layout, Rect};
306    ///
307    /// # let area = Rect::default();
308    /// let constraints = Constraint::from_maxes([1, 2, 3]);
309    /// let layout = Layout::default().constraints(constraints).split(area);
310    /// ```
311    pub fn from_maxes<T>(maxes: T) -> Vec<Self>
312    where
313        T: IntoIterator<Item = u16>,
314    {
315        maxes.into_iter().map(Self::Max).collect()
316    }
317
318    /// Convert an iterator of mins into a vector of constraints
319    ///
320    /// # Examples
321    ///
322    /// ```rust
323    /// use ratatui_core::layout::{Constraint, Layout, Rect};
324    ///
325    /// # let area = Rect::default();
326    /// let constraints = Constraint::from_mins([1, 2, 3]);
327    /// let layout = Layout::default().constraints(constraints).split(area);
328    /// ```
329    pub fn from_mins<T>(mins: T) -> Vec<Self>
330    where
331        T: IntoIterator<Item = u16>,
332    {
333        mins.into_iter().map(Self::Min).collect()
334    }
335
336    /// Convert an iterator of proportional factors into a vector of constraints
337    ///
338    /// # Examples
339    ///
340    /// ```rust
341    /// use ratatui_core::layout::{Constraint, Layout, Rect};
342    ///
343    /// # let area = Rect::default();
344    /// let constraints = Constraint::from_fills([1, 2, 3]);
345    /// let layout = Layout::default().constraints(constraints).split(area);
346    /// ```
347    pub fn from_fills<T>(proportional_factors: T) -> Vec<Self>
348    where
349        T: IntoIterator<Item = u16>,
350    {
351        proportional_factors.into_iter().map(Self::Fill).collect()
352    }
353}
354
355impl From<u16> for Constraint {
356    /// Convert a `u16` into a [`Constraint::Length`]
357    ///
358    /// This is useful when you want to specify a fixed size for a layout, but don't want to
359    /// explicitly create a [`Constraint::Length`] yourself.
360    ///
361    /// # Examples
362    ///
363    /// ```rust
364    /// use ratatui_core::layout::{Constraint, Direction, Layout, Rect};
365    ///
366    /// # let area = Rect::default();
367    /// let layout = Layout::new(Direction::Vertical, [1, 2, 3]).split(area);
368    /// let layout = Layout::horizontal([1, 2, 3]).split(area);
369    /// let layout = Layout::vertical([1, 2, 3]).split(area);
370    /// ````
371    fn from(length: u16) -> Self {
372        Self::Length(length)
373    }
374}
375
376impl From<&Self> for Constraint {
377    fn from(constraint: &Self) -> Self {
378        *constraint
379    }
380}
381
382impl AsRef<Self> for Constraint {
383    fn as_ref(&self) -> &Self {
384        self
385    }
386}
387
388impl Default for Constraint {
389    fn default() -> Self {
390        Self::Percentage(100)
391    }
392}
393
394impl fmt::Display for Constraint {
395    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396        match self {
397            Self::Percentage(p) => write!(f, "Percentage({p})"),
398            Self::Ratio(n, d) => write!(f, "Ratio({n}, {d})"),
399            Self::Length(l) => write!(f, "Length({l})"),
400            Self::Fill(l) => write!(f, "Fill({l})"),
401            Self::Max(m) => write!(f, "Max({m})"),
402            Self::Min(m) => write!(f, "Min({m})"),
403        }
404    }
405}
406
407#[cfg(test)]
408mod tests {
409    use alloc::string::ToString;
410    use alloc::vec;
411
412    use super::*;
413
414    #[test]
415    fn default() {
416        assert_eq!(Constraint::default(), Constraint::Percentage(100));
417    }
418
419    #[test]
420    fn to_string() {
421        assert_eq!(Constraint::Percentage(50).to_string(), "Percentage(50)");
422        assert_eq!(Constraint::Ratio(1, 2).to_string(), "Ratio(1, 2)");
423        assert_eq!(Constraint::Length(10).to_string(), "Length(10)");
424        assert_eq!(Constraint::Max(10).to_string(), "Max(10)");
425        assert_eq!(Constraint::Min(10).to_string(), "Min(10)");
426    }
427
428    #[test]
429    fn from_lengths() {
430        let expected = [
431            Constraint::Length(1),
432            Constraint::Length(2),
433            Constraint::Length(3),
434        ];
435        assert_eq!(Constraint::from_lengths([1, 2, 3]), expected);
436        assert_eq!(Constraint::from_lengths(vec![1, 2, 3]), expected);
437    }
438
439    #[test]
440    fn from_ratios() {
441        let expected = [
442            Constraint::Ratio(1, 4),
443            Constraint::Ratio(1, 2),
444            Constraint::Ratio(1, 4),
445        ];
446        assert_eq!(Constraint::from_ratios([(1, 4), (1, 2), (1, 4)]), expected);
447        assert_eq!(
448            Constraint::from_ratios(vec![(1, 4), (1, 2), (1, 4)]),
449            expected
450        );
451    }
452
453    #[test]
454    fn from_percentages() {
455        let expected = [
456            Constraint::Percentage(25),
457            Constraint::Percentage(50),
458            Constraint::Percentage(25),
459        ];
460        assert_eq!(Constraint::from_percentages([25, 50, 25]), expected);
461        assert_eq!(Constraint::from_percentages(vec![25, 50, 25]), expected);
462    }
463
464    #[test]
465    fn from_maxes() {
466        let expected = [Constraint::Max(1), Constraint::Max(2), Constraint::Max(3)];
467        assert_eq!(Constraint::from_maxes([1, 2, 3]), expected);
468        assert_eq!(Constraint::from_maxes(vec![1, 2, 3]), expected);
469    }
470
471    #[test]
472    fn from_mins() {
473        let expected = [Constraint::Min(1), Constraint::Min(2), Constraint::Min(3)];
474        assert_eq!(Constraint::from_mins([1, 2, 3]), expected);
475        assert_eq!(Constraint::from_mins(vec![1, 2, 3]), expected);
476    }
477
478    #[test]
479    fn from_fills() {
480        let expected = [
481            Constraint::Fill(1),
482            Constraint::Fill(2),
483            Constraint::Fill(3),
484        ];
485        assert_eq!(Constraint::from_fills([1, 2, 3]), expected);
486        assert_eq!(Constraint::from_fills(vec![1, 2, 3]), expected);
487    }
488
489    #[test]
490    #[expect(deprecated)]
491    fn apply() {
492        assert_eq!(Constraint::Percentage(0).apply(100), 0);
493        assert_eq!(Constraint::Percentage(50).apply(100), 50);
494        assert_eq!(Constraint::Percentage(100).apply(100), 100);
495        assert_eq!(Constraint::Percentage(200).apply(100), 100);
496        assert_eq!(Constraint::Percentage(u16::MAX).apply(100), 100);
497
498        // 0/0 intentionally avoids a panic by returning 0.
499        assert_eq!(Constraint::Ratio(0, 0).apply(100), 0);
500        // 1/0 intentionally avoids a panic by returning 100% of the length.
501        assert_eq!(Constraint::Ratio(1, 0).apply(100), 100);
502        assert_eq!(Constraint::Ratio(0, 1).apply(100), 0);
503        assert_eq!(Constraint::Ratio(1, 2).apply(100), 50);
504        assert_eq!(Constraint::Ratio(2, 2).apply(100), 100);
505        assert_eq!(Constraint::Ratio(3, 2).apply(100), 100);
506        assert_eq!(Constraint::Ratio(u32::MAX, 2).apply(100), 100);
507
508        assert_eq!(Constraint::Length(0).apply(100), 0);
509        assert_eq!(Constraint::Length(50).apply(100), 50);
510        assert_eq!(Constraint::Length(100).apply(100), 100);
511        assert_eq!(Constraint::Length(200).apply(100), 100);
512        assert_eq!(Constraint::Length(u16::MAX).apply(100), 100);
513
514        assert_eq!(Constraint::Max(0).apply(100), 0);
515        assert_eq!(Constraint::Max(50).apply(100), 50);
516        assert_eq!(Constraint::Max(100).apply(100), 100);
517        assert_eq!(Constraint::Max(200).apply(100), 100);
518        assert_eq!(Constraint::Max(u16::MAX).apply(100), 100);
519
520        assert_eq!(Constraint::Min(0).apply(100), 100);
521        assert_eq!(Constraint::Min(50).apply(100), 100);
522        assert_eq!(Constraint::Min(100).apply(100), 100);
523        assert_eq!(Constraint::Min(200).apply(100), 200);
524        assert_eq!(Constraint::Min(u16::MAX).apply(100), u16::MAX);
525    }
526}