ratatui/layout/
constraint.rs

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