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}