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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
fn render(self, area: Rect, buf: &mut Buffer) {
RgbSwatch.render(area, buf);
let area = area.inner(Margin {
vertical: 1,
horizontal: 2,
});
Clear.render(area, buf);
let vertical = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
let [inbox, email] = vertical.areas(area);
render_inbox(self.row_index, inbox, buf);
render_email(self.row_index, email, buf);
}
}
fn render_inbox(selected_index: usize, area: Rect, buf: &mut Buffer) {
let vertical = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
let [tabs, inbox] = vertical.areas(area);
let theme = THEME.email;
Tabs::new(vec![" Inbox ", " Sent ", " Drafts "])
.style(theme.tabs)
.highlight_style(theme.tabs_selected)
.select(0)
.divider("")
.render(tabs, buf);
let highlight_symbol = ">>";
let from_width = EMAILS
.iter()
.map(|e| e.from.width())
.max()
.unwrap_or_default();
let items = EMAILS.iter().map(|e| {
let from = format!("{:width$}", e.from, width = from_width).into();
ListItem::new(Line::from(vec![from, " ".into(), e.subject.into()]))
});
let mut state = ListState::default().with_selected(Some(selected_index));
StatefulWidget::render(
List::new(items)
.style(theme.inbox)
.highlight_style(theme.selected_item)
.highlight_symbol(highlight_symbol),
inbox,
buf,
&mut state,
);
let mut scrollbar_state = ScrollbarState::default()
.content_length(EMAILS.len())
.position(selected_index);
Scrollbar::default()
.begin_symbol(None)
.end_symbol(None)
.track_symbol(None)
.thumb_symbol("▐")
.render(inbox, buf, &mut scrollbar_state);
}
fn render_email(selected_index: usize, area: Rect, buf: &mut Buffer) {
let theme = THEME.email;
let email = EMAILS.get(selected_index);
let block = Block::new()
.style(theme.body)
.padding(Padding::new(2, 2, 0, 0))
.borders(Borders::TOP)
.border_type(BorderType::Thick);
let inner = block.inner(area);
block.render(area, buf);
if let Some(email) = email {
let vertical = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]);
let [headers_area, body_area] = vertical.areas(inner);
let headers = vec![
Line::from(vec![
"From: ".set_style(theme.header),
email.from.set_style(theme.header_value),
]),
Line::from(vec![
"Subject: ".set_style(theme.header),
email.subject.set_style(theme.header_value),
]),
"-".repeat(inner.width as usize).dim().into(),
];
Paragraph::new(headers)
.style(theme.body)
.render(headers_area, buf);
let body = email.body.lines().map(Line::from).collect_vec();
Paragraph::new(body)
.style(theme.body)
.render(body_area, buf);
} else {
Paragraph::new("No email selected").render(inner, buf);
}
}
- 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 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
fn render_user_constraints_legend(&self, area: Rect, buf: &mut Buffer) {
let constraints = self.constraints.iter().map(|_| Constraint::Fill(1));
let blocks = Layout::horizontal(constraints).split(area);
for (i, (area, constraint)) in blocks.iter().zip(self.constraints.iter()).enumerate() {
let selected = self.selected_index == i;
ConstraintBlock::new(*constraint, selected, true).render(*area, buf);
}
}
fn render_layout_block(&self, flex: Flex, area: Rect, buf: &mut Buffer) {
let [label_area, axis_area, blocks_area] =
Layout::vertical([Length(1), Max(1), Length(4)]).areas(area);
if label_area.height > 0 {
format!("Flex::{flex:?}").bold().render(label_area, buf);
}
self.axis(area.width).render(axis_area, buf);
let (blocks, spacers) = Layout::horizontal(&self.constraints)
.flex(flex)
.spacing(self.spacing)
.split_with_spacers(blocks_area);
for (i, (area, constraint)) in blocks.iter().zip(self.constraints.iter()).enumerate() {
let selected = self.selected_index == i;
ConstraintBlock::new(*constraint, selected, false).render(*area, buf);
}
for area in spacers.iter() {
SpacerBlock.render(*area, buf);
}
}
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 162 163 164 165 166 167 168 169 170 171 172 173 174
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
// increase the layout cache to account for the number of layout events. This ensures that
// layout is not generally reprocessed on every frame (which would lead to possible janky
// results when there are more than one possible solution to the requested layout). This
// assumes the user changes spacing about a 100 times or so.
let cache_size = EXAMPLE_DATA.len() * SelectedTab::iter().len() * 100;
Layout::init_cache(NonZeroUsize::new(cache_size).unwrap());
while self.is_running() {
terminal.draw(|frame| frame.render_widget(self, frame.area()))?;
self.handle_events()?;
}
Ok(())
}
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?
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
fn draw(frame: &mut Frame, downloads: &Downloads) {
let area = frame.area();
let block = Block::new().title(Line::from("Progress").centered());
frame.render_widget(block, area);
let vertical = Layout::vertical([Constraint::Length(2), Constraint::Length(4)]).margin(1);
let horizontal = Layout::horizontal([Constraint::Percentage(20), Constraint::Percentage(80)]);
let [progress_area, main] = vertical.areas(area);
let [list_area, gauge_area] = horizontal.areas(main);
// total progress
let done = NUM_DOWNLOADS - downloads.pending.len() - downloads.in_progress.len();
#[allow(clippy::cast_precision_loss)]
let progress = LineGauge::default()
.filled_style(Style::default().fg(Color::Blue))
.label(format!("{done}/{NUM_DOWNLOADS}"))
.ratio(done as f64 / NUM_DOWNLOADS as f64);
frame.render_widget(progress, progress_area);
// in progress downloads
let items: Vec<ListItem> = downloads
.in_progress
.values()
.map(|download| {
ListItem::new(Line::from(vec![
Span::raw(symbols::DOT),
Span::styled(
format!(" download {:>2}", download.id),
Style::default()
.fg(Color::LightGreen)
.add_modifier(Modifier::BOLD),
),
Span::raw(format!(
" ({}ms)",
download.started_at.elapsed().as_millis()
)),
]))
})
.collect();
let list = List::new(items);
frame.render_widget(list, list_area);
#[allow(clippy::cast_possible_truncation)]
for (i, (_, download)) in downloads.in_progress.iter().enumerate() {
let gauge = Gauge::default()
.gauge_style(Style::default().fg(Color::Yellow))
.ratio(download.progress / 100.0);
if gauge_area.top().saturating_add(i as u16) > area.bottom() {
continue;
}
frame.render_widget(
gauge,
Rect {
x: gauge_area.left(),
y: gauge_area.top().saturating_add(i as u16),
width: gauge_area.width,
height: 1,
},
);
}
}
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 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
fn render_examples(area: Rect, buf: &mut Buffer, flex: Flex, spacing: u16) {
let heights = EXAMPLE_DATA
.iter()
.map(|(desc, _)| get_description_height(desc) + 4);
let areas = Layout::vertical(heights).flex(Flex::Start).split(area);
for (area, (description, constraints)) in areas.iter().zip(EXAMPLE_DATA.iter()) {
Example::new(constraints, description, flex, spacing).render(*area, buf);
}
}
}
impl Example {
fn new(constraints: &[Constraint], description: &str, flex: Flex, spacing: u16) -> Self {
Self {
constraints: constraints.into(),
description: description.into(),
flex,
spacing,
}
}
}
impl Widget for Example {
fn render(self, area: Rect, buf: &mut Buffer) {
let title_height = get_description_height(&self.description);
let layout = Layout::vertical([Length(title_height), Fill(0)]);
let [title, illustrations] = layout.areas(area);
let (blocks, spacers) = Layout::horizontal(&self.constraints)
.flex(self.flex)
.spacing(self.spacing)
.split_with_spacers(illustrations);
if !self.description.is_empty() {
Paragraph::new(
self.description
.split('\n')
.map(|s| format!("// {s}").italic().fg(tailwind::SLATE.c400))
.map(Line::from)
.collect::<Vec<Line>>(),
)
.render(title, buf);
}
for (block, constraint) in blocks.iter().zip(&self.constraints) {
Self::illustration(*constraint, block.width).render(*block, buf);
}
for spacer in spacers.iter() {
Self::render_spacer(*spacer, buf);
}
}
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
fn render_layout_block(&self, flex: Flex, area: Rect, buf: &mut Buffer) {
let [label_area, axis_area, blocks_area] =
Layout::vertical([Length(1), Max(1), Length(4)]).areas(area);
if label_area.height > 0 {
format!("Flex::{flex:?}").bold().render(label_area, buf);
}
self.axis(area.width).render(axis_area, buf);
let (blocks, spacers) = Layout::horizontal(&self.constraints)
.flex(flex)
.spacing(self.spacing)
.split_with_spacers(blocks_area);
for (i, (area, constraint)) in blocks.iter().zip(self.constraints.iter()).enumerate() {
let selected = self.selected_index == i;
ConstraintBlock::new(*constraint, selected, false).render(*area, buf);
}
for area in spacers.iter() {
SpacerBlock.render(*area, buf);
}
}
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 68 69 70 71 72 73 74 75 76 77 78 79
fn draw(&self, frame: &mut Frame) {
let [title, vertical, horizontal] = Layout::vertical([
Constraint::Length(1),
Constraint::Fill(1),
Constraint::Fill(1),
])
.spacing(1)
.areas(frame.area());
frame.render_widget("Barchart".bold().into_centered_line(), title);
frame.render_widget(vertical_barchart(&self.temperatures), vertical);
frame.render_widget(horizontal_barchart(&self.temperatures), horizontal);
}
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
fn render_layout_blocks(&self, area: Rect, buf: &mut Buffer) {
let [user_constraints, area] = Layout::vertical([Length(3), Fill(1)])
.spacing(1)
.areas(area);
self.render_user_constraints_legend(user_constraints, buf);
let [start, center, end, space_around, space_between] =
Layout::vertical([Length(7); 5]).areas(area);
self.render_layout_block(Flex::Start, start, buf);
self.render_layout_block(Flex::Center, center, buf);
self.render_layout_block(Flex::End, end, buf);
self.render_layout_block(Flex::SpaceAround, space_around, buf);
self.render_layout_block(Flex::SpaceBetween, space_between, buf);
}
fn render_user_constraints_legend(&self, area: Rect, buf: &mut Buffer) {
let constraints = self.constraints.iter().map(|_| Constraint::Fill(1));
let blocks = Layout::horizontal(constraints).split(area);
for (i, (area, constraint)) in blocks.iter().zip(self.constraints.iter()).enumerate() {
let selected = self.selected_index == i;
ConstraintBlock::new(*constraint, selected, true).render(*area, buf);
}
}
fn render_layout_block(&self, flex: Flex, area: Rect, buf: &mut Buffer) {
let [label_area, axis_area, blocks_area] =
Layout::vertical([Length(1), Max(1), Length(4)]).areas(area);
if label_area.height > 0 {
format!("Flex::{flex:?}").bold().render(label_area, buf);
}
self.axis(area.width).render(axis_area, buf);
let (blocks, spacers) = Layout::horizontal(&self.constraints)
.flex(flex)
.spacing(self.spacing)
.split_with_spacers(blocks_area);
for (i, (area, constraint)) in blocks.iter().zip(self.constraints.iter()).enumerate() {
let selected = self.selected_index == i;
ConstraintBlock::new(*constraint, selected, false).render(*area, buf);
}
for area in spacers.iter() {
SpacerBlock.render(*area, buf);
}
}
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
fn render(self, area: Rect, buf: &mut Buffer) {
let title_height = get_description_height(&self.description);
let layout = Layout::vertical([Length(title_height), Fill(0)]);
let [title, illustrations] = layout.areas(area);
let (blocks, spacers) = Layout::horizontal(&self.constraints)
.flex(self.flex)
.spacing(self.spacing)
.split_with_spacers(illustrations);
if !self.description.is_empty() {
Paragraph::new(
self.description
.split('\n')
.map(|s| format!("// {s}").italic().fg(tailwind::SLATE.c400))
.map(Line::from)
.collect::<Vec<Line>>(),
)
.render(title, buf);
}
for (block, constraint) in blocks.iter().zip(&self.constraints) {
Self::illustration(*constraint, block.width).render(*block, buf);
}
for spacer in spacers.iter() {
Self::render_spacer(*spacer, buf);
}
}
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
fn render(self, area: Rect, buf: &mut Buffer) {
RgbSwatch.render(area, buf);
let area = area.inner(Margin {
vertical: 1,
horizontal: 2,
});
Clear.render(area, buf);
let vertical = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
let [inbox, email] = vertical.areas(area);
render_inbox(self.row_index, inbox, buf);
render_email(self.row_index, email, buf);
}
}
fn render_inbox(selected_index: usize, area: Rect, buf: &mut Buffer) {
let vertical = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
let [tabs, inbox] = vertical.areas(area);
let theme = THEME.email;
Tabs::new(vec![" Inbox ", " Sent ", " Drafts "])
.style(theme.tabs)
.highlight_style(theme.tabs_selected)
.select(0)
.divider("")
.render(tabs, buf);
let highlight_symbol = ">>";
let from_width = EMAILS
.iter()
.map(|e| e.from.width())
.max()
.unwrap_or_default();
let items = EMAILS.iter().map(|e| {
let from = format!("{:width$}", e.from, width = from_width).into();
ListItem::new(Line::from(vec![from, " ".into(), e.subject.into()]))
});
let mut state = ListState::default().with_selected(Some(selected_index));
StatefulWidget::render(
List::new(items)
.style(theme.inbox)
.highlight_style(theme.selected_item)
.highlight_symbol(highlight_symbol),
inbox,
buf,
&mut state,
);
let mut scrollbar_state = ScrollbarState::default()
.content_length(EMAILS.len())
.position(selected_index);
Scrollbar::default()
.begin_symbol(None)
.end_symbol(None)
.track_symbol(None)
.thumb_symbol("▐")
.render(inbox, buf, &mut scrollbar_state);
}
fn render_email(selected_index: usize, area: Rect, buf: &mut Buffer) {
let theme = THEME.email;
let email = EMAILS.get(selected_index);
let block = Block::new()
.style(theme.body)
.padding(Padding::new(2, 2, 0, 0))
.borders(Borders::TOP)
.border_type(BorderType::Thick);
let inner = block.inner(area);
block.render(area, buf);
if let Some(email) = email {
let vertical = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]);
let [headers_area, body_area] = vertical.areas(inner);
let headers = vec![
Line::from(vec![
"From: ".set_style(theme.header),
email.from.set_style(theme.header_value),
]),
Line::from(vec![
"Subject: ".set_style(theme.header),
email.subject.set_style(theme.header_value),
]),
"-".repeat(inner.width as usize).dim().into(),
];
Paragraph::new(headers)
.style(theme.body)
.render(headers_area, buf);
let body = email.body.lines().map(Line::from).collect_vec();
Paragraph::new(body)
.style(theme.body)
.render(body_area, buf);
} else {
Paragraph::new("No email selected").render(inner, buf);
}
}
- 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
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
fn calculate_layout(area: Rect) -> (Rect, Vec<Vec<Rect>>) {
let main_layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
let block_layout = Layout::vertical([Constraint::Max(4); 9]);
let [title_area, main_area] = main_layout.areas(area);
let main_areas = block_layout
.split(main_area)
.iter()
.map(|&area| {
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(area)
.to_vec()
})
.collect();
(title_area, main_areas)
}
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
fn draw(frame: &mut Frame) {
let area = frame.area().inner(Margin {
vertical: 1,
horizontal: 1,
});
let mut start = OffsetDateTime::now_local()
.unwrap()
.date()
.replace_month(Month::January)
.unwrap()
.replace_day(1)
.unwrap();
let list = make_dates(start.year());
let rows = Layout::vertical([Constraint::Ratio(1, 3); 3]).split(area);
let cols = rows.iter().flat_map(|row| {
Layout::horizontal([Constraint::Ratio(1, 4); 4])
.split(*row)
.to_vec()
});
for col in cols {
let cal = cals::get_cal(start.month(), start.year(), &list);
frame.render_widget(cal, col);
start = start.replace_month(start.month().next()).unwrap();
}
}
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 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
fn render_layout_block(&self, flex: Flex, area: Rect, buf: &mut Buffer) {
let [label_area, axis_area, blocks_area] =
Layout::vertical([Length(1), Max(1), Length(4)]).areas(area);
if label_area.height > 0 {
format!("Flex::{flex:?}").bold().render(label_area, buf);
}
self.axis(area.width).render(axis_area, buf);
let (blocks, spacers) = Layout::horizontal(&self.constraints)
.flex(flex)
.spacing(self.spacing)
.split_with_spacers(blocks_area);
for (i, (area, constraint)) in blocks.iter().zip(self.constraints.iter()).enumerate() {
let selected = self.selected_index == i;
ConstraintBlock::new(*constraint, selected, false).render(*area, buf);
}
for area in spacers.iter() {
SpacerBlock.render(*area, buf);
}
}
More examples
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
fn render(self, area: Rect, buf: &mut Buffer) {
let title_height = get_description_height(&self.description);
let layout = Layout::vertical([Length(title_height), Fill(0)]);
let [title, illustrations] = layout.areas(area);
let (blocks, spacers) = Layout::horizontal(&self.constraints)
.flex(self.flex)
.spacing(self.spacing)
.split_with_spacers(illustrations);
if !self.description.is_empty() {
Paragraph::new(
self.description
.split('\n')
.map(|s| format!("// {s}").italic().fg(tailwind::SLATE.c400))
.map(Line::from)
.collect::<Vec<Line>>(),
)
.render(title, buf);
}
for (block, constraint) in blocks.iter().zip(&self.constraints) {
Self::illustration(*constraint, block.width).render(*block, buf);
}
for spacer in spacers.iter() {
Self::render_spacer(*spacer, buf);
}
}
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§unsafe fn clone_to_uninit(&self, dst: *mut T)
unsafe fn clone_to_uninit(&self, dst: *mut T)
clone_to_uninit
)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