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}