layout/
layout.rs

1//! # [Ratatui] Layout example
2//!
3//! The latest version of this example is available in the [examples] folder in the repository.
4//!
5//! Please note that the examples are designed to be run against the `main` branch of the Github
6//! repository. This means that you may not be able to compile with the latest release version on
7//! crates.io, or the one that you have installed locally.
8//!
9//! See the [examples readme] for more information on finding examples that match the version of the
10//! library you are using.
11//!
12//! [Ratatui]: https://github.com/ratatui/ratatui
13//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
14//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
15
16use itertools::Itertools;
17use ratatui::{
18    crossterm::event::{self, Event, KeyCode, KeyEventKind},
19    layout::{
20        Constraint::{self, Length, Max, Min, Percentage, Ratio},
21        Layout, Rect,
22    },
23    style::{Color, Style, Stylize},
24    text::Line,
25    widgets::{Block, Paragraph},
26    DefaultTerminal, Frame,
27};
28
29fn main() -> color_eyre::Result<()> {
30    color_eyre::install()?;
31    let terminal = ratatui::init();
32    let app_result = run(terminal);
33    ratatui::restore();
34    app_result
35}
36
37fn run(mut terminal: DefaultTerminal) -> color_eyre::Result<()> {
38    loop {
39        terminal.draw(draw)?;
40        if let Event::Key(key) = event::read()? {
41            if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
42                break Ok(());
43            }
44        }
45    }
46}
47
48#[allow(clippy::too_many_lines)]
49fn draw(frame: &mut Frame) {
50    let vertical = Layout::vertical([
51        Length(4),  // text
52        Length(50), // examples
53        Min(0),     // fills remaining space
54    ]);
55    let [text_area, examples_area, _] = vertical.areas(frame.area());
56
57    // title
58    frame.render_widget(
59        Paragraph::new(vec![
60            Line::from("Horizontal Layout Example. Press q to quit".dark_gray()).centered(),
61            Line::from("Each line has 2 constraints, plus Min(0) to fill the remaining space."),
62            Line::from("E.g. the second line of the Len/Min box is [Length(2), Min(2), Min(0)]"),
63            Line::from("Note: constraint labels that don't fit are truncated"),
64        ]),
65        text_area,
66    );
67
68    let example_rows = Layout::vertical([
69        Length(9),
70        Length(9),
71        Length(9),
72        Length(9),
73        Length(9),
74        Min(0), // fills remaining space
75    ])
76    .split(examples_area);
77    let example_areas = example_rows.iter().flat_map(|area| {
78        Layout::horizontal([
79            Length(14),
80            Length(14),
81            Length(14),
82            Length(14),
83            Length(14),
84            Min(0), // fills remaining space
85        ])
86        .split(*area)
87        .iter()
88        .copied()
89        .take(5) // ignore Min(0)
90        .collect_vec()
91    });
92
93    // the examples are a cartesian product of the following constraints
94    // e.g. Len/Len, Len/Min, Len/Max, Len/Perc, Len/Ratio, Min/Len, Min/Min, ...
95    let examples = [
96        (
97            "Len",
98            [
99                Length(0),
100                Length(2),
101                Length(3),
102                Length(6),
103                Length(10),
104                Length(15),
105            ],
106        ),
107        ("Min", [Min(0), Min(2), Min(3), Min(6), Min(10), Min(15)]),
108        ("Max", [Max(0), Max(2), Max(3), Max(6), Max(10), Max(15)]),
109        (
110            "Perc",
111            [
112                Percentage(0),
113                Percentage(25),
114                Percentage(50),
115                Percentage(75),
116                Percentage(100),
117                Percentage(150),
118            ],
119        ),
120        (
121            "Ratio",
122            [
123                Ratio(0, 4),
124                Ratio(1, 4),
125                Ratio(2, 4),
126                Ratio(3, 4),
127                Ratio(4, 4),
128                Ratio(6, 4),
129            ],
130        ),
131    ];
132
133    for ((a, b), area) in examples
134        .iter()
135        .cartesian_product(examples.iter())
136        .zip(example_areas)
137    {
138        let (name_a, examples_a) = a;
139        let (name_b, examples_b) = b;
140        let constraints = examples_a.iter().copied().zip(examples_b.iter().copied());
141        render_example_combination(frame, area, &format!("{name_a}/{name_b}"), constraints);
142    }
143}
144
145/// Renders a single example box
146fn render_example_combination(
147    frame: &mut Frame,
148    area: Rect,
149    title: &str,
150    constraints: impl ExactSizeIterator<Item = (Constraint, Constraint)>,
151) {
152    let block = Block::bordered()
153        .title(title.gray())
154        .style(Style::reset())
155        .border_style(Style::default().fg(Color::DarkGray));
156    let inner = block.inner(area);
157    frame.render_widget(block, area);
158    let layout = Layout::vertical(vec![Length(1); constraints.len() + 1]).split(inner);
159    for ((a, b), &area) in constraints.into_iter().zip(layout.iter()) {
160        render_single_example(frame, area, vec![a, b, Min(0)]);
161    }
162    // This is to make it easy to visually see the alignment of the examples
163    // with the constraints.
164    frame.render_widget(Paragraph::new("123456789012"), layout[6]);
165}
166
167/// Renders a single example line
168fn render_single_example(frame: &mut Frame, area: Rect, constraints: Vec<Constraint>) {
169    let red = Paragraph::new(constraint_label(constraints[0])).on_red();
170    let blue = Paragraph::new(constraint_label(constraints[1])).on_blue();
171    let green = Paragraph::new("ยท".repeat(12)).on_green();
172    let horizontal = Layout::horizontal(constraints);
173    let [r, b, g] = horizontal.areas(area);
174    frame.render_widget(red, r);
175    frame.render_widget(blue, b);
176    frame.render_widget(green, g);
177}
178
179fn constraint_label(constraint: Constraint) -> String {
180    match constraint {
181        Constraint::Ratio(a, b) => format!("{a}:{b}"),
182        Constraint::Length(n)
183        | Constraint::Min(n)
184        | Constraint::Max(n)
185        | Constraint::Percentage(n)
186        | Constraint::Fill(n) => format!("{n}"),
187    }
188}