pub struct RenderState<'alloc>(/* private fields */);Expand description
Represents the state required to render a visible screen (a viewport) of a terminal instance.
This is stateful and optimized for repeated updates from a single terminal instance and only updating dirty regions of the screen.
The key design principle of this API is that it only needs read/write access to the terminal instance during the update call. This allows the render state to minimally impact terminal IO performance and also allows the renderer to be safely multi-threaded (as long as a lock is held during the update call to ensure exclusive access to the terminal instance).
The basic usage of this API is:
- Create an empty render state
- Update it from a terminal instance whenever you need.
- Read from the render state to get the data needed to draw your frame.
§Dirty Tracking
Dirty tracking is a key feature of the render state that allows renderers to efficiently determine what parts of the screen have changed and only redraw changed regions.
The render state API keeps track of dirty state at two independent layers: a global dirty state that indicates whether the entire frame is clean, partially dirty, or fully dirty, and a per-row dirty state that allows tracking which rows in a partially dirty frame have changed.
The user of the render state API is expected to unset both of these. The update call does not unset dirty state, it only updates it.
An extremely important detail: setting one dirty state doesn’t unset the other. For example, setting the global dirty state to false does not reset the row-level dirty flags. So, the caller of the render state API must be careful to manage both layers of dirty state correctly.
§Examples
§Creating and updating render state
// Create a terminal and render state, then update the render state
// from the terminal. The render state captures a snapshot of everything
// needed to draw a frame.
use libghostty_vt::{Terminal, TerminalOptions, RenderState};
let mut terminal = Terminal::new(TerminalOptions {
cols: 40,
rows: 5,
max_scrollback: 10000,
}).unwrap();
let mut render_state = RenderState::new().unwrap();
// Feed some styled content into the terminal.
terminal.vt_write(b"Hello, \x1b[1;32mworld\x1b[0m!\r\n");
terminal.vt_write(b"\x1b[4munderlined\x1b[0m text\r\n");
terminal.vt_write(b"\x1b[38;2;255;128;0morange\x1b[0m\r\n");
assert!(render_state.update(&terminal).is_ok());§Checking dirty state
// Check the global dirty state to decide how much work the renderer
// needs to do. After rendering, reset it to false.
let snapshot = render_state.update(&terminal).unwrap();
match snapshot.dirty().unwrap() {
Dirty::Clean => println!("Frame is clean, nothing to draw."),
Dirty::Partial => println!("Partial redraw needed."),
Dirty::Full => println!("Full redraw needed."),
}§Reading colors
// Retrieve colors (background, foreground, palette) from the render
// state. These are needed to resolve palette-indexed cell colors.
let snapshot = render_state.update(&terminal).unwrap();
let colors = snapshot.colors().unwrap();
println!(
"Background: {:02x}{:02x}{:02x}",
colors.background.r, colors.background.g, colors.background.b
);
println!(
"Foreground: {:02x}{:02x}{:02x}",
colors.background.r, colors.background.g, colors.background.b
);§Reading cursor state
// Read cursor position and visual style from the render state.
use libghostty_vt::render::CursorViewport;
let snapshot = render_state.update(&terminal).unwrap();
if snapshot.cursor_visible().unwrap() {
if let Some(CursorViewport { x, y, .. }) = snapshot.cursor_viewport().unwrap() {
let style = snapshot.cursor_visual_style().unwrap();
println!("Cursor at ({x}, {y}), style {style:?}");
}
}§Iterating rows and cells
// Iterate rows via the row iterator. For each dirty row, iterate its
// cells, read codepoints/graphemes and styles, and emit ANSI-colored
// output as a simple "renderer".
use libghostty_vt::render::{RowIterator, CellIterator};
// During setup:
let mut rows = RowIterator::new().unwrap();
let mut cells = CellIterator::new().unwrap();
// On each frame:
let snapshot = render_state.update(&terminal).unwrap();
let mut row_iter = rows.update(&snapshot);
let mut row_index = 0;
while let Some(row) = row_iter.next() {
// Check per-row dirty state; a real renderer would skip clean rows.
print!(
"Row {row_index} [{}]",
if row.dirty().unwrap() { "dirty" } else { "clean" }
);
// Get cells for this row (reuses the same cells handle).
let mut cell_iter = cells.update(&row);
while let Some(cell) = cell_iter.next() {
let graphemes = cell.graphemes().unwrap();
println!("{:?}", &graphemes);
}
row_index += 1;
println!()
}Implementations§
Source§impl<'alloc> RenderState<'alloc>
impl<'alloc> RenderState<'alloc>
Sourcepub fn new_with_alloc<'ctx: 'alloc, Ctx>(
alloc: &'alloc Allocator<'ctx, Ctx>,
) -> Result<Self>
pub fn new_with_alloc<'ctx: 'alloc, Ctx>( alloc: &'alloc Allocator<'ctx, Ctx>, ) -> Result<Self>
Create a new render state instance with a custom allocator.
See the crate-level documentation regarding custom memory management and lifetimes.