Skip to main content

Terminal

Struct Terminal 

Source
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>

Source

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.

Source

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-uiPerfOverlayApp 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.

Source

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.

Source

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.

Source

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.

Source

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>

Source

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.

Source

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.

Source

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>

Source

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();
}
Source

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>

Source

pub fn new(backend: B) -> Self

Create a terminal with the given backend. Grid dimensions are queried from the backend.

§Panics

Panics if the backend reports a width of 0 (e.g. a minimized window, or a surface queried before the first configure); see Grid::new. A reported height of 0 is fine.

Source

pub const fn surface(&mut self) -> Surface<'_>

A Surface scoped to the whole terminal on layer 0, for manual control over presenting (e.g. partial updates spread across several calls, or conditionally skipping a present). Most callers want draw instead.

Source

pub const fn size(&self) -> Size

Returns the current grid dimensions.

Source

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.

Source

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.

Source

pub fn set_cursor_visible(&mut self, visible: bool)

Show or hide the cursor.

Forwards to Cursor::set_cursor_visible on the backend.

Source

pub fn set_cursor_position(&mut self, position: Pos)

Move the cursor to a position.

Forwards to Cursor::set_cursor_position on the backend.

Source

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.

Source

pub const fn grid(&self) -> &Grid

Returns a reference to the current grid.

Source

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.

Source

pub const fn backend(&self) -> &B

Returns a reference to the backend.

Source

pub const fn backend_mut(&mut self) -> &mut B

Returns a mutable reference to the backend.

Trait Implementations§

Source§

impl<B: Backend> Debug for Terminal<B>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Prints size and present_count; elides the frame buffers and the backend.

Auto Trait Implementations§

§

impl<B> Freeze for Terminal<B>
where B: Freeze,

§

impl<B> RefUnwindSafe for Terminal<B>
where B: RefUnwindSafe,

§

impl<B> Send for Terminal<B>
where B: Send,

§

impl<B> Sync for Terminal<B>
where B: Sync,

§

impl<B> Unpin for Terminal<B>
where B: Unpin,

§

impl<B> UnsafeUnpin for Terminal<B>
where B: UnsafeUnpin,

§

impl<B> UnwindSafe for Terminal<B>
where B: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.