pub struct Layout { /* private fields */ }
Expand description
A layout is a set of constraints that can be applied to a given area to split it into smaller ones.
A layout is composed of:
- a direction (horizontal or vertical)
- a set of constraints (length, ratio, percentage, fill, min, max)
- a margin (horizontal and vertical), the space between the edge of the main area and the split areas
- a flex option
- a spacing option
The algorithm used to compute the layout is based on the cassowary-rs
solver. It is a simple
linear solver that can be used to solve linear equations and inequalities. In our case, we
define a set of constraints that are applied to split the provided area into Rects aligned in a
single direction, and the solver computes the values of the position and sizes that satisfy as
many of the constraints in order of their priorities.
When the layout is computed, the result is cached in a thread-local cache, so that subsequent
calls with the same parameters are faster. The cache is a LruCache
, and the size of the cache
can be configured using Layout::init_cache()
.
§Constructors
There are four ways to create a new layout:
Layout::default
: create a new layout with default valuesLayout::new
: create a new layout with a given direction and constraintsLayout::vertical
: create a new vertical layout with the given constraintsLayout::horizontal
: create a new horizontal layout with the given constraints
§Setters
There are several setters to modify the layout:
Layout::direction
: set the direction of the layoutLayout::constraints
: set the constraints of the layoutLayout::margin
: set the margin of the layoutLayout::horizontal_margin
: set the horizontal margin of the layoutLayout::vertical_margin
: set the vertical margin of the layoutLayout::flex
: set the way the space is distributed when the constraints are satisfiedLayout::spacing
: sets the gap between the constraints of the layout
§Example
use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
widgets::Paragraph,
Frame,
};
fn render(frame: &mut Frame, area: Rect) {
let layout = Layout::new(
Direction::Vertical,
[Constraint::Length(5), Constraint::Min(0)],
)
.split(Rect::new(0, 0, 10, 10));
frame.render_widget(Paragraph::new("foo"), layout[0]);
frame.render_widget(Paragraph::new("bar"), layout[1]);
}
See the layout
, flex
, and constraints
examples in the Examples folder for more details
about how to use layouts.
Implementations§
Source§impl Layout
impl Layout
Sourcepub const DEFAULT_CACHE_SIZE: usize = 500usize
pub const DEFAULT_CACHE_SIZE: usize = 500usize
This is a somewhat arbitrary size for the layout cache based on adding the columns and rows
on my laptop’s terminal (171+51 = 222) and doubling it for good measure and then adding a
bit more to make it a round number. This gives enough entries to store a layout for every
row and every column, twice over, which should be enough for most apps. For those that need
more, the cache size can be set with Layout::init_cache()
.
Sourcepub fn new<I>(direction: Direction, constraints: I) -> Self
pub fn new<I>(direction: Direction, constraints: I) -> Self
Creates a new layout with default values.
The constraints
parameter accepts any type that implements IntoIterator<Item = Into<Constraint>>
. This includes arrays, slices, vectors, iterators. Into<Constraint>
is
implemented on u16
, so you can pass an array, Vec
, etc. of u16
to this function to
create a layout with fixed size chunks.
Default values for the other fields are:
margin
: 0, 0flex
:Flex::Start
spacing
: 0
§Examples
use ratatui::layout::{Constraint, Direction, Layout};
Layout::new(
Direction::Horizontal,
[Constraint::Length(5), Constraint::Min(0)],
);
Layout::new(
Direction::Vertical,
[1, 2, 3].iter().map(|&c| Constraint::Length(c)),
);
Layout::new(Direction::Horizontal, vec![1, 2]);
Sourcepub fn vertical<I>(constraints: I) -> Self
pub fn vertical<I>(constraints: I) -> Self
Creates a new vertical layout with default values.
The constraints
parameter accepts any type that implements IntoIterator<Item = Into<Constraint>>
. This includes arrays, slices, vectors, iterators, etc.
§Examples
use ratatui::layout::{Constraint, Layout};
let layout = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
Examples found in repository?
More examples
69 fn render(self, area: Rect, buf: &mut Buffer) {
70 RgbSwatch.render(area, buf);
71 let area = area.inner(Margin {
72 vertical: 1,
73 horizontal: 2,
74 });
75 Clear.render(area, buf);
76 let vertical = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
77 let [inbox, email] = vertical.areas(area);
78 render_inbox(self.row_index, inbox, buf);
79 render_email(self.row_index, email, buf);
80 }
81}
82fn render_inbox(selected_index: usize, area: Rect, buf: &mut Buffer) {
83 let vertical = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
84 let [tabs, inbox] = vertical.areas(area);
85 let theme = THEME.email;
86 Tabs::new(vec![" Inbox ", " Sent ", " Drafts "])
87 .style(theme.tabs)
88 .highlight_style(theme.tabs_selected)
89 .select(0)
90 .divider("")
91 .render(tabs, buf);
92
93 let highlight_symbol = ">>";
94 let from_width = EMAILS
95 .iter()
96 .map(|e| e.from.width())
97 .max()
98 .unwrap_or_default();
99 let items = EMAILS.iter().map(|e| {
100 let from = format!("{:width$}", e.from, width = from_width).into();
101 ListItem::new(Line::from(vec![from, " ".into(), e.subject.into()]))
102 });
103 let mut state = ListState::default().with_selected(Some(selected_index));
104 StatefulWidget::render(
105 List::new(items)
106 .style(theme.inbox)
107 .highlight_style(theme.selected_item)
108 .highlight_symbol(highlight_symbol),
109 inbox,
110 buf,
111 &mut state,
112 );
113 let mut scrollbar_state = ScrollbarState::default()
114 .content_length(EMAILS.len())
115 .position(selected_index);
116 Scrollbar::default()
117 .begin_symbol(None)
118 .end_symbol(None)
119 .track_symbol(None)
120 .thumb_symbol("▐")
121 .render(inbox, buf, &mut scrollbar_state);
122}
123
124fn render_email(selected_index: usize, area: Rect, buf: &mut Buffer) {
125 let theme = THEME.email;
126 let email = EMAILS.get(selected_index);
127 let block = Block::new()
128 .style(theme.body)
129 .padding(Padding::new(2, 2, 0, 0))
130 .borders(Borders::TOP)
131 .border_type(BorderType::Thick);
132 let inner = block.inner(area);
133 block.render(area, buf);
134 if let Some(email) = email {
135 let vertical = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]);
136 let [headers_area, body_area] = vertical.areas(inner);
137 let headers = vec![
138 Line::from(vec![
139 "From: ".set_style(theme.header),
140 email.from.set_style(theme.header_value),
141 ]),
142 Line::from(vec![
143 "Subject: ".set_style(theme.header),
144 email.subject.set_style(theme.header_value),
145 ]),
146 "-".repeat(inner.width as usize).dim().into(),
147 ];
148 Paragraph::new(headers)
149 .style(theme.body)
150 .render(headers_area, buf);
151 let body = email.body.lines().map(Line::from).collect_vec();
152 Paragraph::new(body)
153 .style(theme.body)
154 .render(body_area, buf);
155 } else {
156 Paragraph::new("No email selected").render(inner, buf);
157 }
158}
- examples/demo2/app.rs
- examples/flex.rs
- examples/ratatui-logo.rs
- examples/barchart.rs
- examples/custom_widget.rs
- examples/chart.rs
- examples/tabs.rs
- examples/block.rs
- examples/list.rs
- examples/line_gauge.rs
- examples/constraint-explorer.rs
- examples/canvas.rs
- examples/gauge.rs
- examples/demo2/tabs/traceroute.rs
- examples/calendar.rs
- examples/widget_impl.rs
- examples/popup.rs
- examples/paragraph.rs
- examples/demo2/tabs/weather.rs
- examples/sparkline.rs
- examples/inline.rs
- examples/user_input.rs
- examples/layout.rs
- examples/scrollbar.rs
Sourcepub fn horizontal<I>(constraints: I) -> Self
pub fn horizontal<I>(constraints: I) -> Self
Creates a new horizontal layout with default values.
The constraints
parameter accepts any type that implements IntoIterator<Item = Into<Constraint>>
. This includes arrays, slices, vectors, iterators, etc.
§Examples
use ratatui::layout::{Constraint, Layout};
let layout = Layout::horizontal([Constraint::Length(5), Constraint::Min(0)]);
Examples found in repository?
More examples
348 fn render_user_constraints_legend(&self, area: Rect, buf: &mut Buffer) {
349 let constraints = self.constraints.iter().map(|_| Constraint::Fill(1));
350 let blocks = Layout::horizontal(constraints).split(area);
351
352 for (i, (area, constraint)) in blocks.iter().zip(self.constraints.iter()).enumerate() {
353 let selected = self.selected_index == i;
354 ConstraintBlock::new(*constraint, selected, true).render(*area, buf);
355 }
356 }
357
358 fn render_layout_block(&self, flex: Flex, area: Rect, buf: &mut Buffer) {
359 let [label_area, axis_area, blocks_area] =
360 Layout::vertical([Length(1), Max(1), Length(4)]).areas(area);
361
362 if label_area.height > 0 {
363 format!("Flex::{flex:?}").bold().render(label_area, buf);
364 }
365
366 self.axis(area.width).render(axis_area, buf);
367
368 let (blocks, spacers) = Layout::horizontal(&self.constraints)
369 .flex(flex)
370 .spacing(self.spacing)
371 .split_with_spacers(blocks_area);
372
373 for (i, (area, constraint)) in blocks.iter().zip(self.constraints.iter()).enumerate() {
374 let selected = self.selected_index == i;
375 ConstraintBlock::new(*constraint, selected, false).render(*area, buf);
376 }
377
378 for area in spacers.iter() {
379 SpacerBlock.render(*area, buf);
380 }
381 }
Sourcepub fn init_cache(cache_size: NonZeroUsize)
pub fn init_cache(cache_size: NonZeroUsize)
Initialize an empty cache with a custom size. The cache is keyed on the layout and area, so
that subsequent calls with the same parameters are faster. The cache is a LruCache
, and
grows until cache_size
is reached.
By default, the cache size is Self::DEFAULT_CACHE_SIZE
.
Examples found in repository?
161 fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
162 // increase the layout cache to account for the number of layout events. This ensures that
163 // layout is not generally reprocessed on every frame (which would lead to possible janky
164 // results when there are more than one possible solution to the requested layout). This
165 // assumes the user changes spacing about a 100 times or so.
166 let cache_size = EXAMPLE_DATA.len() * SelectedTab::iter().len() * 100;
167 Layout::init_cache(NonZeroUsize::new(cache_size).unwrap());
168
169 while self.is_running() {
170 terminal.draw(|frame| frame.render_widget(self, frame.area()))?;
171 self.handle_events()?;
172 }
173 Ok(())
174 }
Sourcepub const fn direction(self, direction: Direction) -> Self
pub const fn direction(self, direction: Direction) -> Self
Set the direction of the layout.
§Examples
use ratatui::layout::{Constraint, Direction, Layout, Rect};
let layout = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Length(5), Constraint::Min(0)])
.split(Rect::new(0, 0, 10, 10));
assert_eq!(layout[..], [Rect::new(0, 0, 5, 10), Rect::new(5, 0, 5, 10)]);
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(5), Constraint::Min(0)])
.split(Rect::new(0, 0, 10, 10));
assert_eq!(layout[..], [Rect::new(0, 0, 10, 5), Rect::new(0, 5, 10, 5)]);
Sourcepub fn constraints<I>(self, constraints: I) -> Self
pub fn constraints<I>(self, constraints: I) -> Self
Sets the constraints of the layout.
The constraints
parameter accepts any type that implements IntoIterator<Item = Into<Constraint>>
. This includes arrays, slices, vectors, iterators. Into<Constraint>
is
implemented on u16, so you can pass an array or vec of u16 to this function to create a
layout with fixed size chunks.
Note that the constraints are applied to the whole area that is to be split, so using percentages and ratios with the other constraints may not have the desired effect of splitting the area up. (e.g. splitting 100 into [min 20, 50%, 50%], may not result in [20, 40, 40] but rather an indeterminate result between [20, 50, 30] and [20, 30, 50]).
§Examples
use ratatui::layout::{Constraint, Layout, Rect};
let layout = Layout::default()
.constraints([
Constraint::Percentage(20),
Constraint::Ratio(1, 5),
Constraint::Length(2),
Constraint::Min(2),
Constraint::Max(2),
])
.split(Rect::new(0, 0, 10, 10));
assert_eq!(
layout[..],
[
Rect::new(0, 0, 10, 2),
Rect::new(0, 2, 10, 2),
Rect::new(0, 4, 10, 2),
Rect::new(0, 6, 10, 2),
Rect::new(0, 8, 10, 2),
]
);
Layout::default().constraints([Constraint::Min(0)]);
Layout::default().constraints(&[Constraint::Min(0)]);
Layout::default().constraints(vec![Constraint::Min(0)]);
Layout::default().constraints([Constraint::Min(0)].iter().filter(|_| true));
Layout::default().constraints([1, 2, 3].iter().map(|&c| Constraint::Length(c)));
Layout::default().constraints([1, 2, 3]);
Layout::default().constraints(vec![1, 2, 3]);
Sourcepub const fn margin(self, margin: u16) -> Self
pub const fn margin(self, margin: u16) -> Self
Set the margin of the layout.
§Examples
use ratatui::layout::{Constraint, Layout, Rect};
let layout = Layout::default()
.constraints([Constraint::Min(0)])
.margin(2)
.split(Rect::new(0, 0, 10, 10));
assert_eq!(layout[..], [Rect::new(2, 2, 6, 6)]);
Examples found in repository?
231fn draw(frame: &mut Frame, downloads: &Downloads) {
232 let area = frame.area();
233
234 let block = Block::new().title(Line::from("Progress").centered());
235 frame.render_widget(block, area);
236
237 let vertical = Layout::vertical([Constraint::Length(2), Constraint::Length(4)]).margin(1);
238 let horizontal = Layout::horizontal([Constraint::Percentage(20), Constraint::Percentage(80)]);
239 let [progress_area, main] = vertical.areas(area);
240 let [list_area, gauge_area] = horizontal.areas(main);
241
242 // total progress
243 let done = NUM_DOWNLOADS - downloads.pending.len() - downloads.in_progress.len();
244 #[allow(clippy::cast_precision_loss)]
245 let progress = LineGauge::default()
246 .filled_style(Style::default().fg(Color::Blue))
247 .label(format!("{done}/{NUM_DOWNLOADS}"))
248 .ratio(done as f64 / NUM_DOWNLOADS as f64);
249 frame.render_widget(progress, progress_area);
250
251 // in progress downloads
252 let items: Vec<ListItem> = downloads
253 .in_progress
254 .values()
255 .map(|download| {
256 ListItem::new(Line::from(vec![
257 Span::raw(symbols::DOT),
258 Span::styled(
259 format!(" download {:>2}", download.id),
260 Style::default()
261 .fg(Color::LightGreen)
262 .add_modifier(Modifier::BOLD),
263 ),
264 Span::raw(format!(
265 " ({}ms)",
266 download.started_at.elapsed().as_millis()
267 )),
268 ]))
269 })
270 .collect();
271 let list = List::new(items);
272 frame.render_widget(list, list_area);
273
274 #[allow(clippy::cast_possible_truncation)]
275 for (i, (_, download)) in downloads.in_progress.iter().enumerate() {
276 let gauge = Gauge::default()
277 .gauge_style(Style::default().fg(Color::Yellow))
278 .ratio(download.progress / 100.0);
279 if gauge_area.top().saturating_add(i as u16) > area.bottom() {
280 continue;
281 }
282 frame.render_widget(
283 gauge,
284 Rect {
285 x: gauge_area.left(),
286 y: gauge_area.top().saturating_add(i as u16),
287 width: gauge_area.width,
288 height: 1,
289 },
290 );
291 }
292}
Sourcepub const fn horizontal_margin(self, horizontal: u16) -> Self
pub const fn horizontal_margin(self, horizontal: u16) -> Self
Set the horizontal margin of the layout.
§Examples
use ratatui::layout::{Constraint, Layout, Rect};
let layout = Layout::default()
.constraints([Constraint::Min(0)])
.horizontal_margin(2)
.split(Rect::new(0, 0, 10, 10));
assert_eq!(layout[..], [Rect::new(2, 0, 6, 10)]);
Sourcepub const fn vertical_margin(self, vertical: u16) -> Self
pub const fn vertical_margin(self, vertical: u16) -> Self
Set the vertical margin of the layout.
§Examples
use ratatui::layout::{Constraint, Layout, Rect};
let layout = Layout::default()
.constraints([Constraint::Min(0)])
.vertical_margin(2)
.split(Rect::new(0, 0, 10, 10));
assert_eq!(layout[..], [Rect::new(0, 2, 10, 6)]);
Sourcepub const fn flex(self, flex: Flex) -> Self
pub const fn flex(self, flex: Flex) -> Self
The flex
method allows you to specify the flex behavior of the layout.
§Arguments
flex
: AFlex
enum value that represents the flex behavior of the layout. It can be one of the following:Flex::Legacy
: The last item is stretched to fill the excess space.Flex::Start
: The items are aligned to the start of the layout.Flex::Center
: The items are aligned to the center of the layout.Flex::End
: The items are aligned to the end of the layout.Flex::SpaceAround
: The items are evenly distributed with equal space around them.Flex::SpaceBetween
: The items are evenly distributed with equal space between them.
§Examples
In this example, the items in the layout will be aligned to the start.
use ratatui::layout::{Constraint::*, Flex, Layout};
let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).flex(Flex::Start);
In this example, the items in the layout will be stretched equally to fill the available space.
use ratatui::layout::{Constraint::*, Flex, Layout};
let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).flex(Flex::Legacy);
Examples found in repository?
More examples
397 fn render_examples(area: Rect, buf: &mut Buffer, flex: Flex, spacing: u16) {
398 let heights = EXAMPLE_DATA
399 .iter()
400 .map(|(desc, _)| get_description_height(desc) + 4);
401 let areas = Layout::vertical(heights).flex(Flex::Start).split(area);
402 for (area, (description, constraints)) in areas.iter().zip(EXAMPLE_DATA.iter()) {
403 Example::new(constraints, description, flex, spacing).render(*area, buf);
404 }
405 }
406}
407
408impl Example {
409 fn new(constraints: &[Constraint], description: &str, flex: Flex, spacing: u16) -> Self {
410 Self {
411 constraints: constraints.into(),
412 description: description.into(),
413 flex,
414 spacing,
415 }
416 }
417}
418
419impl Widget for Example {
420 fn render(self, area: Rect, buf: &mut Buffer) {
421 let title_height = get_description_height(&self.description);
422 let layout = Layout::vertical([Length(title_height), Fill(0)]);
423 let [title, illustrations] = layout.areas(area);
424
425 let (blocks, spacers) = Layout::horizontal(&self.constraints)
426 .flex(self.flex)
427 .spacing(self.spacing)
428 .split_with_spacers(illustrations);
429
430 if !self.description.is_empty() {
431 Paragraph::new(
432 self.description
433 .split('\n')
434 .map(|s| format!("// {s}").italic().fg(tailwind::SLATE.c400))
435 .map(Line::from)
436 .collect::<Vec<Line>>(),
437 )
438 .render(title, buf);
439 }
440
441 for (block, constraint) in blocks.iter().zip(&self.constraints) {
442 Self::illustration(*constraint, block.width).render(*block, buf);
443 }
444
445 for spacer in spacers.iter() {
446 Self::render_spacer(*spacer, buf);
447 }
448 }
358 fn render_layout_block(&self, flex: Flex, area: Rect, buf: &mut Buffer) {
359 let [label_area, axis_area, blocks_area] =
360 Layout::vertical([Length(1), Max(1), Length(4)]).areas(area);
361
362 if label_area.height > 0 {
363 format!("Flex::{flex:?}").bold().render(label_area, buf);
364 }
365
366 self.axis(area.width).render(axis_area, buf);
367
368 let (blocks, spacers) = Layout::horizontal(&self.constraints)
369 .flex(flex)
370 .spacing(self.spacing)
371 .split_with_spacers(blocks_area);
372
373 for (i, (area, constraint)) in blocks.iter().zip(self.constraints.iter()).enumerate() {
374 let selected = self.selected_index == i;
375 ConstraintBlock::new(*constraint, selected, false).render(*area, buf);
376 }
377
378 for area in spacers.iter() {
379 SpacerBlock.render(*area, buf);
380 }
381 }
Sourcepub fn spacing<T>(self, spacing: T) -> Self
pub fn spacing<T>(self, spacing: T) -> Self
Sets the spacing between items in the layout.
The spacing
method sets the spacing between items in the layout. The spacing is applied
evenly between all segments. The spacing value represents the number of cells between each
item.
Spacing can be positive integers, representing gaps between segments; or negative integers
representing overlaps. Additionally, one of the variants of the Spacing
enum can be
passed to this function. See the documentation of the Spacing
enum for more information.
Note that if the layout has only one segment, the spacing will not be applied.
Also, spacing will not be applied for Flex::SpaceAround
and Flex::SpaceBetween
§Examples
In this example, the spacing between each item in the layout is set to 2 cells.
use ratatui::layout::{Constraint::*, Layout};
let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).spacing(2);
In this example, the spacing between each item in the layout is set to -1 cells, i.e. the three segments will have an overlapping border.
use ratatui::layout::{Constraint::*, Layout};
let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).spacing(-1);
Examples found in repository?
More examples
67 fn draw(&self, frame: &mut Frame) {
68 let [title, vertical, horizontal] = Layout::vertical([
69 Constraint::Length(1),
70 Constraint::Fill(1),
71 Constraint::Fill(1),
72 ])
73 .spacing(1)
74 .areas(frame.area());
75
76 frame.render_widget("Barchart".bold().into_centered_line(), title);
77 frame.render_widget(vertical_barchart(&self.temperatures), vertical);
78 frame.render_widget(horizontal_barchart(&self.temperatures), horizontal);
79 }
331 fn render_layout_blocks(&self, area: Rect, buf: &mut Buffer) {
332 let [user_constraints, area] = Layout::vertical([Length(3), Fill(1)])
333 .spacing(1)
334 .areas(area);
335
336 self.render_user_constraints_legend(user_constraints, buf);
337
338 let [start, center, end, space_around, space_between] =
339 Layout::vertical([Length(7); 5]).areas(area);
340
341 self.render_layout_block(Flex::Start, start, buf);
342 self.render_layout_block(Flex::Center, center, buf);
343 self.render_layout_block(Flex::End, end, buf);
344 self.render_layout_block(Flex::SpaceAround, space_around, buf);
345 self.render_layout_block(Flex::SpaceBetween, space_between, buf);
346 }
347
348 fn render_user_constraints_legend(&self, area: Rect, buf: &mut Buffer) {
349 let constraints = self.constraints.iter().map(|_| Constraint::Fill(1));
350 let blocks = Layout::horizontal(constraints).split(area);
351
352 for (i, (area, constraint)) in blocks.iter().zip(self.constraints.iter()).enumerate() {
353 let selected = self.selected_index == i;
354 ConstraintBlock::new(*constraint, selected, true).render(*area, buf);
355 }
356 }
357
358 fn render_layout_block(&self, flex: Flex, area: Rect, buf: &mut Buffer) {
359 let [label_area, axis_area, blocks_area] =
360 Layout::vertical([Length(1), Max(1), Length(4)]).areas(area);
361
362 if label_area.height > 0 {
363 format!("Flex::{flex:?}").bold().render(label_area, buf);
364 }
365
366 self.axis(area.width).render(axis_area, buf);
367
368 let (blocks, spacers) = Layout::horizontal(&self.constraints)
369 .flex(flex)
370 .spacing(self.spacing)
371 .split_with_spacers(blocks_area);
372
373 for (i, (area, constraint)) in blocks.iter().zip(self.constraints.iter()).enumerate() {
374 let selected = self.selected_index == i;
375 ConstraintBlock::new(*constraint, selected, false).render(*area, buf);
376 }
377
378 for area in spacers.iter() {
379 SpacerBlock.render(*area, buf);
380 }
381 }
420 fn render(self, area: Rect, buf: &mut Buffer) {
421 let title_height = get_description_height(&self.description);
422 let layout = Layout::vertical([Length(title_height), Fill(0)]);
423 let [title, illustrations] = layout.areas(area);
424
425 let (blocks, spacers) = Layout::horizontal(&self.constraints)
426 .flex(self.flex)
427 .spacing(self.spacing)
428 .split_with_spacers(illustrations);
429
430 if !self.description.is_empty() {
431 Paragraph::new(
432 self.description
433 .split('\n')
434 .map(|s| format!("// {s}").italic().fg(tailwind::SLATE.c400))
435 .map(Line::from)
436 .collect::<Vec<Line>>(),
437 )
438 .render(title, buf);
439 }
440
441 for (block, constraint) in blocks.iter().zip(&self.constraints) {
442 Self::illustration(*constraint, block.width).render(*block, buf);
443 }
444
445 for spacer in spacers.iter() {
446 Self::render_spacer(*spacer, buf);
447 }
448 }
Sourcepub fn areas<const N: usize>(&self, area: Rect) -> [Rect; N]
pub fn areas<const N: usize>(&self, area: Rect) -> [Rect; N]
Split the rect into a number of sub-rects according to the given Layout
.
An ergonomic wrapper around Layout::split
that returns an array of Rect
s instead of
Rc<[Rect]>
.
This method requires the number of constraints to be known at compile time. If you don’t
know the number of constraints at compile time, use Layout::split
instead.
§Panics
Panics if the number of constraints is not equal to the length of the returned array.
§Examples
use ratatui::{layout::{Layout, Constraint}, Frame};
let area = frame.area();
let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
let [top, main] = layout.areas(area);
// or explicitly specify the number of constraints:
let areas = layout.areas::<2>(area);
Examples found in repository?
More examples
69 fn render(self, area: Rect, buf: &mut Buffer) {
70 RgbSwatch.render(area, buf);
71 let area = area.inner(Margin {
72 vertical: 1,
73 horizontal: 2,
74 });
75 Clear.render(area, buf);
76 let vertical = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
77 let [inbox, email] = vertical.areas(area);
78 render_inbox(self.row_index, inbox, buf);
79 render_email(self.row_index, email, buf);
80 }
81}
82fn render_inbox(selected_index: usize, area: Rect, buf: &mut Buffer) {
83 let vertical = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
84 let [tabs, inbox] = vertical.areas(area);
85 let theme = THEME.email;
86 Tabs::new(vec![" Inbox ", " Sent ", " Drafts "])
87 .style(theme.tabs)
88 .highlight_style(theme.tabs_selected)
89 .select(0)
90 .divider("")
91 .render(tabs, buf);
92
93 let highlight_symbol = ">>";
94 let from_width = EMAILS
95 .iter()
96 .map(|e| e.from.width())
97 .max()
98 .unwrap_or_default();
99 let items = EMAILS.iter().map(|e| {
100 let from = format!("{:width$}", e.from, width = from_width).into();
101 ListItem::new(Line::from(vec![from, " ".into(), e.subject.into()]))
102 });
103 let mut state = ListState::default().with_selected(Some(selected_index));
104 StatefulWidget::render(
105 List::new(items)
106 .style(theme.inbox)
107 .highlight_style(theme.selected_item)
108 .highlight_symbol(highlight_symbol),
109 inbox,
110 buf,
111 &mut state,
112 );
113 let mut scrollbar_state = ScrollbarState::default()
114 .content_length(EMAILS.len())
115 .position(selected_index);
116 Scrollbar::default()
117 .begin_symbol(None)
118 .end_symbol(None)
119 .track_symbol(None)
120 .thumb_symbol("▐")
121 .render(inbox, buf, &mut scrollbar_state);
122}
123
124fn render_email(selected_index: usize, area: Rect, buf: &mut Buffer) {
125 let theme = THEME.email;
126 let email = EMAILS.get(selected_index);
127 let block = Block::new()
128 .style(theme.body)
129 .padding(Padding::new(2, 2, 0, 0))
130 .borders(Borders::TOP)
131 .border_type(BorderType::Thick);
132 let inner = block.inner(area);
133 block.render(area, buf);
134 if let Some(email) = email {
135 let vertical = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]);
136 let [headers_area, body_area] = vertical.areas(inner);
137 let headers = vec![
138 Line::from(vec![
139 "From: ".set_style(theme.header),
140 email.from.set_style(theme.header_value),
141 ]),
142 Line::from(vec![
143 "Subject: ".set_style(theme.header),
144 email.subject.set_style(theme.header_value),
145 ]),
146 "-".repeat(inner.width as usize).dim().into(),
147 ];
148 Paragraph::new(headers)
149 .style(theme.body)
150 .render(headers_area, buf);
151 let body = email.body.lines().map(Line::from).collect_vec();
152 Paragraph::new(body)
153 .style(theme.body)
154 .render(body_area, buf);
155 } else {
156 Paragraph::new("No email selected").render(inner, buf);
157 }
158}
- examples/demo2/app.rs
- examples/flex.rs
- examples/ratatui-logo.rs
- examples/barchart.rs
- examples/custom_widget.rs
- examples/chart.rs
- examples/tabs.rs
- examples/block.rs
- examples/list.rs
- examples/line_gauge.rs
- examples/constraint-explorer.rs
- examples/canvas.rs
- examples/gauge.rs
- examples/demo2/tabs/traceroute.rs
- examples/widget_impl.rs
- examples/popup.rs
- examples/demo2/tabs/recipe.rs
- examples/demo2/tabs/weather.rs
- examples/inline.rs
- examples/user_input.rs
- examples/layout.rs
Sourcepub fn spacers<const N: usize>(&self, area: Rect) -> [Rect; N]
pub fn spacers<const N: usize>(&self, area: Rect) -> [Rect; N]
Split the rect into a number of sub-rects according to the given Layout
and return just
the spacers between the areas.
This method requires the number of constraints to be known at compile time. If you don’t
know the number of constraints at compile time, use Layout::split_with_spacers
instead.
This method is similar to Layout::areas
, and can be called with the same parameters, but
it returns just the spacers between the areas. The result of calling the areas
method is
cached, so this will generally not re-run the solver, but will just return the cached
result.
§Panics
Panics if the number of constraints + 1 is not equal to the length of the returned array.
§Examples
use ratatui::{layout::{Layout, Constraint}, Frame};
let area = frame.area();
let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
let [top, main] = layout.areas(area);
let [before, inbetween, after] = layout.spacers(area);
// or explicitly specify the number of constraints:
let spacers = layout.spacers::<2>(area);
Sourcepub fn split(&self, area: Rect) -> Rc<[Rect]>
pub fn split(&self, area: Rect) -> Rc<[Rect]>
Wrapper function around the cassowary-rs solver to be able to split a given area into smaller ones based on the preferred widths or heights and the direction.
Note that the constraints are applied to the whole area that is to be split, so using percentages and ratios with the other constraints may not have the desired effect of splitting the area up. (e.g. splitting 100 into [min 20, 50%, 50%], may not result in [20, 40, 40] but rather an indeterminate result between [20, 50, 30] and [20, 30, 50]).
This method stores the result of the computation in a thread-local cache keyed on the layout
and area, so that subsequent calls with the same parameters are faster. The cache is a
LruCache
, and grows until Self::DEFAULT_CACHE_SIZE
is reached by default, if the cache
is initialized with the Layout::init_cache()
grows until the initialized cache size.
There is a helper method that can be used to split the whole area into smaller ones based on
the layout: Layout::areas()
. That method is a shortcut for calling this method. It
allows you to destructure the result directly into variables, which is useful when you know
at compile time the number of areas that will be created.
§Examples
use ratatui::layout::{Constraint, Direction, Layout, Rect};
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(5), Constraint::Min(0)])
.split(Rect::new(2, 2, 10, 10));
assert_eq!(layout[..], [Rect::new(2, 2, 10, 5), Rect::new(2, 7, 10, 5)]);
let layout = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Ratio(1, 3), Constraint::Ratio(2, 3)])
.split(Rect::new(0, 0, 9, 2));
assert_eq!(layout[..], [Rect::new(0, 0, 3, 2), Rect::new(3, 0, 6, 2)]);
Examples found in repository?
More examples
77fn calculate_layout(area: Rect) -> (Rect, Vec<Vec<Rect>>) {
78 let main_layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
79 let block_layout = Layout::vertical([Constraint::Max(4); 9]);
80 let [title_area, main_area] = main_layout.areas(area);
81 let main_areas = block_layout
82 .split(main_area)
83 .iter()
84 .map(|&area| {
85 Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
86 .split(area)
87 .to_vec()
88 })
89 .collect();
90 (title_area, main_areas)
91}
45fn draw(frame: &mut Frame) {
46 let area = frame.area().inner(Margin {
47 vertical: 1,
48 horizontal: 1,
49 });
50
51 let mut start = OffsetDateTime::now_local()
52 .unwrap()
53 .date()
54 .replace_month(Month::January)
55 .unwrap()
56 .replace_day(1)
57 .unwrap();
58
59 let list = make_dates(start.year());
60
61 let rows = Layout::vertical([Constraint::Ratio(1, 3); 3]).split(area);
62 let cols = rows.iter().flat_map(|row| {
63 Layout::horizontal([Constraint::Ratio(1, 4); 4])
64 .split(*row)
65 .to_vec()
66 });
67 for col in cols {
68 let cal = cals::get_cal(start.month(), start.year(), &list);
69 frame.render_widget(cal, col);
70 start = start.replace_month(start.month().next()).unwrap();
71 }
72}
Sourcepub fn split_with_spacers(&self, area: Rect) -> (Rc<[Rect]>, Rc<[Rect]>)
pub fn split_with_spacers(&self, area: Rect) -> (Rc<[Rect]>, Rc<[Rect]>)
Wrapper function around the cassowary-rs solver that splits the given area into smaller ones based on the preferred widths or heights and the direction, with the ability to include spacers between the areas.
This method is similar to split
, but it returns two sets of rectangles: one for the areas
and one for the spacers.
This method stores the result of the computation in a thread-local cache keyed on the layout
and area, so that subsequent calls with the same parameters are faster. The cache is a
LruCache
, and grows until Self::DEFAULT_CACHE_SIZE
is reached by default, if the cache
is initialized with the Layout::init_cache()
grows until the initialized cache size.
§Examples
use ratatui::layout::{Constraint, Direction, Layout, Rect};
let (areas, spacers) = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(5), Constraint::Min(0)])
.split_with_spacers(Rect::new(2, 2, 10, 10));
assert_eq!(areas[..], [Rect::new(2, 2, 10, 5), Rect::new(2, 7, 10, 5)]);
assert_eq!(
spacers[..],
[
Rect::new(2, 2, 10, 0),
Rect::new(2, 7, 10, 0),
Rect::new(2, 12, 10, 0)
]
);
let (areas, spacers) = Layout::default()
.direction(Direction::Horizontal)
.spacing(1)
.constraints([Constraint::Ratio(1, 3), Constraint::Ratio(2, 3)])
.split_with_spacers(Rect::new(0, 0, 10, 2));
assert_eq!(areas[..], [Rect::new(0, 0, 3, 2), Rect::new(4, 0, 6, 2)]);
assert_eq!(
spacers[..],
[
Rect::new(0, 0, 0, 2),
Rect::new(3, 0, 1, 2),
Rect::new(10, 0, 0, 2)
]
);
Examples found in repository?
358 fn render_layout_block(&self, flex: Flex, area: Rect, buf: &mut Buffer) {
359 let [label_area, axis_area, blocks_area] =
360 Layout::vertical([Length(1), Max(1), Length(4)]).areas(area);
361
362 if label_area.height > 0 {
363 format!("Flex::{flex:?}").bold().render(label_area, buf);
364 }
365
366 self.axis(area.width).render(axis_area, buf);
367
368 let (blocks, spacers) = Layout::horizontal(&self.constraints)
369 .flex(flex)
370 .spacing(self.spacing)
371 .split_with_spacers(blocks_area);
372
373 for (i, (area, constraint)) in blocks.iter().zip(self.constraints.iter()).enumerate() {
374 let selected = self.selected_index == i;
375 ConstraintBlock::new(*constraint, selected, false).render(*area, buf);
376 }
377
378 for area in spacers.iter() {
379 SpacerBlock.render(*area, buf);
380 }
381 }
More examples
420 fn render(self, area: Rect, buf: &mut Buffer) {
421 let title_height = get_description_height(&self.description);
422 let layout = Layout::vertical([Length(title_height), Fill(0)]);
423 let [title, illustrations] = layout.areas(area);
424
425 let (blocks, spacers) = Layout::horizontal(&self.constraints)
426 .flex(self.flex)
427 .spacing(self.spacing)
428 .split_with_spacers(illustrations);
429
430 if !self.description.is_empty() {
431 Paragraph::new(
432 self.description
433 .split('\n')
434 .map(|s| format!("// {s}").italic().fg(tailwind::SLATE.c400))
435 .map(Line::from)
436 .collect::<Vec<Line>>(),
437 )
438 .render(title, buf);
439 }
440
441 for (block, constraint) in blocks.iter().zip(&self.constraints) {
442 Self::illustration(*constraint, block.width).render(*block, buf);
443 }
444
445 for spacer in spacers.iter() {
446 Self::render_spacer(*spacer, buf);
447 }
448 }
Trait Implementations§
impl Eq for Layout
impl StructuralPartialEq for Layout
Auto Trait Implementations§
impl Freeze for Layout
impl RefUnwindSafe for Layout
impl Send for Layout
impl Sync for Layout
impl Unpin for Layout
impl UnwindSafe for Layout
Blanket Implementations§
Source§impl<S, D, Swp, Dwp, T> AdaptInto<D, Swp, Dwp, T> for Swhere
T: Real + Zero + Arithmetics + Clone,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
D: AdaptFrom<S, Swp, Dwp, T>,
impl<S, D, Swp, Dwp, T> AdaptInto<D, Swp, Dwp, T> for Swhere
T: Real + Zero + Arithmetics + Clone,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
D: AdaptFrom<S, Swp, Dwp, T>,
Source§fn adapt_into_using<M>(self, method: M) -> Dwhere
M: TransformMatrix<T>,
fn adapt_into_using<M>(self, method: M) -> Dwhere
M: TransformMatrix<T>,
Source§fn adapt_into(self) -> D
fn adapt_into(self) -> D
Source§impl<T, C> ArraysFrom<C> for Twhere
C: IntoArrays<T>,
impl<T, C> ArraysFrom<C> for Twhere
C: IntoArrays<T>,
Source§fn arrays_from(colors: C) -> T
fn arrays_from(colors: C) -> T
Source§impl<T, C> ArraysInto<C> for Twhere
C: FromArrays<T>,
impl<T, C> ArraysInto<C> for Twhere
C: FromArrays<T>,
Source§fn arrays_into(self) -> C
fn arrays_into(self) -> C
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<WpParam, T, U> Cam16IntoUnclamped<WpParam, T> for Uwhere
T: FromCam16Unclamped<WpParam, U>,
impl<WpParam, T, U> Cam16IntoUnclamped<WpParam, T> for Uwhere
T: FromCam16Unclamped<WpParam, U>,
Source§type Scalar = <T as FromCam16Unclamped<WpParam, U>>::Scalar
type Scalar = <T as FromCam16Unclamped<WpParam, U>>::Scalar
parameters
when converting.Source§fn cam16_into_unclamped(
self,
parameters: BakedParameters<WpParam, <U as Cam16IntoUnclamped<WpParam, T>>::Scalar>,
) -> T
fn cam16_into_unclamped( self, parameters: BakedParameters<WpParam, <U as Cam16IntoUnclamped<WpParam, T>>::Scalar>, ) -> T
self
into C
, using the provided parameters.Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T, C> ComponentsFrom<C> for Twhere
C: IntoComponents<T>,
impl<T, C> ComponentsFrom<C> for Twhere
C: IntoComponents<T>,
Source§fn components_from(colors: C) -> T
fn components_from(colors: C) -> T
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key
and return true
if they are equal.Source§impl<T> FromAngle<T> for T
impl<T> FromAngle<T> for T
Source§fn from_angle(angle: T) -> T
fn from_angle(angle: T) -> T
angle
.Source§impl<T, U> FromStimulus<U> for Twhere
U: IntoStimulus<T>,
impl<T, U> FromStimulus<U> for Twhere
U: IntoStimulus<T>,
Source§fn from_stimulus(other: U) -> T
fn from_stimulus(other: U) -> T
other
into Self
, while performing the appropriate scaling,
rounding and clamping.Source§impl<T, U> IntoAngle<U> for Twhere
U: FromAngle<T>,
impl<T, U> IntoAngle<U> for Twhere
U: FromAngle<T>,
Source§fn into_angle(self) -> U
fn into_angle(self) -> U
T
.Source§impl<WpParam, T, U> IntoCam16Unclamped<WpParam, T> for Uwhere
T: Cam16FromUnclamped<WpParam, U>,
impl<WpParam, T, U> IntoCam16Unclamped<WpParam, T> for Uwhere
T: Cam16FromUnclamped<WpParam, U>,
Source§type Scalar = <T as Cam16FromUnclamped<WpParam, U>>::Scalar
type Scalar = <T as Cam16FromUnclamped<WpParam, U>>::Scalar
parameters
when converting.Source§fn into_cam16_unclamped(
self,
parameters: BakedParameters<WpParam, <U as IntoCam16Unclamped<WpParam, T>>::Scalar>,
) -> T
fn into_cam16_unclamped( self, parameters: BakedParameters<WpParam, <U as IntoCam16Unclamped<WpParam, T>>::Scalar>, ) -> T
self
into C
, using the provided parameters.Source§impl<T, U> IntoColor<U> for Twhere
U: FromColor<T>,
impl<T, U> IntoColor<U> for Twhere
U: FromColor<T>,
Source§fn into_color(self) -> U
fn into_color(self) -> U
Source§impl<T, U> IntoColorUnclamped<U> for Twhere
U: FromColorUnclamped<T>,
impl<T, U> IntoColorUnclamped<U> for Twhere
U: FromColorUnclamped<T>,
Source§fn into_color_unclamped(self) -> U
fn into_color_unclamped(self) -> U
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self
into a Left
variant of Either<Self, Self>
if into_left
is true
.
Converts self
into a Right
variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self
into a Left
variant of Either<Self, Self>
if into_left(&self)
returns true
.
Converts self
into a Right
variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoStimulus<T> for T
impl<T> IntoStimulus<T> for T
Source§fn into_stimulus(self) -> T
fn into_stimulus(self) -> T
self
into T
, while performing the appropriate scaling,
rounding and clamping.Source§impl<T, C> TryComponentsInto<C> for Twhere
C: TryFromComponents<T>,
impl<T, C> TryComponentsInto<C> for Twhere
C: TryFromComponents<T>,
Source§type Error = <C as TryFromComponents<T>>::Error
type Error = <C as TryFromComponents<T>>::Error
try_into_colors
fails to cast.Source§fn try_components_into(self) -> Result<C, <T as TryComponentsInto<C>>::Error>
fn try_components_into(self) -> Result<C, <T as TryComponentsInto<C>>::Error>
Source§impl<T, U> TryIntoColor<U> for Twhere
U: TryFromColor<T>,
impl<T, U> TryIntoColor<U> for Twhere
U: TryFromColor<T>,
Source§fn try_into_color(self) -> Result<U, OutOfBounds<U>>
fn try_into_color(self) -> Result<U, OutOfBounds<U>>
OutOfBounds
error is returned which contains
the unclamped color. Read more