pub struct Frame<'a> { /* private fields */ }
Expand description
A consistent view into the terminal state for rendering a single frame.
This is obtained via the closure argument of Terminal::draw
. It is used to render widgets
to the terminal and control the cursor position.
The changes drawn to the frame are applied only to the current Buffer
. After the closure
returns, the current buffer is compared to the previous buffer and only the changes are applied
to the terminal. This avoids drawing redundant cells.
Implementations§
source§impl Frame<'_>
impl Frame<'_>
sourcepub fn size(&self) -> Rect
pub fn size(&self) -> Rect
The size of the current frame
This is guaranteed not to change during rendering, so may be called multiple times.
If your app listens for a resize event from the backend, it should ignore the values from the event for any calculations that are used to render the current frame and use this value instead as this is the size of the buffer that is used to render the current frame.
Examples found in repository?
More examples
- examples/colors_rgb.rs
- examples/table.rs
- examples/demo2/destroy.rs
- examples/demo2/app.rs
- examples/chart.rs
- examples/canvas.rs
- examples/custom_widget.rs
- examples/demo/ui.rs
- examples/barchart.rs
- examples/calendar.rs
- examples/popup.rs
- examples/ratatui-logo.rs
- examples/panic.rs
- examples/sparkline.rs
- examples/block.rs
- examples/inline.rs
- examples/paragraph.rs
- examples/user_input.rs
- examples/layout.rs
- examples/scrollbar.rs
sourcepub fn render_widget<W: Widget>(&mut self, widget: W, area: Rect)
pub fn render_widget<W: Widget>(&mut self, widget: W, area: Rect)
Render a Widget
to the current buffer using Widget::render
.
Usually the area argument is the size of the current frame or a sub-area of the current
frame (which can be obtained using Layout
to split the total area).
§Example
let block = Block::default();
let area = Rect::new(0, 0, 5, 5);
frame.render_widget(block, area);
Examples found in repository?
More examples
- examples/block.rs
- examples/colors_rgb.rs
- examples/demo2/app.rs
- examples/table.rs
- examples/canvas.rs
- examples/custom_widget.rs
- examples/demo/ui.rs
- examples/barchart.rs
- examples/calendar.rs
- examples/popup.rs
- examples/ratatui-logo.rs
- examples/panic.rs
- examples/sparkline.rs
- examples/chart.rs
- examples/inline.rs
- examples/paragraph.rs
- examples/user_input.rs
- examples/layout.rs
- examples/scrollbar.rs
sourcepub fn render_widget_ref<W: WidgetRef>(&mut self, widget: W, area: Rect)
Available on crate feature unstable-widget-ref
only.
pub fn render_widget_ref<W: WidgetRef>(&mut self, widget: W, area: Rect)
unstable-widget-ref
only.Render a WidgetRef
to the current buffer using WidgetRef::render_ref
.
Usually the area argument is the size of the current frame or a sub-area of the current
frame (which can be obtained using Layout
to split the total area).
§Example
let block = Block::default();
let area = Rect::new(0, 0, 5, 5);
frame.render_widget_ref(block, area);
§Availability
This API is marked as unstable and is only available when the unstable-widget-ref
crate feature is enabled. This comes with no stability guarantees, and could be changed or removed at any time.
sourcepub fn render_stateful_widget<W>(
&mut self,
widget: W,
area: Rect,
state: &mut W::State
)where
W: StatefulWidget,
pub fn render_stateful_widget<W>(
&mut self,
widget: W,
area: Rect,
state: &mut W::State
)where
W: StatefulWidget,
Render a StatefulWidget
to the current buffer using StatefulWidget::render
.
Usually the area argument is the size of the current frame or a sub-area of the current
frame (which can be obtained using Layout
to split the total area).
The last argument should be an instance of the StatefulWidget::State
associated to the
given StatefulWidget
.
§Example
let mut state = ListState::default().with_selected(Some(1));
let list = List::new(vec![ListItem::new("Item 1"), ListItem::new("Item 2")]);
let area = Rect::new(0, 0, 5, 5);
frame.render_stateful_widget(list, area, &mut state);
Examples found in repository?
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 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
fn render_table(f: &mut Frame, app: &mut App, area: Rect) {
let header_style = Style::default()
.fg(app.colors.header_fg)
.bg(app.colors.header_bg);
let selected_style = Style::default()
.add_modifier(Modifier::REVERSED)
.fg(app.colors.selected_style_fg);
let header = ["Name", "Address", "Email"]
.iter()
.cloned()
.map(Cell::from)
.collect::<Row>()
.style(header_style)
.height(1);
let rows = app.items.iter().enumerate().map(|(i, data)| {
let color = match i % 2 {
0 => app.colors.normal_row_color,
_ => app.colors.alt_row_color,
};
let item = data.ref_array();
item.iter()
.cloned()
.map(|content| Cell::from(Text::from(format!("\n{}\n", content))))
.collect::<Row>()
.style(Style::new().fg(app.colors.row_fg).bg(color))
.height(4)
});
let bar = " █ ";
let t = Table::new(
rows,
[
// + 1 is for padding.
Constraint::Length(app.longest_item_lens.0 + 1),
Constraint::Min(app.longest_item_lens.1 + 1),
Constraint::Min(app.longest_item_lens.2),
],
)
.header(header)
.highlight_style(selected_style)
.highlight_symbol(Text::from(vec![
"".into(),
bar.into(),
bar.into(),
"".into(),
]))
.bg(app.colors.buffer_bg)
.highlight_spacing(HighlightSpacing::Always);
f.render_stateful_widget(t, area, &mut app.state);
}
fn constraint_len_calculator(items: &[Data]) -> (u16, u16, u16) {
let name_len = items
.iter()
.map(Data::name)
.map(UnicodeWidthStr::width)
.max()
.unwrap_or(0);
let address_len = items
.iter()
.map(Data::address)
.flat_map(str::lines)
.map(UnicodeWidthStr::width)
.max()
.unwrap_or(0);
let email_len = items
.iter()
.map(Data::email)
.map(UnicodeWidthStr::width)
.max()
.unwrap_or(0);
(name_len as u16, address_len as u16, email_len as u16)
}
fn render_scrollbar(f: &mut Frame, app: &mut App, area: Rect) {
f.render_stateful_widget(
Scrollbar::default()
.orientation(ScrollbarOrientation::VerticalRight)
.begin_symbol(None)
.end_symbol(None),
area.inner(&Margin {
vertical: 1,
horizontal: 1,
}),
&mut app.scroll_state,
);
}
More examples
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 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
fn ui(f: &mut Frame, app: &mut App) {
let size = f.size();
// Words made "loooong" to demonstrate line breaking.
let s = "Veeeeeeeeeeeeeeeery loooooooooooooooooong striiiiiiiiiiiiiiiiiiiiiiiiiing. ";
let mut long_line = s.repeat(usize::from(size.width) / s.len() + 4);
long_line.push('\n');
let chunks = Layout::vertical([
Constraint::Min(1),
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
])
.split(size);
let text = vec![
Line::from("This is a line "),
Line::from("This is a line ".red()),
Line::from("This is a line".on_dark_gray()),
Line::from("This is a longer line".crossed_out()),
Line::from(long_line.clone()),
Line::from("This is a line".reset()),
Line::from(vec![
Span::raw("Masked text: "),
Span::styled(
Masked::new("password", '*'),
Style::default().fg(Color::Red),
),
]),
Line::from("This is a line "),
Line::from("This is a line ".red()),
Line::from("This is a line".on_dark_gray()),
Line::from("This is a longer line".crossed_out()),
Line::from(long_line.clone()),
Line::from("This is a line".reset()),
Line::from(vec![
Span::raw("Masked text: "),
Span::styled(
Masked::new("password", '*'),
Style::default().fg(Color::Red),
),
]),
];
app.vertical_scroll_state = app.vertical_scroll_state.content_length(text.len());
app.horizontal_scroll_state = app.horizontal_scroll_state.content_length(long_line.len());
let create_block = |title: &'static str| Block::bordered().gray().title(title.bold());
let title = Block::default()
.title("Use h j k l or ◄ ▲ ▼ ► to scroll ".bold())
.title_alignment(Alignment::Center);
f.render_widget(title, chunks[0]);
let paragraph = Paragraph::new(text.clone())
.gray()
.block(create_block("Vertical scrollbar with arrows"))
.scroll((app.vertical_scroll as u16, 0));
f.render_widget(paragraph, chunks[1]);
f.render_stateful_widget(
Scrollbar::default()
.orientation(ScrollbarOrientation::VerticalRight)
.begin_symbol(Some("↑"))
.end_symbol(Some("↓")),
chunks[1],
&mut app.vertical_scroll_state,
);
let paragraph = Paragraph::new(text.clone())
.gray()
.block(create_block(
"Vertical scrollbar without arrows, without track symbol and mirrored",
))
.scroll((app.vertical_scroll as u16, 0));
f.render_widget(paragraph, chunks[2]);
f.render_stateful_widget(
Scrollbar::default()
.orientation(ScrollbarOrientation::VerticalLeft)
.symbols(scrollbar::VERTICAL)
.begin_symbol(None)
.track_symbol(None)
.end_symbol(None),
chunks[2].inner(&Margin {
vertical: 1,
horizontal: 0,
}),
&mut app.vertical_scroll_state,
);
let paragraph = Paragraph::new(text.clone())
.gray()
.block(create_block(
"Horizontal scrollbar with only begin arrow & custom thumb symbol",
))
.scroll((0, app.horizontal_scroll as u16));
f.render_widget(paragraph, chunks[3]);
f.render_stateful_widget(
Scrollbar::default()
.orientation(ScrollbarOrientation::HorizontalBottom)
.thumb_symbol("🬋")
.end_symbol(None),
chunks[3].inner(&Margin {
vertical: 0,
horizontal: 1,
}),
&mut app.horizontal_scroll_state,
);
let paragraph = Paragraph::new(text.clone())
.gray()
.block(create_block(
"Horizontal scrollbar without arrows & custom thumb and track symbol",
))
.scroll((0, app.horizontal_scroll as u16));
f.render_widget(paragraph, chunks[4]);
f.render_stateful_widget(
Scrollbar::default()
.orientation(ScrollbarOrientation::HorizontalBottom)
.thumb_symbol("░")
.track_symbol(Some("─")),
chunks[4].inner(&Margin {
vertical: 0,
horizontal: 1,
}),
&mut app.horizontal_scroll_state,
);
}
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 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
fn draw_charts(f: &mut Frame, app: &mut App, area: Rect) {
let constraints = if app.show_chart {
vec![Constraint::Percentage(50), Constraint::Percentage(50)]
} else {
vec![Constraint::Percentage(100)]
};
let chunks = Layout::horizontal(constraints).split(area);
{
let chunks = Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(chunks[0]);
{
let chunks =
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(chunks[0]);
// Draw tasks
let tasks: Vec<ListItem> = app
.tasks
.items
.iter()
.map(|i| ListItem::new(vec![text::Line::from(Span::raw(*i))]))
.collect();
let tasks = List::new(tasks)
.block(Block::default().borders(Borders::ALL).title("List"))
.highlight_style(Style::default().add_modifier(Modifier::BOLD))
.highlight_symbol("> ");
f.render_stateful_widget(tasks, chunks[0], &mut app.tasks.state);
// Draw logs
let info_style = Style::default().fg(Color::Blue);
let warning_style = Style::default().fg(Color::Yellow);
let error_style = Style::default().fg(Color::Magenta);
let critical_style = Style::default().fg(Color::Red);
let logs: Vec<ListItem> = app
.logs
.items
.iter()
.map(|&(evt, level)| {
let s = match level {
"ERROR" => error_style,
"CRITICAL" => critical_style,
"WARNING" => warning_style,
_ => info_style,
};
let content = vec![text::Line::from(vec![
Span::styled(format!("{level:<9}"), s),
Span::raw(evt),
])];
ListItem::new(content)
})
.collect();
let logs = List::new(logs).block(Block::default().borders(Borders::ALL).title("List"));
f.render_stateful_widget(logs, chunks[1], &mut app.logs.state);
}
let barchart = BarChart::default()
.block(Block::default().borders(Borders::ALL).title("Bar chart"))
.data(&app.barchart)
.bar_width(3)
.bar_gap(2)
.bar_set(if app.enhanced_graphics {
symbols::bar::NINE_LEVELS
} else {
symbols::bar::THREE_LEVELS
})
.value_style(
Style::default()
.fg(Color::Black)
.bg(Color::Green)
.add_modifier(Modifier::ITALIC),
)
.label_style(Style::default().fg(Color::Yellow))
.bar_style(Style::default().fg(Color::Green));
f.render_widget(barchart, chunks[1]);
}
if app.show_chart {
let x_labels = vec![
Span::styled(
format!("{}", app.signals.window[0]),
Style::default().add_modifier(Modifier::BOLD),
),
Span::raw(format!(
"{}",
(app.signals.window[0] + app.signals.window[1]) / 2.0
)),
Span::styled(
format!("{}", app.signals.window[1]),
Style::default().add_modifier(Modifier::BOLD),
),
];
let datasets = vec![
Dataset::default()
.name("data2")
.marker(symbols::Marker::Dot)
.style(Style::default().fg(Color::Cyan))
.data(&app.signals.sin1.points),
Dataset::default()
.name("data3")
.marker(if app.enhanced_graphics {
symbols::Marker::Braille
} else {
symbols::Marker::Dot
})
.style(Style::default().fg(Color::Yellow))
.data(&app.signals.sin2.points),
];
let chart = Chart::new(datasets)
.block(
Block::default()
.title(Span::styled(
"Chart",
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
))
.borders(Borders::ALL),
)
.x_axis(
Axis::default()
.title("X Axis")
.style(Style::default().fg(Color::Gray))
.bounds(app.signals.window)
.labels(x_labels),
)
.y_axis(
Axis::default()
.title("Y Axis")
.style(Style::default().fg(Color::Gray))
.bounds([-20.0, 20.0])
.labels(vec![
Span::styled("-20", Style::default().add_modifier(Modifier::BOLD)),
Span::raw("0"),
Span::styled("20", Style::default().add_modifier(Modifier::BOLD)),
]),
);
f.render_widget(chart, chunks[1]);
}
}
sourcepub fn render_stateful_widget_ref<W>(
&mut self,
widget: W,
area: Rect,
state: &mut W::State
)where
W: StatefulWidgetRef,
Available on crate feature unstable-widget-ref
only.
pub fn render_stateful_widget_ref<W>(
&mut self,
widget: W,
area: Rect,
state: &mut W::State
)where
W: StatefulWidgetRef,
unstable-widget-ref
only.Render a StatefulWidgetRef
to the current buffer using
StatefulWidgetRef::render_ref
.
Usually the area argument is the size of the current frame or a sub-area of the current
frame (which can be obtained using Layout
to split the total area).
The last argument should be an instance of the StatefulWidgetRef::State
associated to
the given StatefulWidgetRef
.
§Example
let mut state = ListState::default().with_selected(Some(1));
let list = List::new(vec![ListItem::new("Item 1"), ListItem::new("Item 2")]);
let area = Rect::new(0, 0, 5, 5);
frame.render_stateful_widget_ref(list, area, &mut state);
§Availability
This API is marked as unstable and is only available when the unstable-widget-ref
crate feature is enabled. This comes with no stability guarantees, and could be changed or removed at any time.
sourcepub fn set_cursor(&mut self, x: u16, y: u16)
pub fn set_cursor(&mut self, x: u16, y: u16)
After drawing this frame, make the cursor visible and put it at the specified (x, y) coordinates. If this method is not called, the cursor will be hidden.
Note that this will interfere with calls to Terminal::hide_cursor()
,
Terminal::show_cursor()
, and Terminal::set_cursor()
. Pick one of the APIs and stick
with it.
Examples found in repository?
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 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
fn ui(f: &mut Frame, app: &App) {
let vertical = Layout::vertical([
Constraint::Length(1),
Constraint::Length(3),
Constraint::Min(1),
]);
let [help_area, input_area, messages_area] = vertical.areas(f.size());
let (msg, style) = match app.input_mode {
InputMode::Normal => (
vec![
"Press ".into(),
"q".bold(),
" to exit, ".into(),
"e".bold(),
" to start editing.".bold(),
],
Style::default().add_modifier(Modifier::RAPID_BLINK),
),
InputMode::Editing => (
vec![
"Press ".into(),
"Esc".bold(),
" to stop editing, ".into(),
"Enter".bold(),
" to record the message".into(),
],
Style::default(),
),
};
let text = Text::from(Line::from(msg)).patch_style(style);
let help_message = Paragraph::new(text);
f.render_widget(help_message, help_area);
let input = Paragraph::new(app.input.as_str())
.style(match app.input_mode {
InputMode::Normal => Style::default(),
InputMode::Editing => Style::default().fg(Color::Yellow),
})
.block(Block::default().borders(Borders::ALL).title("Input"));
f.render_widget(input, input_area);
match app.input_mode {
InputMode::Normal =>
// Hide the cursor. `Frame` does this by default, so we don't need to do anything here
{}
InputMode::Editing => {
// Make the cursor visible and ask ratatui to put it at the specified coordinates after
// rendering
f.set_cursor(
// Draw the cursor at the current position in the input field.
// This position is can be controlled via the left and right arrow key
input_area.x + app.cursor_position as u16 + 1,
// Move one line down, from the border to the input line
input_area.y + 1,
)
}
}
let messages: Vec<ListItem> = app
.messages
.iter()
.enumerate()
.map(|(i, m)| {
let content = Line::from(Span::raw(format!("{i}: {m}")));
ListItem::new(content)
})
.collect();
let messages =
List::new(messages).block(Block::default().borders(Borders::ALL).title("Messages"));
f.render_widget(messages, messages_area);
}
sourcepub fn buffer_mut(&mut self) -> &mut Buffer
pub fn buffer_mut(&mut self) -> &mut Buffer
Gets the buffer that this Frame
draws into as a mutable reference.
Examples found in repository?
16 17 18 19 20 21 22 23 24 25 26 27
pub fn destroy(frame: &mut Frame<'_>) {
let frame_count = frame.count().saturating_sub(DELAY);
if frame_count == 0 {
return;
}
let area = frame.size();
let buf = frame.buffer_mut();
drip(frame_count, area, buf);
text(frame_count, area, buf);
}
sourcepub fn count(&self) -> usize
pub fn count(&self) -> usize
Returns the current frame count.
This method provides access to the frame count, which is a sequence number indicating how many frames have been rendered up to (but not including) this one. It can be used for purposes such as animation, performance tracking, or debugging.
Each time a frame has been rendered, this count is incremented, providing a consistent way to reference the order and number of frames processed by the terminal. When count reaches its maximum value (usize::MAX), it wraps around to zero.
This count is particularly useful when dealing with dynamic content or animations where the state of the display changes over time. By tracking the frame count, developers can synchronize updates or changes to the content with the rendering process.
§Examples
let current_count = frame.count();
println!("Current frame count: {}", current_count);
Examples found in repository?
16 17 18 19 20 21 22 23 24 25 26 27
pub fn destroy(frame: &mut Frame<'_>) {
let frame_count = frame.count().saturating_sub(DELAY);
if frame_count == 0 {
return;
}
let area = frame.size();
let buf = frame.buffer_mut();
drip(frame_count, area, buf);
text(frame_count, area, buf);
}