pub struct Terminal<B: Backend> { /* private fields */ }Expand description
A double-buffered terminal generic over a Backend.
Owns the current and previous frame grids and the backend’s lifecycle (resize, present,
events). Drawing itself goes entirely through Surface: see draw for the
common case (draw a frame, then present it) and surface for manual control
over presenting.
§Out-of-bounds drawing
Surface clips any write that falls outside its own area rather than panicking; see
Surface’s own “out-of-bounds drawing” documentation.
§Examples
use retroglyph_core::backend::Headless;
use retroglyph_core::color::Color;
use retroglyph_core::terminal::Terminal;
let mut term = Terminal::new(Headless::new(20, 5));
term.draw(|surface| {
surface.put((2, 1), '@', retroglyph_core::color::Style::new().fg(Color::GREEN));
})
.unwrap();Implementations§
Source§impl<B: Backend> Terminal<B>
impl<B: Backend> Terminal<B>
Sourcepub fn poll(&mut self, timeout: Duration) -> Option<Event>
pub fn poll(&mut self, timeout: Duration) -> Option<Event>
Polls for an input event, waiting up to timeout.
If an event was previously buffered by has_input, it is
returned immediately. Otherwise, the backend is polled for a new event.
Event::Resize events arriving from the backend are automatically applied: both
grids are resized before the event is returned to the caller, so the game loop can
immediately redraw at the new size. An event coming back off this terminal’s own queue
(from requeue_events, or buffered by
wait_for_input) was already applied when it first entered, so
it is returned as-is rather than resized again. See poll_backend.
Sourcepub fn requeue_events(&mut self, events: impl IntoIterator<Item = Event>)
pub fn requeue_events(&mut self, events: impl IntoIterator<Item = Event>)
Hands events back to this terminal’s own queue, in order, so a later
poll/drain_events/drain_events_into
call yields them again before the backend is polled for anything new.
This is the supported way for a wrapper that drains events to intercept some of them
(e.g. retroglyph-ui’ PerfOverlayApp filtering out its own toggle key) to give
the rest back: it goes through Terminal’s own queue, never a backend-specific input
path, so it works identically on every Backend regardless of how (or whether) that
backend implements Input::push_event.
Sourcepub fn drain_events(&mut self) -> impl Iterator<Item = Event> + use<'_, B>
pub fn drain_events(&mut self) -> impl Iterator<Item = Event> + use<'_, B>
Drains all available events without blocking.
Returns an iterator that yields every pending event: the internal queued event
followed by all events buffered in the backend. The iterator polls the backend
with zero timeout repeatedly until None is returned.
This is needed for frame-based game loops (e.g. software backend + WASM, where
frames are gated by requestAnimationFrame). Multiple keypresses can arrive
between frames; draining all of them ensures accumulated input doesn’t replay in
slow motion.
Crossterm and headless backends can also use this, but the single-event poll
pattern works for them because their loops aren’t frame-capped.
§One-shot semantics
The returned iterator borrows self and drains the queue as it is consumed: the
first caller to iterate it gets every pending event, and a second, independent
call to drain_events afterward gets nothing. If more than one subsystem needs
this frame’s input (e.g. persistent chrome and an active screen), collect once
and share the collected events by reference, or use
drain_events_into to drain into a reusable buffer
instead of allocating a fresh Vec every frame.
Sourcepub fn drain_events_into(&mut self, buf: &mut Vec<Event>)
pub fn drain_events_into(&mut self, buf: &mut Vec<Event>)
Drains all available events without blocking, appending them to buf.
buf is cleared first, then filled with every pending event in the same order
drain_events would yield them. Unlike drain_events, the
borrow of self ends when this call returns, so the terminal is free to draw or
be polled again afterward, and the caller can hand buf to multiple consumers by
shared reference without materializing a new Vec every frame.
This is the same shape as std::io::Read::read_to_end: allocate the buffer once
at startup, reuse it every frame, and let this method manage its contents.
Sourcepub fn has_input(&mut self) -> bool
pub fn has_input(&mut self) -> bool
Checks if a pending input event is available without blocking.
If an event is already buffered, returns true. Otherwise, polls the backend
with zero timeout. If the backend returns an event, it is stored in the internal
buffer and true is returned; otherwise, returns false.
Sourcepub fn wait_for_input(&mut self, timeout: Duration) -> bool
pub fn wait_for_input(&mut self, timeout: Duration) -> bool
Blocks until an input event is available or timeout elapses, without consuming it.
Like has_input, a discovered event is buffered internally so a
subsequent poll, has_input, or
drain_events call still observes it: this method only answers
“did something happen”, it never hands the event to the caller. That’s what lets a driver
loop block between frames without stealing the event the app’s own update reads; see
run_blocking_with’s use of this for Flow::Idle.
Returns true if an event arrived within timeout, false if timeout elapsed with
nothing pending. Pass Duration::MAX to block indefinitely.
Backends that never block (e.g. Headless, which returns
immediately regardless of timeout; see Input::poll_event)
return promptly rather than actually waiting; this method is a real wait only on
backends that genuinely block (crossterm, window).
Source§impl<B: Backend> Terminal<B>
impl<B: Backend> Terminal<B>
Sourcepub fn draw(
&mut self,
f: impl FnOnce(&mut Surface<'_>),
) -> Result<(), <B as Output>::Error>
pub fn draw( &mut self, f: impl FnOnce(&mut Surface<'_>), ) -> Result<(), <B as Output>::Error>
Draws one frame: f gets a Surface scoped to the whole terminal on layer 0, then the
frame is presented (see present) once f returns.
This is the common entry point for drawing: a caller that draws every frame regardless of
whether anything changed calls this once per frame. A caller that only wants to redraw
when its own state changed should gate the call to draw itself (e.g. if state.changed() { term.draw(|s| render(s, &state))?; }) rather than rely on draw/
present to no-op.
§Errors
Propagates errors from present.
Sourcepub const fn present_count(&self) -> u64
pub const fn present_count(&self) -> u64
Number of times present has been called so far.
Wraps on overflow; intended for detecting whether present was called at all between two
points in time (compare a saved count against the current one), not as a precise total.
Embedding drivers (e.g. retroglyph-window’s windowed drivers) use this to decide whether
application code already presented during a frame, so they can skip a redundant
driver-side present.
Sourcepub fn present(&mut self) -> Result<(), <B as Output>::Error>
pub fn present(&mut self) -> Result<(), <B as Output>::Error>
Present the current frame: computes the diff against the previous frame, sends changed
cells to the backend, flushes, then swaps buffers. Always presents unconditionally, even
if nothing was drawn since the last call; most callers want draw instead
of calling this directly.
When the backend requires a full frame (see
crate::backend::Output::needs_full_frame), all cells from every allocated layer are
sent rather than just the diff, so pixel-based backends can clear and
redraw to avoid orphaned pixels from sub-cell offsets.
After a present, the new current buffer is cleared so the next frame starts empty.
Callers should not draw into a frame and skip presenting it: the next draw
call starts from an empty grid regardless.
§Immediate mode
This is an immediate-mode API (the same trade ratatui makes): the
current buffer is wiped after every present, so each frame must redraw
its entire scene from scratch by default. retain_layer is the
escape hatch: it makes one specific layer’s last-presented content stand in for a redraw,
so the app can skip regenerating it. The diff only bounds what is sent to the backend
(terminal or pixel I/O); it does not bound the CPU cost of your redraw, except for a
layer marked via retain_layer.
§Panics
Never panics in practice: retained_layers and dropped_layers are indexed by u8 layer
id and grown only up to idx + 1 for idx = usize::from(layer_id) in
retain_layer/drop_layer, so their length is
always at most 256 and every index encountered here fits in u8.
§Errors
Propagates errors from the backend’s draw_layers or
flush operations. Either failure returns before the
current/previous buffers are swapped, so the cells from the failed frame stay marked
dirty in previous and are resent the next time present succeeds. current is still
cleared, same as on success, so the caller doesn’t need to redraw anything to recover:
just call draw/present again, and the next frame starts from an empty grid like any
other.
Source§impl<B: Backend> Terminal<B>
impl<B: Backend> Terminal<B>
Sourcepub fn retain_layer(&mut self, layer: impl Into<u8>)
pub fn retain_layer(&mut self, layer: impl Into<u8>)
Marks layer so the next present treats it as unchanged instead of
requiring the app to have redrawn it: present copies layer’s last-presented content
back into current before diffing, so the diff (and thus the backend) sees no change on
it, whatever the app did or didn’t draw into it this frame.
Call this before draw/present on a frame where a
layer’s content is known not to have changed (e.g. the camera didn’t move since the last
frame, so a cached map layer is still correct) and skip drawing it that frame. This is
the actual point of the method: present’s diff already keeps the
backend from re-receiving unchanged cells, but the app still has to regenerate them
every frame to produce a buffer worth diffing. Marking a layer retained lets the app skip
that regeneration too, at the cost of a per-cell copy handled internally (a flat, verbatim
replace, far cheaper than most real content generation).
This is a one-shot opt-in, not a sticky mode: it only affects the very next present, so
a caller that wants a layer retained for several frames in a row must call this again
before each of them. resize also clears any pending retention.
§Examples
use retroglyph_core::backend::Headless;
use retroglyph_core::surface::Layer;
use retroglyph_core::terminal::Terminal;
let mut term = Terminal::new(Headless::new(10, 5));
let camera_moved = false;
if camera_moved {
term.draw(|s| s.on_tier(Layer::World).print((0, 0), "map", Default::default()))
.unwrap();
} else {
// The camera didn't move this frame: skip regenerating the map layer.
term.retain_layer(Layer::World);
term.draw(|s| s.on_tier(Layer::Hud).print((0, 1), "HP: 10", Default::default()))
.unwrap();
}Sourcepub fn drop_layer(&mut self, layer: impl Into<u8>)
pub fn drop_layer(&mut self, layer: impl Into<u8>)
Marks layer to be deallocated, forgetting it was ever drawn to.
Grid::max_layer only grows on write, so a terminal that ever draws to a layer above 0,
even for a single frame, stays on present’s flatten path for the rest of
the process, whether or not that layer is still in use (retroglyph#1028). This is the
explicit escape hatch: call it once a layer’s content is truly done (a one-off overlay
dismissed, a transient effect finished), and once every layer above 0 has been dropped,
present falls back onto its single-layer fast path.
layer’s content is cleared immediately (so this frame’s diff still tells the backend to
erase whatever it last showed there, exactly as if the app had simply stopped drawing to
it), but the underlying buffer is only freed, and max_layer only allowed to fall, once
the next present has sent that erase and no longer needs the layer for
its diff. Deallocating any earlier would make the layer invisible to present’s diff
(which only walks current’s own allocated layers), silently dropping the erase instead of
sending it.
This is a one-shot request, not a sticky mode: unlike retain_layer,
which defers one frame’s redraw, this defers the actual deallocation, so drawing to layer
again before the next present (undoing the drop) cancels it instead of losing that draw:
the layer stays allocated and behaves like any other write. Any pending
retain_layer call for layer is cleared immediately, though:
retaining content that no longer exists would resurrect it on the next present regardless
of whether the drop itself goes through.
§Panics
Panics if layer is 0: layer 0 is always allocated and can never be dropped.
§Examples
use retroglyph_core::backend::Headless;
use retroglyph_core::surface::Layer;
use retroglyph_core::terminal::Terminal;
let mut term = Terminal::new(Headless::new(10, 5));
term.draw(|s| s.on_tier(Layer::Hud).print((0, 0), "Paused", Default::default()))
.unwrap();
term.drop_layer(Layer::Hud);
term.present().unwrap(); // Sends the erase, then frees the layer.
assert_eq!(term.grid().max_layer(), 0);Source§impl<B: Backend> Terminal<B>
impl<B: Backend> Terminal<B>
Sourcepub const fn area(&self) -> Rect
pub const fn area(&self) -> Rect
Returns the full drawing surface as a Rect at the origin.
Equivalent to Rect::new(0, 0, width, height). Handy for passing the
whole terminal to layout helpers or region-based drawing.
Sourcepub fn resize(&mut self, width: u16, height: u16)
pub fn resize(&mut self, width: u16, height: u16)
Resize both grids to width × height cells.
Unlike new, a width of 0 does not panic here: a terminal can be resized
down to zero columns (a minimized or zero-width window) and back up again, and the
single-layer present path keeps working at zero width. A height of 0 is likewise fine.
Content within the overlapping region is preserved in the current grid.
The previous grid is cleared so the next present redraws
the entire new surface rather than diffing stale data.
§Panics
A zero-width terminal only supports the single-layer fast path. If any layer above 0 is
allocated when present runs at zero width, present panics while
building its flatten buffers (see Grid::new); either avoid multi-layer drawing at zero
width, or drop_layer every layer above 0 first.
Sourcepub fn set_cursor_visible(&mut self, visible: bool)
pub fn set_cursor_visible(&mut self, visible: bool)
Show or hide the cursor.
Forwards to Cursor::set_cursor_visible on
the backend.
Sourcepub fn set_cursor_position(&mut self, position: Pos)
pub fn set_cursor_position(&mut self, position: Pos)
Move the cursor to a position.
Forwards to Cursor::set_cursor_position
on the backend.
Sourcepub fn set_cursor_style(&mut self, style: CursorStyle)
pub fn set_cursor_style(&mut self, style: CursorStyle)
Set the cursor’s shape (and blink behavior).
Forwards to Cursor::set_cursor_style on the
backend.
Sourcepub const fn grid_mut(&mut self) -> &mut Grid
pub const fn grid_mut(&mut self) -> &mut Grid
Returns a mutable reference to the current grid, with no clipping or layer scoping.
Escape hatch for whole-grid operations that don’t fit Surface’s clipped,
single-layer model (e.g. Grid::blit). Most drawing should go through
draw/surface instead.
Sourcepub const fn backend_mut(&mut self) -> &mut B
pub const fn backend_mut(&mut self) -> &mut B
Returns a mutable reference to the backend.