# retroglyph-core - Complete API Documentation > A 2D pseudographic terminal library -- core types, no backend **Version:** 0.0.0 **Authors:** Matan Lurey **License:** MIT **Repository:** https://github.com/crates-lurey-io/retroglyph **Keywords:** roguelike, terminal, grid, gamedev Generated: 2026-08-05 20:21:09 UTC Created by: [cargo-llms-txt](https://github.com/masinc/cargo-llms-txt) ## Table of Contents ### src/app.rs - pub enum Flow - pub struct Frame - pub trait App - pub fn run_blocking - pub struct RunOptions - impl RunOptions - impl Default for RunOptions - pub fn run_blocking_with - impl App for tests::Counter - impl App for tests::AlwaysIdle - impl App for tests::DrawsAndExits - impl App for tests::IdleThenExit - impl App for tests::ObservesQueuedEventAfterIdle ### src/symbols/mod.rs - pub mod bar - pub mod block - pub mod border - pub mod braille - pub mod line - pub use subcell::{Glyph, HALF_BLOCKS, Pixel, QUADRANTS, SEXTANTS, quantize_half_block, quantize_quadrant, quantize_sextant} - pub struct BorderSet - pub struct LineSet ### src/symbols/block.rs - pub const FULL - pub const SEVEN_EIGHTHS - pub const THREE_QUARTERS - pub const FIVE_EIGHTHS - pub const HALF - pub const THREE_EIGHTHS - pub const ONE_QUARTER - pub const ONE_EIGHTH ### src/symbols/line.rs - pub const NORMAL - pub const DOUBLE - pub const THICK ### src/symbols/braille.rs - pub const BLANK - pub const DOT_1 - pub const DOT_2 - pub const DOT_3 - pub const DOT_4 - pub const DOT_5 - pub const DOT_6 - pub const DOT_7 - pub const DOT_8 - pub const DOTS - pub fn glyph ### src/symbols/bar.rs - pub const ONE_EIGHTH - pub const ONE_QUARTER - pub const THREE_EIGHTHS - pub const HALF - pub const FIVE_EIGHTHS - pub const THREE_QUARTERS - pub const SEVEN_EIGHTHS - pub const FULL - pub const NINE_LEVELS ### src/symbols/border.rs - pub const PLAIN - pub const ROUNDED - pub const DOUBLE - pub const THICK ### src/symbols/subcell.rs - pub type Pixel - pub const HALF_BLOCKS - pub const QUADRANTS - pub const SEXTANTS - pub struct Glyph - pub fn quantize_half_block - pub fn quantize_quadrant - pub fn quantize_sextant ### src/terminal/mod.rs - pub struct Terminal - impl Terminal - impl core::fmt::Debug for Terminal ### src/terminal/present.rs - impl Terminal - impl tests::FlushOnceFailing - impl Output for tests::FlushOnceFailing - impl Input for tests::FlushOnceFailing - impl Cursor for tests::FlushOnceFailing - impl Output for tests::NeedsFullFrameWithoutCompositing - impl Input for tests::NeedsFullFrameWithoutCompositing - impl Cursor for tests::NeedsFullFrameWithoutCompositing - impl tests::CompositingBackend - impl Output for tests::CompositingBackend - impl Input for tests::CompositingBackend - impl Cursor for tests::CompositingBackend - impl Output for tests::TogglingCompositor - impl Input for tests::TogglingCompositor - impl Cursor for tests::TogglingCompositor ### src/terminal/input.rs - impl Terminal - impl tests::ResizeCounting - impl Output for tests::ResizeCounting - impl Input for tests::ResizeCounting - impl Cursor for tests::ResizeCounting ### src/terminal/retain.rs - impl Terminal - impl tests::CompositingBackend - impl Output for tests::CompositingBackend - impl Input for tests::CompositingBackend - impl Cursor for tests::CompositingBackend ### src/lib.rs - pub mod app - pub mod backend - pub mod color - pub mod dev - pub mod event - pub mod frames - pub mod grid - pub mod layout - pub mod math - pub mod surface - pub mod symbols - pub mod terminal - pub mod testing - pub mod text - pub mod tile ### src/color/mod.rs - pub use ansi::{AnsiColor, InvalidAnsiIndex, Quantize} - pub use parse::ParseColorError - pub use style::Style - pub use tint::Tint - pub enum Color ### src/color/tint.rs - pub enum Tint - impl Tint ### src/color/style.rs - pub struct Style - impl Style ### src/color/ansi.rs - pub enum Quantize - pub enum AnsiColor - impl AnsiColor - pub struct InvalidAnsiIndex - impl core::fmt::Display for InvalidAnsiIndex - impl core::error::Error for InvalidAnsiIndex - impl TryFrom for AnsiColor ### src/color/named.rs - impl Color ### src/color/convert.rs - impl Color ### src/color/parse.rs - impl core::fmt::Display for Color - pub struct ParseColorError - impl core::fmt::Display for ParseColorError - impl core::error::Error for ParseColorError - impl core::str::FromStr for Color - impl serde::Serialize for Color - impl serde::Deserialize for Color ### src/testing/mod.rs - pub mod conformance - pub const STEP_DELTA - pub const DEFAULT_MAX_STEPS - pub struct TestHarness - impl TestHarness - pub enum RunError - impl fmt::Display for RunError - impl core::error::Error for RunError - impl App for tests::Clicker ### src/testing/conformance.rs - pub fn fnv1a - pub trait Observable - pub fn assert_output_contract - pub fn assert_cursor_contract - pub fn assert_cursor_style_contract - pub fn assert_input_contract - impl tests::HeadlessObserver - impl Output for tests::HeadlessObserver - impl Cursor for tests::HeadlessObserver - impl Observable for tests::HeadlessObserver ### src/layout/mod.rs - pub use align::{HAlign, VAlign} - pub use text_layout::TextLayout - pub use word_wrap::wrap ### src/layout/word_wrap.rs - pub fn wrap ### src/layout/align.rs - pub enum HAlign - impl HAlign - pub enum VAlign - impl VAlign ### src/layout/text_layout.rs - pub struct TextLayout - impl TextLayout ### src/math.rs - pub fn mul_add - pub fn round - pub fn sin - pub fn cos - pub fn powf - pub fn exp ### src/dev.rs - pub enum BuildMode - impl BuildMode - pub const DEV - dev_only! ### src/tile.rs - pub struct Tile - impl Default for Tile - impl Tile ### src/surface/mod.rs - pub use styled::StyledSurface - pub struct Surface - pub enum Layer - impl Layer - impl From for u8 - impl Surface ### src/surface/styled.rs - pub struct StyledSurface - impl StyledSurface ### src/surface/geometry.rs - impl Surface ### src/surface/draw/mod.rs - impl Surface ### src/surface/draw/spans.rs - impl Surface ### src/surface/draw/cells.rs - impl Surface ### src/surface/draw/text.rs - impl Surface ### src/event/mod.rs - pub use key::{KeyCode, KeyEvent, KeyEventKind, KeyLocation, KeyModifiers, KeyState, ModifierKey} - pub use mouse::{MouseButton, MouseEvent, MouseEventKind, PhysicalPos} - pub enum SystemTheme - pub enum Event - pub fn coalesces_with ### src/event/mouse.rs - pub type PhysicalPos - pub enum MouseButton - pub enum MouseEventKind - pub struct MouseEvent - impl MouseEvent ### src/event/key.rs - pub struct KeyModifiers - impl KeyModifiers - impl BitOr for KeyModifiers - impl BitOrAssign for KeyModifiers - impl BitAnd for KeyModifiers - impl BitAndAssign for KeyModifiers - impl Not for KeyModifiers - pub enum ModifierKey - pub enum KeyCode - pub enum KeyEventKind - pub enum KeyLocation - pub struct KeyEvent - impl KeyEvent - pub struct KeyState - impl KeyState - impl core::hash::Hasher for tests::TestHasher ### src/frames/mod.rs - pub use clock::FrameClock - pub use stats::FrameStats ### src/frames/clock.rs - pub struct FrameClock - impl FrameClock ### src/frames/stats.rs - pub struct FrameStats - impl Default for FrameStats - impl FrameStats ### src/grid/mod.rs - pub use ixy::HasSize - pub enum BlendMode - impl BlendMode - pub type Size - pub type Pos - pub type Rect - pub struct Offset - impl Offset - impl From for Offset - impl From for Unknown - impl TileExtra - impl LayerBuf - pub struct Grid - impl Grid ### src/grid/diff.rs - impl Grid - impl Iterator for LayerDiff ### src/grid/api.rs - impl Grid ### src/grid/spans.rs - impl Grid ### src/grid/trait_impls.rs - impl Index for Grid - impl IndexMut for Grid - impl fmt::Display for Grid - impl fmt::Debug for Grid ### src/grid/layers/mod.rs - impl Grid ### src/grid/layers/tint.rs - impl Grid ### src/grid/layers/flatten.rs - impl Grid ### src/grid/layers/blit.rs - impl Grid ### src/text.rs - pub fn width - pub fn width_usize - pub fn char_width - pub fn split_at_width - pub fn truncate_measured - pub struct Span - impl Span - impl From for Span - pub struct Line - impl Line - impl From for Line - impl From for Line - impl From for Line - impl From for Line - spans! ### src/backend/mod.rs - pub mod headless - pub use headless::Headless - pub trait BackendError - impl BackendError for core::convert::Infallible - impl BackendError for std::io::Error - pub struct DrawCell - impl DrawCell - pub trait Output - pub trait Input - pub trait Cursor - pub enum CursorStyle - pub trait Backend - impl Backend for T ### src/backend/headless.rs - pub struct Headless - impl Headless - impl Output for Headless - impl Input for Headless - impl Cursor for Headless --- ## README.md ### retroglyph-core A 2D pseudographic terminal library -- core types, no backend Part of the [retroglyph](https://github.com/crates-lurey-io/retroglyph) workspace. --- ## src/app.rs ### Flow ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Flow { Continue, Idle, Exit, } ``` Whether the game loop should continue or stop after a frame, and whether that frame renders. ### Frame ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Frame { pub delta: Duration, pub frame: u64, } ``` Per-frame context handed to [`App::update`](crate::app::App::update). ### App ```rust pub trait App { fn update(&mut self, term: &mut Terminal, frame: &Frame) -> Flow; } ``` The per-frame update contract for a game. Implement this once, generically over the backend, to run everywhere: ``` use retroglyph_core::app::{App, Flow, Frame}; use retroglyph_core::backend::Backend; use retroglyph_core::color::Style; use retroglyph_core::terminal::Terminal; struct MyGame; impl App for MyGame { fn update(&mut self, term: &mut Terminal, _frame: &Frame) -> Flow { term.surface().put((0, 0), '@', Style::default()); Flow::Exit } } ``` ### run_blocking ```rust pub fn run_blocking(term: Terminal, app: A) -> Result<(), B::Error> where B: Backend, A: App ``` Drive an [`App`](crate::app::App) with a blocking, event-driven loop until it returns [`Flow::Exit`](crate::app::Flow::Exit). Generic over the backend, so it powers every non-inverted backend (`Crossterm` in `retroglyph-crossterm`, [`Headless`](crate::backend::Headless)) with no per-backend loop code. Inverted backends (software/winit) provide their own driver. The terminal is owned and dropped when the loop exits, so backend teardown (for example crossterm's terminal restore) runs on the way out. See [`App::update`](crate::app::App::update) for the present/idle contract this and every other driver follows. Equivalent to `run_blocking_with(term, app, RunOptions::default())`: on [`Flow::Idle`](crate::app::Flow::Idle), blocks on input rather than calling `update` again immediately, so a turn-based app that's idle most of the time costs approximately nothing. Use [`run_blocking_with`](crate::app::run_blocking_with) with [`RunOptions::animated`](crate::app::RunOptions::animated) for a continuously-rendering app instead. #### Errors Returns the backend's error if the automatic `present()` call fails. The loop stops and the terminal is dropped (running backend teardown) before the error is returned. ### RunOptions ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct RunOptions { } ``` Options controlling [`run_blocking_with`](crate::app::run_blocking_with)'s pacing and idle behavior. ### impl RunOptions ```rust impl RunOptions { pub fn animated(target_fps: u32) -> Self; pub fn with_target_fps(self, target_fps: u32) -> Self; pub fn target_fps(&self) -> Option; pub fn event_driven(self, event_driven: bool) -> Self; pub fn is_event_driven(&self) -> bool; pub fn with_idle_wake(self, idle_wake: Duration) -> Self; pub fn idle_wake(&self) -> Option; } ``` ### impl Default for RunOptions ```rust impl Default for RunOptions { } ``` ### run_blocking_with ```rust pub fn run_blocking_with(term: Terminal, app: A, options: RunOptions) -> Result<(), B::Error> where B: Backend, A: App ``` Drive an [`App`](crate::app::App) with a blocking loop until it returns [`Flow::Exit`](crate::app::Flow::Exit), paced by `options`. The zero-config [`run_blocking`](crate::app::run_blocking) is equivalent to `run_blocking_with(term, app, RunOptions::default())`. Pass [`RunOptions::animated`](crate::app::RunOptions::animated) for a continuously-rendering loop capped at a fixed rate instead, using a [`FrameClock`](crate::frames::FrameClock) internally so `update` is called at even intervals rather than however fast the host can spin. With [`RunOptions::is_event_driven`](crate::app::RunOptions::is_event_driven) `true` (the default), [`Flow::Idle`](crate::app::Flow::Idle) blocks the loop on input (via [`Terminal::wait_for_input`](crate::terminal::Terminal::wait_for_input)) instead of calling `update` again immediately: an idle app has nothing new to show, so there is no reason to burn CPU polling it at all, let alone faster than any configured rate. With `event_driven` `false`, an idle loop still waits out the remainder of the current `target_fps` interval (if set) before calling `update` again, rather than looping immediately, but never blocks on input. #### Errors Returns the backend's error if the automatic `present()` call fails. The loop stops and the terminal is dropped (running backend teardown) before the error is returned. #### Panics Panics if `options.target_fps` is `Some(0)`: pacing at a `FrameClock` internally, which requires a non-zero rate (see [`FrameClock::new`](crate::frames::FrameClock::new)). ### impl App for Counter ```rust impl App for Counter { } ``` ### impl App for AlwaysIdle ```rust impl App for AlwaysIdle { } ``` ### impl App for DrawsAndExits ```rust impl App for DrawsAndExits { } ``` ### impl App for IdleThenExit ```rust impl App for IdleThenExit { } ``` ### impl App for ObservesQueuedEventAfterIdle ```rust impl App for ObservesQueuedEventAfterIdle { } ``` ## src/symbols/mod.rs ### subcell::{Glyph, HALF_BLOCKS, Pixel, QUADRANTS, SEXTANTS, quantize_half_block, quantize_quadrant, quantize_sextant} ```rust pub use subcell::{Glyph, HALF_BLOCKS, Pixel, QUADRANTS, SEXTANTS, quantize_half_block, quantize_quadrant, quantize_sextant}; ``` ### BorderSet ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct BorderSet { pub top_left: char, pub top_right: char, pub bottom_left: char, pub bottom_right: char, pub horizontal: char, pub vertical: char, } ``` The six glyphs that make up a single-style box border. Field names match position, not weight: [`border::PLAIN`], [`border::ROUNDED`], [`border::DOUBLE`], and [`border::THICK`] are all the same shape, drawn with different line weights and corner styles. ### LineSet ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct LineSet { pub horizontal: char, pub vertical: char, pub cross: char, pub vertical_left: char, pub vertical_right: char, pub horizontal_down: char, pub horizontal_up: char, } ``` The seven glyphs needed to draw a gridline that can cross, tee, or run straight through a cell, as used by table/grid dividers rather than an outer [`BorderSet`] frame. ## src/symbols/block.rs ### FULL ```rust pub const FULL: char ``` A fully filled cell. ### SEVEN_EIGHTHS ```rust pub const SEVEN_EIGHTHS: char ``` Filled 7/8 from the left. ### THREE_QUARTERS ```rust pub const THREE_QUARTERS: char ``` Filled 3/4 from the left. ### FIVE_EIGHTHS ```rust pub const FIVE_EIGHTHS: char ``` Filled 5/8 from the left. ### HALF ```rust pub const HALF: char ``` Filled 1/2 from the left. ### THREE_EIGHTHS ```rust pub const THREE_EIGHTHS: char ``` Filled 3/8 from the left. ### ONE_QUARTER ```rust pub const ONE_QUARTER: char ``` Filled 1/4 from the left. ### ONE_EIGHTH ```rust pub const ONE_EIGHTH: char ``` Filled 1/8 from the left. ## src/symbols/line.rs ### NORMAL ```rust pub const NORMAL: LineSet ``` Single-line gridline characters (`─│┼┤├┬┴`). ### DOUBLE ```rust pub const DOUBLE: LineSet ``` Double-line gridline characters (`═║╬╣╠╦╩`). ### THICK ```rust pub const THICK: LineSet ``` Heavy (thick) gridline characters (`━┃╋┫┣┳┻`). `horizontal`/`vertical` are the same glyphs as [`super::border::THICK`]'s and share its notdef gap. The 4 tees and the cross (`┫┣┳┻╋`) have no glyph in any font this crate bundles either; drawing with `THICK` through `retroglyph_window`'s fullest bundled `FontChain` falls back to the notdef substitute for all 7 entries. Supply a font with real heavy-line glyphs to draw this set as intended. ## src/symbols/braille.rs ### BLANK ```rust pub const BLANK: char ``` The empty braille cell (no dots set), `⠀` (U+2800, distinct from a plain space). ### DOT_1 ```rust pub const DOT_1: u8 ``` Dot at column 0, row 0. ### DOT_2 ```rust pub const DOT_2: u8 ``` Dot at column 0, row 1. ### DOT_3 ```rust pub const DOT_3: u8 ``` Dot at column 0, row 2. ### DOT_4 ```rust pub const DOT_4: u8 ``` Dot at column 1, row 0. ### DOT_5 ```rust pub const DOT_5: u8 ``` Dot at column 1, row 1. ### DOT_6 ```rust pub const DOT_6: u8 ``` Dot at column 1, row 2. ### DOT_7 ```rust pub const DOT_7: u8 ``` Dot at column 0, row 3. ### DOT_8 ```rust pub const DOT_8: u8 ``` Dot at column 1, row 3. ### DOTS ```rust pub const DOTS: [[u8; N]; N] ``` The 2x4 dot-position table, indexed `[row][col]`, giving the bit for each cell in the braille dot grid. ### glyph ```rust pub fn glyph(pattern: u8) -> char ``` The glyph for `pattern`, a bitmask of the eight `DOT_*` constants (or values from [`DOTS`]) OR'd together. Every value of `pattern` maps to a valid glyph: `U+2800..=U+28FF` contains no surrogate code points, so this never falls back to a placeholder. ## src/symbols/bar.rs ### ONE_EIGHTH ```rust pub const ONE_EIGHTH: char ``` Filled 1/8 from the bottom. ### ONE_QUARTER ```rust pub const ONE_QUARTER: char ``` Filled 1/4 from the bottom. ### THREE_EIGHTHS ```rust pub const THREE_EIGHTHS: char ``` Filled 3/8 from the bottom. ### HALF ```rust pub const HALF: char ``` Filled 1/2 from the bottom. ### FIVE_EIGHTHS ```rust pub const FIVE_EIGHTHS: char ``` Filled 5/8 from the bottom. ### THREE_QUARTERS ```rust pub const THREE_QUARTERS: char ``` Filled 3/4 from the bottom. ### SEVEN_EIGHTHS ```rust pub const SEVEN_EIGHTHS: char ``` Filled 7/8 from the bottom. ### FULL ```rust pub const FULL: char ``` A fully filled cell. ### NINE_LEVELS ```rust pub const NINE_LEVELS: [char; N] ``` The nine bar levels from empty to full, indexed `0..=8`: a blank cell, then [`ONE_EIGHTH`] through [`FULL`] in order. Indexing this directly by `round(fraction * 8.0) as usize` turns a `0.0..=1.0` magnitude into the right glyph for one bar column. ## src/symbols/border.rs ### PLAIN ```rust pub const PLAIN: BorderSet ``` Single-line box-drawing characters (`┌─┐│└┘`). ### ROUNDED ```rust pub const ROUNDED: BorderSet ``` [`PLAIN`] with rounded corners (`╭─╮│╰╯`). The 4 corners (`╭╮╰╯`) have no glyph in any font this crate bundles; drawing with `ROUNDED` through `retroglyph_window`'s fullest bundled `FontChain` falls back to the notdef substitute for those 4 entries (`horizontal`/`vertical` are shared with [`PLAIN`], which CP437 does cover). Supply a font with real corner glyphs to draw this set as intended. ### DOUBLE ```rust pub const DOUBLE: BorderSet ``` Double-line box-drawing characters (`╔═╗║╚╝`). ### THICK ```rust pub const THICK: BorderSet ``` Heavy (thick) single-line box-drawing characters (`┏━┓┃┗┛`). No glyph in any font this crate bundles covers any of these 6 entries; drawing with `THICK` through `retroglyph_window`'s fullest bundled `FontChain` falls back to the notdef substitute for the whole set. Supply a font with real heavy-line glyphs to draw this set as intended. ## src/symbols/subcell.rs ### Pixel ```rust pub type Pixel = (u8, u8, u8) ``` A raw 24-bit RGB pixel sample: `(r, g, b)`, one byte per channel. ### HALF_BLOCKS ```rust pub const HALF_BLOCKS: [char; N] ``` The Unicode Block Elements glyphs for a 1-wide x 2-tall pixel block, indexed by a 2-bit pattern (bit 0 = top pixel set, bit 1 = bottom pixel set). ### QUADRANTS ```rust pub const QUADRANTS: [char; N] ``` The 16 quadrant block glyphs for a 2x2 pixel block, indexed by a 4-bit pattern in row-major order (bit 0 = top-left, bit 1 = top-right, bit 2 = bottom-left, bit 3 = bottom-right). Adapted from [ratatui-core's `symbols::pixel::QUADRANTS`][ratatui]. [ratatui]: https://github.com/ratatui/ratatui/blob/main/ratatui-core/src/symbols/pixel.rs ### SEXTANTS ```rust pub const SEXTANTS: [char; N] ``` The 64 sextant glyphs for a 2x3 pixel block. Indexed by a 6-bit pattern in row-major order (bit 0 = top-left, bit 1 = top-right, bit 2 = mid-left, bit 3 = mid-right, bit 4 = bottom-left, bit 5 = bottom-right). Mostly from Unicode's "Symbols for Legacy Computing" block; adapted from [ratatui-core's `symbols::pixel::SEXTANTS`][ratatui]. [ratatui]: https://github.com/ratatui/ratatui/blob/main/ratatui-core/src/symbols/pixel.rs ### Glyph ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Glyph { pub ch: char, pub fg: Color, pub bg: Color, } ``` A posterized pixel block: the best-matching glyph plus its foreground and background colors. The background color is only meaningful for glyphs that don't cover the full cell (anything but `' '` and `'█'`); the foreground color is only meaningful for glyphs other than `' '`. Both are still populated for those edge cases (as the block's overall average color) so a caller never has to special-case `Glyph` before styling a cell with it. ### quantize_half_block ```rust pub fn quantize_half_block(pixels: [impl Trait; N]) -> Glyph ``` Posterizes a 1-wide x 2-tall pixel block (`[top, bottom]`) to `' '`/`▀`/`▄`/`█` plus two representative colors. This is the lowest-fidelity, most compatible option: plain Unicode Block Elements, supported by essentially every monospace terminal font. See the `16_subcell_image` example for `quantize_half_block` in action: . #### Examples ``` use retroglyph_core::symbols::quantize_half_block; let glyph = quantize_half_block([(255, 255, 255), (0, 0, 0)]); assert_eq!(glyph.ch, '▀'); // top half set, bottom clear ``` Never panics: `pixels` is a fixed-size 2-element array, so there is no length to validate and no index into it that can be out of bounds. ### quantize_quadrant ```rust pub fn quantize_quadrant(pixels: [impl Trait; N]) -> Glyph ``` Posterizes a 2x2 pixel block (`[top_left, top_right, bottom_left, bottom_right]`) to one of the 16 quadrant block glyphs plus two representative colors. Doubles both horizontal and vertical resolution over [`quantize_half_block`](crate::symbols::quantize_half_block). On the bundled pixel backends (`retroglyph-software`, `retroglyph-gl`), rendering these glyphs correctly requires a font that actually declares coverage for the quadrant block characters: CP437 has no mapping for them. A font built with `retroglyph_window`'s `BitmapFont::new` (CP437-only) renders every quadrant glyph as a solid block. Supply quadrant coverage by passing either a primary font or a `BitmapFont::with_charset` fallback in a `FontChain` to those backends' `font()` builder method; the glyph then takes the cell's foreground color, which a tileset sprite (the other way to draw a non-CP437 shape) does not. See the `16_subcell_image` example for `quantize_quadrant` in action: . #### Examples ``` use retroglyph_core::symbols::quantize_quadrant; let black = (0, 0, 0); let white = (255, 255, 255); let glyph = quantize_quadrant([black, white, black, black]); assert_eq!(glyph.ch, '▝'); // top-right quadrant ``` See [`quantize_half_block`](crate::symbols::quantize_half_block) for why this never panics. ### quantize_sextant ```rust pub fn quantize_sextant(pixels: [impl Trait; N]) -> Glyph ``` Posterizes a 2-wide x 3-tall pixel block (`[top_left, top_right, mid_left, mid_right, bottom_left, bottom_right]`) to one of the 64 sextant glyphs plus two representative colors. The highest-fidelity option (doubles vertical resolution again over [`quantize_quadrant`](crate::symbols::quantize_quadrant)), and the newest/least universally supported: sextant glyphs come from a 2022 Unicode addition and need a font with "Symbols for Legacy Computing" coverage to render as blocks rather than tofu/replacement characters. Font coverage works the same way as [`quantize_quadrant`](crate::symbols::quantize_quadrant)'s: CP437 has no mapping for sextant glyphs either, so see that function's docs for the `FontChain` setup needed to render them. See the `16_subcell_image` example for `quantize_sextant` in action: . #### Examples ``` use retroglyph_core::symbols::quantize_sextant; let black = (0, 0, 0); let white = (255, 255, 255); let glyph = quantize_sextant([white, black, black, black, black, black]); assert_eq!(glyph.ch, '🬀'); // top-left sextant only ``` See [`quantize_half_block`](crate::symbols::quantize_half_block) for why this never panics. ## src/terminal/mod.rs ### Terminal ```rust pub struct Terminal { } ``` 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`](Self::draw) for the common case (draw a frame, then present it) and [`surface`](Self::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(); ``` ### impl Terminal ```rust impl Terminal { pub fn new(backend: B) -> Self; pub fn surface(&mut self) -> Surface<'_>; pub fn size(&self) -> Size; pub fn area(&self) -> Rect; pub fn resize(&mut self, width: u16, height: u16); pub fn set_cursor_visible(&mut self, visible: bool); pub fn set_cursor_position(&mut self, position: Pos); pub fn set_cursor_style(&mut self, style: CursorStyle); pub fn grid(&self) -> &Grid; pub fn grid_mut(&mut self) -> &mut Grid; pub fn backend(&self) -> &B; pub fn backend_mut(&mut self) -> &mut B; } ``` ### impl core::fmt::Debug for Terminal ```rust impl core::fmt::Debug for Terminal { } ``` ## src/terminal/present.rs ### impl Terminal ```rust impl Terminal { pub fn draw(&mut self, f: impl Trait) -> Result<(), Output::Error>; pub fn present_count(&self) -> u64; pub fn present(&mut self) -> Result<(), Output::Error>; } ``` ### impl FlushOnceFailing ```rust impl FlushOnceFailing { } ``` ### impl Output for FlushOnceFailing ```rust impl Output for FlushOnceFailing { } ``` ### impl Input for FlushOnceFailing ```rust impl Input for FlushOnceFailing { } ``` ### impl Cursor for FlushOnceFailing ```rust impl Cursor for FlushOnceFailing { } ``` ### impl Output for NeedsFullFrameWithoutCompositing ```rust impl Output for NeedsFullFrameWithoutCompositing { } ``` ### impl Input for NeedsFullFrameWithoutCompositing ```rust impl Input for NeedsFullFrameWithoutCompositing { } ``` ### impl Cursor for NeedsFullFrameWithoutCompositing ```rust impl Cursor for NeedsFullFrameWithoutCompositing { } ``` ### impl CompositingBackend ```rust impl CompositingBackend { } ``` ### impl Output for CompositingBackend ```rust impl Output for CompositingBackend { } ``` ### impl Input for CompositingBackend ```rust impl Input for CompositingBackend { } ``` ### impl Cursor for CompositingBackend ```rust impl Cursor for CompositingBackend { } ``` ### impl Output for TogglingCompositor ```rust impl Output for TogglingCompositor { } ``` ### impl Input for TogglingCompositor ```rust impl Input for TogglingCompositor { } ``` ### impl Cursor for TogglingCompositor ```rust impl Cursor for TogglingCompositor { } ``` ## src/terminal/input.rs ### impl Terminal ```rust impl Terminal { pub fn poll(&mut self, timeout: Duration) -> Option; pub fn requeue_events(&mut self, events: impl Trait); pub fn drain_events(&mut self) -> impl Trait; pub fn drain_events_into(&mut self, buf: &mut Vec); pub fn has_input(&mut self) -> bool; pub fn wait_for_input(&mut self, timeout: Duration) -> bool; } ``` ### impl ResizeCounting ```rust impl ResizeCounting { } ``` ### impl Output for ResizeCounting ```rust impl Output for ResizeCounting { } ``` ### impl Input for ResizeCounting ```rust impl Input for ResizeCounting { } ``` ### impl Cursor for ResizeCounting ```rust impl Cursor for ResizeCounting { } ``` ## src/terminal/retain.rs ### impl Terminal ```rust impl Terminal { pub fn retain_layer(&mut self, layer: impl Trait); pub fn drop_layer(&mut self, layer: impl Trait); } ``` ### impl CompositingBackend ```rust impl CompositingBackend { } ``` ### impl Output for CompositingBackend ```rust impl Output for CompositingBackend { } ``` ### impl Input for CompositingBackend ```rust impl Input for CompositingBackend { } ``` ### impl Cursor for CompositingBackend ```rust impl Cursor for CompositingBackend { } ``` ## src/lib.rs ## src/color/mod.rs ### ansi::{AnsiColor, InvalidAnsiIndex, Quantize} ```rust pub use ansi::{AnsiColor, InvalidAnsiIndex, Quantize}; ``` ### parse::ParseColorError ```rust pub use parse::ParseColorError; ``` ### style::Style ```rust pub use style::Style; ``` ### tint::Tint ```rust pub use tint::Tint; ``` ### Color ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Color { Default, Ansi(AnsiColor), Indexed(u8), Rgb { r: u8, g: u8, b: u8 }, } ``` Represents a color in the terminal grid. #### Examples ``` use retroglyph_core::color::Color; let named = Color::GREEN; let rgb = Color::Rgb { r: 255, g: 0, b: 0 }; let indexed = Color::Indexed(42); assert_ne!(named, rgb); assert_ne!(rgb, indexed); ``` ## src/color/tint.rs ### Tint ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Tint { None, Multiply { r: u8, g: u8, b: u8 }, Mix { r: u8, g: u8, b: u8, amount: u8 }, } ``` How a sprite's own pixels are recoloured at draw time. A sprite is composited from the artwork's pixels, and a cell's [`Style::fg`](crate::color::Style::fg) does not touch it (see [`Surface::put_span`](crate::surface::Surface::put_span)). A tint is the separate channel that does, so one piece of artwork can serve a biome variant, a damage flash, or a shadowed copy of itself without a second sprite in the sheet. Pixel backends only. Cell backends have no sprite to recolour and ignore a tint entirely; they draw the cell's glyph in its own [`Style`](crate::color::Style), as always. #### Why not reuse `fg` Tinting a sprite by the cell's foreground colour is what most tileset libraries do, and it works for them because their foreground colour has exactly one job and defaults to white, the identity of a multiply. Neither holds here. [`Color::Default`](crate::color::Color::Default) means "whatever foreground the terminal is configured for", not white, so it has no sensible reading as a modulation value. More importantly, a cell drawn as a sprite by a pixel backend is drawn as an `fg`-coloured *glyph* by a cell backend, and the colour that reads correctly as a solid character is not the colour that reads correctly multiplied onto artwork that already has colour of its own. One field cannot serve both. #### Choosing an operation [`Multiply`](Self::Multiply) is the workhorse and can only darken: every channel scales toward zero. It preserves the artwork's own shading, which is what makes it right for variants of one material (grass to savanna, stone to mossy stone) and for lighting. [`Mix`](Self::Mix) blends toward a colour and can therefore brighten, which multiply cannot express at all. It is also the only one of the two a caller could not approximate for themselves, since doing so needs the sprite's pixels. `Mix` at full strength replaces the artwork's colour outright while keeping its alpha, which is how a white-on-transparent mask sheet gets recoloured. Alpha is never touched by either: a tint changes what the sprite's opaque pixels look like, never which of them are opaque. Compositing and the cell background showing through transparent pixels behave identically tinted or not. #### Scope: what `Tint` is not for `Tint` is per-cell and per-draw, not per-sheet or per-frame. "Is this sheet art or a mask" is a different, fixed-at-load-time question, answered once by `retroglyph_window::tileset::SheetColor` rather than by this type. The two compose instead of collapsing into one flag (see `retroglyph_window::sprite_cache::SpriteTint`, which resolves both in one place), because "is this sheet art or a mask" (fixed when the asset is authored) and "what colour to flash this cell right now" (fixed per frame) are different questions that would conflict if merged into a single `modulate(bool)`-style flag: a sheet declared art-not-mask still needs to be flashable. Frame- or layer-level colour transforms (day/night cycles, fog of war, a "remembered" map render) are not a use case for `Tint` either. Those apply to everything already drawn, every frame, so routing them through per-cell `Tint` would mean writing the same value into a side-table entry for every cell of every layer, every frame: the wrong lever for a screen-wide effect. That is tracked as its own, not-yet-designed concern in retroglyph#562; it is out of scope here. `Tint` is `#[non_exhaustive]` so more operations (add, screen, replace) can be added later without breaking either backend: the GL encoder already falls through to "no recolour" on an operation it does not recognize. #### Examples ``` use retroglyph_core::color::Tint; // Grass artwork, dimmed toward its own shadow. let shadowed = Tint::multiply(128, 128, 128); assert_eq!(shadowed.apply((200, 180, 60)), (100, 90, 30)); // The same pixels, flashed most of the way to white. let hit = Tint::mix(255, 255, 255, 192); assert_eq!(hit.apply((200, 180, 60)), (241, 236, 207)); // The default costs nothing and changes nothing. assert_eq!(Tint::None.apply((200, 180, 60)), (200, 180, 60)); ``` ### impl Tint ```rust impl Tint { pub fn multiply(r: u8, g: u8, b: u8) -> Self; pub fn mix(r: u8, g: u8, b: u8, amount: u8) -> Self; pub fn is_identity(self) -> bool; pub fn apply(self, rgb: (u8, u8, u8)) -> (u8, u8, u8); pub fn apply_rgb888(self, px: Rgb888) -> Rgb888; pub fn multiply_color(c: crate::color::Color, default: (u8, u8, u8)) -> Self; } ``` ## src/color/style.rs ### Style ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct Style { } ``` A style consisting of foreground and background color. #### Examples ``` use retroglyph_core::color::{Color, Style}; let style = Style::new().fg(Color::GREEN).bg(Color::BLACK); assert_eq!(style.foreground(), Color::GREEN); assert_eq!(style.background(), Color::BLACK); ``` ### impl Style ```rust impl Style { pub fn new() -> Self; pub fn fg(self, color: Color) -> Self; pub fn bg(self, color: Color) -> Self; pub fn foreground(&self) -> Color; pub fn background(&self) -> Color; pub fn patch(self, other: Self) -> Self; pub fn reset_fg(self) -> Self; pub fn reset_bg(self) -> Self; } ``` ## src/color/ansi.rs ### Quantize ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Quantize { Perceptual, Euclidean, } ``` The distance metric [`Color::to_indexed_with`](super::Color::to_indexed_with) and [`Color::to_ansi_with`](super::Color::to_ansi_with) use to find a palette entry's nearest neighbour. #### Examples ``` use retroglyph_core::color::{Color, Quantize}; let salmon = Color::Rgb { r: 250, g: 128, b: 114 }; assert_eq!(salmon.to_indexed_with(Quantize::Perceptual), Color::Indexed(210)); assert_eq!(salmon.to_indexed_with(Quantize::Euclidean), Color::Indexed(209)); ``` ### AnsiColor ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum AnsiColor { Black, Red, Green, Yellow, Blue, Magenta, Cyan, White, BrightBlack, BrightRed, BrightGreen, BrightYellow, BrightBlue, BrightMagenta, BrightCyan, BrightWhite, } ``` Standard 16-color ANSI palette. Prefer `Ansi` colors when you want your game to respect the user's terminal color theme (e.g., Solarized, Nord, or custom themes). Use `Rgb` for fixed colors that must appear identical regardless of the user's terminal configuration. #### Examples ``` use retroglyph_core::color::{AnsiColor, Color}; let color = Color::Ansi(AnsiColor::Green); assert_eq!(AnsiColor::Green.to_index(), 2); assert_eq!(color, Color::GREEN); ``` ### impl AnsiColor ```rust impl AnsiColor { pub fn to_index(self) -> u8; pub fn to_rgb(self) -> (u8, u8, u8); } ``` ### InvalidAnsiIndex ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct InvalidAnsiIndex(pub u8); ``` Error returned when a `u8` value has no corresponding [`AnsiColor`](crate::color::AnsiColor). ### impl core::fmt::Display for InvalidAnsiIndex ```rust impl core::fmt::Display for InvalidAnsiIndex { } ``` ### impl core::error::Error for InvalidAnsiIndex ```rust impl core::error::Error for InvalidAnsiIndex { } ``` ### impl TryFrom for AnsiColor ```rust impl TryFrom for AnsiColor { } ``` ## src/color/named.rs ### impl Color ```rust impl Color { pub fn from_named(name: &str) -> Option; pub fn from_hex(hex: &str) -> Option; } ``` ## src/color/convert.rs ### impl Color ```rust impl Color { pub const BLACK: Type; pub const RED: Type; pub const GREEN: Type; pub const YELLOW: Type; pub const BLUE: Type; pub const MAGENTA: Type; pub const CYAN: Type; pub const WHITE: Type; pub const BRIGHT_BLACK: Type; pub const BRIGHT_RED: Type; pub const BRIGHT_GREEN: Type; pub const BRIGHT_YELLOW: Type; pub const BRIGHT_BLUE: Type; pub const BRIGHT_MAGENTA: Type; pub const BRIGHT_CYAN: Type; pub const BRIGHT_WHITE: Type; pub fn resolve_rgb(self, default: (u8, u8, u8)) -> (u8, u8, u8); pub fn to_srgb(self) -> Option; pub fn from_srgb(srgb: Srgb) -> Self; pub fn lerp(a: Self, b: Self, t: f32) -> Self; pub fn lighten(self, amount: f32) -> Self; pub fn darken(self, amount: f32) -> Self; pub fn saturate(self, amount: f32) -> Self; pub fn desaturate(self, amount: f32) -> Self; pub fn complement(self) -> Self; pub fn to_indexed(self) -> Self; pub fn to_indexed_with(self, metric: Quantize) -> Self; pub fn to_ansi(self) -> Self; pub fn to_ansi_with(self, metric: Quantize) -> Self; } ``` ## src/color/parse.rs ### impl core::fmt::Display for Color ```rust impl core::fmt::Display for Color { } ``` ### ParseColorError ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ParseColorError; ``` Error returned when parsing a [`Color`](crate::color::Color) from a string fails. ### impl core::fmt::Display for ParseColorError ```rust impl core::fmt::Display for ParseColorError { } ``` ### impl core::error::Error for ParseColorError ```rust impl core::error::Error for ParseColorError { } ``` ### impl core::str::FromStr for Color ```rust impl core::str::FromStr for Color { } ``` ### impl serde::Serialize for Color ```rust impl serde::Serialize for Color { } ``` ### impl serde::Deserialize<'de> for Color ```rust impl<'de> serde::Deserialize<'de> for Color { } ``` ## src/color/palette_oklab.rs ## src/testing/mod.rs ### STEP_DELTA ```rust pub const STEP_DELTA: Duration ``` Fixed per-frame delta [`TestHarness::step`](crate::testing::TestHarness::step) hands to [`App::update`](crate::app::App::update). Headless tests have no wall clock; this exists only so [`Frame::delta`](crate::app::Frame::delta)-driven code (tweens, [`FrameClock`](crate::frames::FrameClock)) advances instead of stalling. 16ms is one frame at ~60fps; the value is otherwise arbitrary, but it is load-bearing for any test that counts steps to reach an animation state: a duration-D animation finishes in `ceil(D / 16ms)` steps, so changing this shifts those step counts. ### DEFAULT_MAX_STEPS ```rust pub const DEFAULT_MAX_STEPS: u32 ``` Default step budget for [`TestHarness::run`](crate::testing::TestHarness::run) before it treats a non-draining event queue as a stuck app and panics. Sized to comfortably clear any single queued gesture (a click is two events, the two-frame rule costs a frame each), with headroom, while still failing fast on an app that never drains its input. The exact value is picked by feel, not measured; a test with a legitimately long settle should call [`settle`](crate::testing::TestHarness::settle) with a larger budget rather than raise this shared default. ### TestHarness ```rust pub struct TestHarness { } ``` Drives an [`App`](crate::app::App) against a [`Headless`](crate::backend::Headless) backend: queues synthetic input, steps frames, and reads back the rendered view. #### The two-frame rule A press and a release queued together resolve a frame later than the same gesture arriving from real input, because hit-testing (e.g. `retroglyph-ui`' `Interaction`) snapshots the *previous* frame's pointer state before this frame's queued events are applied. [`click`]( Self::click) queues both events for you, but resolving them still costs two frames: call [`run`](Self::run) or [`settle`](Self::settle) after queuing input, not a single [`step`](Self::step). #### Presenting [`step`](Self::step) presents automatically: skipped on [`Flow::Idle`](crate::app::Flow::Idle), skipped as a no-op if `update` already presented (mirroring [`run_blocking`](crate::app::run_blocking)'s own behavior). Nothing queued is visible in [`view`](Self::view) until a `step` call has run. #### Examples ``` use retroglyph_core::testing::TestHarness; use retroglyph_core::app::{App, Flow, Frame}; use retroglyph_core::backend::Backend; use retroglyph_core::color::Style; use retroglyph_core::terminal::Terminal; struct Counter(u32); impl App for Counter { fn update(&mut self, term: &mut Terminal, frame: &Frame) -> Flow { if term.has_input() { self.0 += 1; } term.surface() .put((0, 0), char::from_digit(self.0, 10).unwrap_or('?'), Style::default()); if frame.frame > 10 { Flow::Exit } else { Flow::Continue } } } let mut harness = TestHarness::new(10, 1); let mut app = Counter(0); harness.key(retroglyph_core::event::KeyCode::Char(' ')); harness.run(&mut app); assert_eq!(app.0, 1); assert!(harness.view().starts_with('1')); ``` ### impl TestHarness ```rust impl TestHarness { pub fn new(width: u16, height: u16) -> Self; pub fn push_event(&mut self, event: Event); pub fn click(&mut self, x: u16, y: u16); pub fn click_button(&mut self, x: u16, y: u16, button: MouseButton); pub fn mouse_move(&mut self, x: u16, y: u16); pub fn key(&mut self, code: KeyCode); pub fn key_with(&mut self, code: KeyCode, modifiers: KeyModifiers); pub fn resize(&mut self, width: u16, height: u16); pub fn step(&mut self, app: &mut A) -> Flow; pub fn settle(&mut self, app: &mut A, max_steps: u32) -> Result; pub fn run(&mut self, app: &mut A) -> u32; pub fn run_steps(&mut self, app: &mut A, steps: u32); pub fn view(&self) -> String; pub fn term(&self) -> &Terminal; pub fn term_mut(&mut self) -> &mut Terminal; } ``` ### RunError ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RunError { ExceededMaxSteps { max_steps: u32 }, } ``` Error returned by [`TestHarness::settle`](crate::testing::TestHarness::settle) when the queue never drained within the step budget. ### impl fmt::Display for RunError ```rust impl fmt::Display for RunError { } ``` ### impl core::error::Error for RunError ```rust impl core::error::Error for RunError { } ``` ### impl App for Clicker ```rust impl App for Clicker { } ``` ## src/testing/conformance.rs ### fnv1a ```rust pub fn fnv1a(bytes: &[u8]) -> u64 ``` Hashes `bytes` with FNV-1a (64-bit). `core::hash::Hasher`/`std::hash::DefaultHasher` are either the wrong shape (no portable digest guarantee) or unavailable at all under `no_std`, so [`Observable`] implementors get a small, dependency-free digest instead. Not cryptographic, and not guaranteed stable across `retroglyph-core` versions: only ever compared within a single test run, never persisted. ### Observable ```rust pub trait Observable: Output { fn snapshot(&mut self) -> u64; } ``` A backend that can report a digest of what changed since the last call. See the module docs for why "since the last call", not the whole history or the whole current state, is the contract every implementation has to meet. ### assert_output_contract ```rust pub fn assert_output_contract(make: F) ``` Drives `B` through [`Output`](crate::backend::Output)'s obligations: `make` must return a fresh backend sized to the requested [`Size`](crate::grid::Size), with no cells drawn yet. #### Panics Panics on the first obligation `B` violates, or if any `Output` call returns `Err` (`Observable` backends in this workspace are all infallible; a fallible one that fails here has a bug this harness cannot usefully attribute, since it isn't the obligation under test). ### assert_cursor_contract ```rust pub fn assert_cursor_contract(make: F) ``` Drives `B` through [`Cursor`](crate::backend::Cursor)'s tracked-cursor obligation. External writes (an app calling [`Cursor::set_cursor_position`](crate::backend::Cursor::set_cursor_position) between two draws) must not desync a backend's internal cursor tracking from where the cursor actually is (retroglyph#713). #### Panics Panics if the tracked cursor desyncs, or if any `Output` call returns `Err`. ### assert_cursor_style_contract ```rust pub fn assert_cursor_style_contract(make: F) ``` Drives `B` through [`Cursor::set_cursor_style`]'s obligation: each [`CursorStyle`] variant must have its own distinct, observable effect (retroglyph#920). `crossterm` and `terminal-wasm` each map every `CursorStyle` variant to a DECSCUSR parameter via their own independent `match`, with no shared source of truth between the two; this assertion doesn't compare backends against each other (their emitted bytes differ by design), but it does pin, once per backend, that the six variants aren't accidentally collapsed onto fewer than six distinct behaviors (e.g. two arms sharing a fallthrough). #### Panics Panics if two distinct `CursorStyle` variants produce the same digest, or if any `Output` call returns `Err`. ### assert_input_contract ```rust pub fn assert_input_contract(make: F) ``` Drives `B` through [`Input`](crate::backend::Input)'s coalescing obligation. A burst of consecutive `Event::Mouse(MouseEventKind::Moved)` pushes must collapse to the latest one, matching [`coalesces_with`](crate::event::coalesces_with). #### Panics Panics if the burst does not coalesce to exactly one event, or if that event isn't the last one pushed. ### impl HeadlessObserver ```rust impl HeadlessObserver { } ``` ### impl Output for HeadlessObserver ```rust impl Output for HeadlessObserver { } ``` ### impl Cursor for HeadlessObserver ```rust impl Cursor for HeadlessObserver { } ``` ### impl Observable for HeadlessObserver ```rust impl Observable for HeadlessObserver { } ``` ## src/layout/mod.rs ### align::{HAlign, VAlign} ```rust pub use align::{HAlign, VAlign}; ``` ### text_layout::TextLayout ```rust pub use text_layout::TextLayout; ``` ### word_wrap::wrap ```rust pub use word_wrap::wrap; ``` ## src/layout/word_wrap.rs ### wrap ```rust pub fn wrap(line: &Line, max_width: u16) -> Vec ``` Word-wraps `line` to `max_width` columns, returning the broken-apart [`Line`](crate::text::Line)s. This is the same greedy, grapheme-cluster-aware wrap pass [`TextLayout`](super::TextLayout) runs internally on every render (breaking on ASCII space, honoring hard `\n`s, force-breaking an overlong word at the column boundary); it's exposed standalone for callers that need the wrapped pieces themselves rather than having them written straight to a surface, such as a scrollback log that wraps each message into rows while still addressing its window in whole messages. Each returned `Line` is a single unstyled or uniformly-styled run per source span that survived onto that row; adjacent graphemes carrying the same [`Style`](crate::color::Style) are coalesced back into one [`Span`](crate::text::Span), so wrapping a plain [`Line::raw`](crate::text::Line::raw) round-trips to plain `Line::raw` rows. #### Examples ``` use retroglyph_core::layout::wrap; use retroglyph_core::text::Line; let line = Line::raw("hello world"); let rows = wrap(&line, 7); assert_eq!(rows.len(), 2); assert_eq!(rows[0].spans[0].content, "hello"); assert_eq!(rows[1].spans[0].content, "world"); ``` ## src/layout/align.rs ### HAlign ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum HAlign { Left, Center, Right, } ``` Horizontal alignment within a bounded rectangle. ### impl HAlign ```rust impl HAlign { pub fn offset(self, area_width: u16, content_width: u16) -> u16; } ``` ### VAlign ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum VAlign { Top, Middle, Bottom, } ``` Vertical alignment within a bounded rectangle. ### impl VAlign ```rust impl VAlign { pub fn offset(self, area_height: u16, content_height: u16) -> u16; } ``` ## src/layout/text_layout.rs ### TextLayout ```rust pub struct TextLayout<'a> { } ``` Builder for laying out a [`Line`](crate::text::Line) within a bounded [`Rect`](crate::grid::Rect). Call [`measure`](crate::layout::TextLayout::measure) to get its [`Size`](crate::grid::Size) without touching any surface, or [`render_to_surface`](crate::layout::TextLayout::render_to_surface) to write directly into a [`Surface`](crate::surface::Surface). #### Examples ``` use retroglyph_core::layout::{TextLayout, HAlign, VAlign}; use retroglyph_core::grid::Rect; use retroglyph_core::text::Line; use retroglyph_core::grid::HasSize; let rect = Rect::new(0, 0, 20, 5); let line = Line::raw("Hello, world!"); let metrics = TextLayout::new(&line) .rect(rect) .h_align(HAlign::Center) .measure(); assert_eq!(metrics.height(), 1); ``` ### impl TextLayout ```rust impl<'a> TextLayout { pub fn new(line: &'a Line) -> Self; pub fn rect(self, rect: Rect) -> Self; pub fn h_align(self, align: HAlign) -> Self; pub fn v_align(self, align: VAlign) -> Self; pub fn measure(&self) -> Size; pub fn render_to_surface(&self, surface: &mut Surface<'_>); pub fn render_to_grid(&self, grid: &mut Grid, layer: u8); } ``` ## src/math.rs ### mul_add ```rust pub fn mul_add(a: f32, b: f32, c: f32) -> f32 ``` `a * b + c`, as one rounding step (a fused multiply-add) rather than two. ### round ```rust pub fn round(x: f32) -> f32 ``` Rounds to the nearest integer, ties away from zero. ### sin ```rust pub fn sin(x: f32) -> f32 ``` Sine of `x`, in radians. ### cos ```rust pub fn cos(x: f32) -> f32 ``` Cosine of `x`, in radians. ### powf ```rust pub fn powf(x: f32, y: f32) -> f32 ``` `x` raised to the power `y`. ### exp ```rust pub fn exp(x: f32) -> f32 ``` `e` raised to the power `x`. ## src/dev.rs ### BuildMode ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum BuildMode { Dev, Release, } ``` Which diagnostics this build compiles in. Read [`CURRENT`](Self::CURRENT) for this build's mode, or use [`dev_only!`](crate::dev_only) to gate a block on it. See the [module docs](self) for how a mode is chosen and why there are two of them rather than three. ### impl BuildMode ```rust impl BuildMode { pub const CURRENT: Type; pub fn is_dev(self) -> bool; pub fn is_release(self) -> bool; } ``` ### DEV ```rust pub const DEV: bool ``` Whether this build compiles in development diagnostics: [`BuildMode::CURRENT`](crate::dev::BuildMode::CURRENT) as a `bool`. Prefer [`dev_only!`](crate::dev_only) for gating a block. Reach for this constant directly when the shape of the code makes a macro awkward, such as an early return or a struct field that only one mode populates. ### dev_only! ```rust macro_rules! dev_only { // macro definition } ``` Runs `body` only in a build that compiles in development diagnostics. Expands to `if DEV { body }`. Because [`DEV`](crate::dev::DEV) is a `const`, a release build folds the branch away and drops `body` with it, including any message strings and bookkeeping it alone references. `body` is type-checked in every mode. That is the point: a diagnostic that only compiles on one profile rots, and the rot surfaces as a broken release build. The cost is that `body` may not reference items that themselves exist only in a dev build. Control flow escapes the block on one profile only. A `return`, `?`, `break`, or `continue` inside `body` runs in a dev build and is skipped entirely in a release build, so the surrounding function must be correct when the block does nothing. Confine `body` to diagnostics and their bookkeeping; if the enclosing function's result depends on it, the profiles disagree. #### Examples ``` use retroglyph_core::dev_only; #### fn warn_overflow(_: (u32, u32), _: (u32, u32)) {} let sprite_px = (32, 32); let cell_px = (16, 16); dev_only!({ if sprite_px > cell_px { warn_overflow(sprite_px, cell_px); } }); ``` The block form is not required; any statements work. ``` #### use retroglyph_core::dev_only; #### let mut misses = 0; dev_only!(misses += 1;); ``` ## src/tile.rs ### Tile ```rust #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Tile { } ``` A single drawable tile in the terminal grid. Each tile occupies one cell on a single layer; a [`Grid`](crate::grid::Grid) holds up to 256 independent layers of tiles per cell, composited bottom-to-top. Sub-cell pixel offsets (`dx`, `dy`) are visual only, they do not affect grid logic or hit-testing. Backends that cannot represent pixel offsets (e.g. `CrosstermBackend`) ignore them. A tile does *not* carry its own multi-codepoint grapheme text (see [`TileFlags::HAS_EXTRA`]): that lives in a sparse side-table on the owning [`Grid`](crate::grid::Grid), keeping every `Tile` a small, fully `Copy` value regardless of whether the `egc` feature is enabled. Read it back via [`DrawCell::grapheme`](crate::backend::DrawCell::grapheme), streamed off [`Grid::layers`](crate::grid::Grid::layers). #### Examples ``` use retroglyph_core::color::{Color, Style}; use retroglyph_core::tile::Tile; let tile = Tile::new('@', Style::new().fg(Color::GREEN)); assert_eq!(tile.glyph(), '@'); assert_eq!(tile.style().foreground(), Color::GREEN); ``` ### impl Default for Tile ```rust impl Default for Tile { } ``` ### impl Tile ```rust impl Tile { pub fn new(glyph: char, style: Style) -> Self; pub fn glyph(&self) -> char; pub fn width(&self) -> u16; pub fn style(&self) -> Style; pub fn dx(&self) -> i16; pub fn dy(&self) -> i16; pub fn flags(&self) -> TileFlags; pub fn span(&self) -> (u16, u16); pub fn span_offset(&self) -> Option<(u16, u16)>; pub fn span_anchor_index(&self, idx: usize, cols: usize) -> Option; pub fn is_empty(&self) -> bool; pub fn is_wide(&self) -> bool; pub fn is_wide_spacer(&self) -> bool; pub fn is_span_anchor(&self) -> bool; pub fn with_glyph(self, glyph: char) -> Self; pub fn with_style(self, style: Style) -> Self; pub fn with_offset(self, dx: i16, dy: i16) -> Self; } ``` ## src/surface/mod.rs ### styled::StyledSurface ```rust pub use styled::StyledSurface; ``` ### Surface ```rust pub struct Surface<'a> { } ``` The render target for every drawing call in the workspace: a mutable reference to a [`Grid`](crate::grid::Grid) plus a fixed `layer`, scoped to an `area` and clipped to a `clip` rect. A `Surface` is typically created once per frame, scoped to the whole drawing surface (e.g. via [`Terminal::draw`](crate::terminal::Terminal::draw)), and handed to every subsystem/widget in turn; each caller's own `area: Rect` (a sub-rect of the surface's own area, e.g. one produced by a layout split) is relative to this surface's own `area` origin, not to the underlying grid. [`Surface::put`](crate::surface::Surface::put)/[`Surface::print`](crate::surface::Surface::print)/... take coordinates in that same local space, where `(0, 0)` is `area`'s top-left corner, and silently drop any write that falls outside [`Surface::clip_rect`](crate::surface::Surface::clip_rect), matching the rest of the workspace's clip-on-draw policy for out-of-bounds drawing. `area` and `clip_rect` answer two different questions. `area` is the region this surface *represents*: what a widget lays itself out in, and what [`width`](Self::width)/ [`height`](Self::height) report. `clip_rect` is the subset of `area` that is actually *visible*: what every write is bounds-checked against. The two start out equal (see [`Surface::new`](crate::surface::Surface::new)) and diverge once [`Surface::clip`](crate::surface::Surface::clip) or [`Surface::scope`](crate::surface::Surface::scope) is used. [`Surface::clip`](crate::surface::Surface::clip) narrows what is visible without changing what this surface represents: `clip_rect` is intersected with the given rect, `area` is untouched. [`Surface::scope`](crate::surface::Surface::scope) does both: `area` becomes the given rect and `clip_rect` is intersected with it, which is what a widget's own sub-surface needs when it should be laid out against a new rect but still bounded by whatever was already visible. Both narrow monotonically: neither can widen `clip_rect` beyond what the parent surface already allowed. A caller that genuinely needs more than one layer at once (e.g. a modal dimming layer 0 while drawing its own content on layer 1) switches layers with [`Surface::on_layer`](crate::surface::Surface::on_layer)/[`Surface::on_tier`](crate::surface::Surface::on_tier) rather than being restricted to the layer it was constructed with. ### Layer ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)] pub enum Layer { World, Hud, Overlay, Debug, } ``` A named z-order tier for [`Surface::on_tier`](crate::surface::Surface::on_tier), covering the split most apps with overlapping UI actually need. Layers are how overlapping UI avoids depending on draw order: a caller who paints a dropdown on [`Layer::Overlay`](crate::surface::Layer::Overlay) gets it on top of the active screen regardless of whether the screen or the dropdown drew first this frame, so the two don't have to agree on an ordering (contrast with painting both through the same layer, where whichever call happens to run last wins). `Layer` derives [`Ord`] over its declaration order, so `Layer::World < Layer::Hud < Layer::Overlay < Layer::Debug` holds without spelling out the underlying grid layer ids -- the same relationship [`Surface::on_tier`](crate::surface::Surface::on_tier) relies on to keep `Layer::Debug` the top-most tier no matter what else is open. This is a convention, not a restriction: [`Surface::on_layer`](crate::surface::Surface::on_layer) still accepts any `u8`, and a tile map or sprite-heavy app with its own multi-layer scheme (terrain/items/actors/...) has no reason to route through `Layer` at all. `Layer` exists for the overlapping-*UI* case: chrome, popups, debug HUDs, where a small, shared, named split is worth more than 256 open numeric ids. #### Examples A persistent HUD bar and a dropdown that must paint over it, in either order, because they're on different tiers rather than racing to draw last: ``` use retroglyph_core::color::Style; use retroglyph_core::grid::{Grid, Rect}; use retroglyph_core::surface::{Layer, Surface}; let area = Rect::new(0, 0, 20, 5); let mut grid = Grid::new(20, 5); let mut surface = Surface::new(&mut grid, area, Layer::World.as_u8()); // The active screen draws on `World`. surface.print((0, 0), "screen content", Style::default()); // Chrome draws on `Hud`, above the screen. surface.on_tier(Layer::Hud).print((0, 0), "File Edit View", Style::default()); // A dropdown draws on `Overlay`, above the HUD: painting it before or after the two calls // above makes no difference, because it's on a higher tier, not drawn later. surface.on_tier(Layer::Overlay).print((0, 1), "New", Style::default()); ``` ### impl Layer ```rust impl Layer { pub fn as_u8(self) -> u8; } ``` ### impl From for u8 ```rust impl From for u8 { } ``` ### impl Surface ```rust impl<'a> Surface { pub fn new(grid: &'a mut Grid, area: Rect, layer: u8) -> Self; } ``` ## src/surface/styled.rs ### StyledSurface ```rust pub struct StyledSurface<'s, 'a> { } ``` A [`Surface`](crate::surface::Surface) with a [`Style`](crate::color::Style) bound in, returned by [`Surface::with_style`](crate::surface::Surface::with_style). Every draw call omits the `style` argument the underlying [`Surface`](crate::surface::Surface) method would otherwise need, using the bound style instead. Reach back to the underlying surface (e.g. to call [`Surface::print_line`](crate::surface::Surface::print_line), whose per-span styles make a bound style meaningless) via [`StyledSurface::surface`](crate::surface::StyledSurface::surface). ### impl StyledSurface ```rust impl<'a> StyledSurface { pub fn style(&self) -> Style; pub fn surface(&mut self) -> &mut Surface<'a>; pub fn put(&mut self, pos: impl Trait, ch: char); pub fn print(&mut self, pos: impl Trait, text: &str); pub fn fill_rect(&mut self, rect: Rect, ch: char); pub fn put_span(&mut self, pos: impl Trait, rows: &[S]) -> Option<()>; pub fn put_span_uniform(&mut self, pos: impl Trait, size: impl Trait, anchor: char, fill: char) -> Option<()>; pub fn put_offset(&mut self, pos: impl Trait, offset: impl Trait, ch: char); pub fn put_signed(&mut self, pos: (i32, i32), ch: char); pub fn blit(&mut self, grid: &Grid, x: u16, y: u16); pub fn clear(&mut self); pub fn clear_region(&mut self, rect: Rect); pub fn width(&self) -> u16; pub fn height(&self) -> u16; pub fn area(&self) -> Rect; pub fn clip_rect(&self) -> Rect; pub fn local_area(&self) -> Rect; } ``` ## src/surface/tests.rs ## src/surface/geometry.rs ### impl Surface ```rust impl<'a> Surface { pub fn area(&self) -> Rect; pub fn clip_rect(&self) -> Rect; pub fn width(&self) -> u16; pub fn height(&self) -> u16; pub fn layer(&self) -> u8; pub fn on_layer(&mut self, layer: u8) -> Surface<'_>; pub fn on_tier(&mut self, tier: Layer) -> Surface<'_>; pub fn tint(&self) -> Tint; pub fn origin(&self) -> (i32, i32); pub fn with_tint(&mut self, tint: Tint) -> Surface<'_>; pub fn clip(&mut self, rect: Rect) -> Surface<'_>; pub fn scope(&mut self, rect: Rect) -> Surface<'_>; pub fn translate(&mut self, origin: (i32, i32)) -> Surface<'_>; pub fn clip_translate(&mut self, area: Rect, origin: (i32, i32)) -> Surface<'_>; pub fn with_style(&mut self, style: Style) -> StyledSurface<'_, 'a>; pub fn grid_mut(&mut self) -> &mut Grid; pub fn grid(&self) -> &Grid; } ``` ## src/surface/draw/mod.rs ### impl Surface ```rust impl Surface { } ``` ## src/surface/draw/spans.rs ### impl Surface ```rust impl Surface { pub fn put_span(&mut self, pos: impl Trait, rows: &[S], style: Style) -> Option<()>; pub fn put_span_uniform(&mut self, pos: impl Trait, size: impl Trait, anchor: char, fill: char, style: Style) -> Option<()>; pub fn put_offset(&mut self, pos: impl Trait, offset: impl Trait, ch: char, style: Style); } ``` ## src/surface/draw/cells.rs ### impl Surface ```rust impl Surface { pub fn put(&mut self, pos: impl Trait, ch: char, style: Style); pub fn put_signed(&mut self, pos: (i32, i32), ch: char, style: Style); pub fn fill_rect(&mut self, rect: Rect, ch: char, style: Style); pub fn blit(&mut self, grid: &Grid, x: u16, y: u16); pub fn clear(&mut self); pub fn clear_region(&mut self, rect: Rect); } ``` ## src/surface/draw/text.rs ### impl Surface ```rust impl Surface { pub fn print(&mut self, pos: impl Trait, text: &str, style: Style); pub fn print_line(&mut self, pos: impl Trait, line: &Line); pub fn print_aligned(&mut self, rect: Rect, text: &str, align: crate::layout::HAlign, style: Style); } ``` ## src/event/mod.rs ### key::{KeyCode, KeyEvent, KeyEventKind, KeyLocation, KeyModifiers, KeyState, ModifierKey} ```rust pub use key::{KeyCode, KeyEvent, KeyEventKind, KeyLocation, KeyModifiers, KeyState, ModifierKey}; ``` ### mouse::{MouseButton, MouseEvent, MouseEventKind, PhysicalPos} ```rust pub use mouse::{MouseButton, MouseEvent, MouseEventKind, PhysicalPos}; ``` ### SystemTheme ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum SystemTheme { Light, Dark, } ``` The system's light/dark color-scheme preference, as reported by the windowing/browser layer. Currently just these two variants: every source that can report this (winit's `Theme`, the browser's `prefers-color-scheme` media query) only ever resolves to one of exactly these two, and a backend that can't determine a preference simply never emits [`Event::ThemeChanged`] rather than emitting a third "unknown" case for callers to handle. Marked `#[non_exhaustive]` for consistency with sibling public enums, in case a future source (e.g. a `HighContrast` case) needs to be added. ### Event ```rust #[derive(Debug, Clone, PartialEq)] pub enum Event { Key(KeyEvent), Mouse(MouseEvent), Resize(u16, u16), Close, ThemeChanged(SystemTheme), Paste(String), FocusGained, FocusLost, Custom(u64), } ``` Terminal input event. Does not derive `Eq`/`Hash`: [`MouseEvent`] does not (its `MouseEventKind::Scroll` variant's `f32` fields implement neither). ### coalesces_with ```rust pub fn coalesces_with(new: &Event, existing: &Event) -> bool ``` Whether `new` should replace the queue's current tail event instead of being pushed alongside it, when a backend is appending `new` to a `Vec`/`VecDeque` of pending events. True for two consecutive [`Event::Mouse`] events both carrying [`MouseEventKind::Moved`], or both carrying [`MouseEventKind::Drag`] with the same button: a queue owner (winit, the wasm FFI boundary, `Headless`) can be fed pointer-move/drag events far faster than a consumer drains them, and only the most recent position matters once it does (whether or not a button is held), so collapsing either run in place keeps the queue from growing unbounded (retroglyph#294, retroglyph#768, retroglyph#942). A `Drag` only coalesces with another `Drag` carrying the *same* button, so a button change mid-drag is never swallowed into the wrong button's position. `Scroll` deliberately does not coalesce despite also being high-frequency: its `dx`/`dy` are deltas, not absolute state, so collapsing a run would discard real scroll distance rather than a stale intermediate value. Every other event kind (clicks, keys, resize, ...) always returns `false`. ## src/event/mouse.rs ### PhysicalPos ```rust pub type PhysicalPos = ixy::Pos ``` Physical (pixel) position relative to the window's top-left corner. Using `ixy::Pos` rather than the cell-grid [`Pos`] (`ixy::Pos`) makes the distinction type-safe: you cannot accidentally pass a pixel coordinate where a cell coordinate is expected. ### MouseButton ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum MouseButton { Left, Right, Middle, } ``` Mouse button identifiers. ### MouseEventKind ```rust #[derive(Debug, Clone, Copy, PartialEq)] pub enum MouseEventKind { Down(MouseButton), Up(MouseButton), Drag(MouseButton), Moved, Scroll { dx: f32, dy: f32 }, } ``` Kinds of mouse events. Does not derive `Eq`/`Hash`: [`Scroll`](Self::Scroll)'s `f32` fields implement neither. ### MouseEvent ```rust #[derive(Debug, Clone, Copy, PartialEq)] pub struct MouseEvent { pub kind: MouseEventKind, pub position: Pos, pub pixel_position: Option, pub modifiers: KeyModifiers, } ``` Mouse input event. Does not derive `Eq`/`Hash`: [`MouseEventKind`] does not (its `Scroll` variant's `f32` fields implement neither). ### impl MouseEvent ```rust impl MouseEvent { pub fn new(kind: MouseEventKind, position: Pos, modifiers: KeyModifiers) -> Self; pub fn with_pixel_position(kind: MouseEventKind, position: Pos, modifiers: KeyModifiers, pixel_position: PhysicalPos) -> Self; } ``` ## src/event/key.rs ### KeyModifiers ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct KeyModifiers(); ``` Keyboard modifier flags. Implemented as a manual bitflag over `u8` (`SHIFT = 1`, `CONTROL = 2`, `ALT = 4`, `SUPER = 8`) rather than a [`bitflags`](https://crates.io/crates/bitflags)-generated type, so this stays a plain value type with no macro-generated API surface. Combine with `|`. ### impl KeyModifiers ```rust impl KeyModifiers { pub const NONE: Type; pub const SHIFT: Type; pub const CONTROL: Type; pub const ALT: Type; pub const SUPER: Type; pub fn from_bits_truncate(bits: u8) -> Self; pub fn from_parts(shift: bool, control: bool, alt: bool, super_: bool) -> Self; pub fn bits(self) -> u8; pub fn contains(self, other: Self) -> bool; pub fn is_empty(self) -> bool; } ``` ### impl BitOr for KeyModifiers ```rust impl BitOr for KeyModifiers { } ``` ### impl BitOrAssign for KeyModifiers ```rust impl BitOrAssign for KeyModifiers { } ``` ### impl BitAnd for KeyModifiers ```rust impl BitAnd for KeyModifiers { } ``` ### impl BitAndAssign for KeyModifiers ```rust impl BitAndAssign for KeyModifiers { } ``` ### impl Not for KeyModifiers ```rust impl Not for KeyModifiers { } ``` ### ModifierKey ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ModifierKey { Shift, Control, Alt, Super, } ``` A modifier key pressed as a standalone key event, independent of the [`KeyModifiers`](crate::event::KeyModifiers) flags carried on non-modifier key events. This is flat (no per-side variants) because side is conveyed separately: pair this with the surrounding [`KeyEvent`](crate::event::KeyEvent)'s [`KeyLocation::Left`](crate::event::KeyLocation::Left)/[`KeyLocation::Right`](crate::event::KeyLocation::Right) rather than duplicating left/right into `ModifierKey` itself. Reporting a bare modifier press as a [`KeyCode::Modifier`](crate::event::KeyCode::Modifier) event is backend-dependent: the crossterm backend requires the terminal to support the kitty keyboard protocol with the `REPORT_ALL_KEYS_AS_ESCAPE_CODES` enhancement flag enabled; plain terminals never report these. ### KeyCode ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum KeyCode { Char(char), F(u8), Backspace, Enter, Left, Right, Up, Down, Home, End, PageUp, PageDown, Tab, BackTab, Delete, Insert, Escape, Modifier(ModifierKey), CapsLock, ScrollLock, NumLock, PrintScreen, Pause, Menu, } ``` Keyboard key codes. ### KeyEventKind ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum KeyEventKind { Press, Repeat, Release, } ``` Whether a key event is a press, an auto-repeat, or a release. Not every backend can distinguish these. Plain terminals only ever emit [`Press`](Self::Press). Backends with richer input report the full set: - The winit/software backend emits `Press`, `Repeat` (winit's `repeat` flag), and `Release`. - The crossterm backend emits the full set only when the terminal supports the kitty keyboard protocol (kitty, `WezTerm`, foot, Ghostty, recent Alacritty); otherwise it degrades to `Press`-only. Marked `#[non_exhaustive]` for consistency with sibling public enums, in case a future source reports a state finer-grained than this set (e.g. distinguishing an OS-level key-repeat from a backend-synthesized one). ### KeyLocation ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum KeyLocation { Standard, Left, Right, Numpad, } ``` The physical location of a key on the keyboard, for keys that appear in more than one place. Mirrors [winit's `KeyLocation`](https://docs.rs/winit/latest/winit/keyboard/enum.KeyLocation.html): a key like "1" carries the same [`KeyCode`](crate::event::KeyCode) whether it's pressed above the letters or on the numpad, and modifier keys like Shift exist on both the left and right sides. This field disambiguates those cases. ### KeyEvent ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct KeyEvent { pub code: KeyCode, pub modifiers: KeyModifiers, pub kind: KeyEventKind, pub location: KeyLocation, } ``` Keyboard input event. ### impl KeyEvent ```rust impl KeyEvent { pub fn new(code: KeyCode, modifiers: KeyModifiers) -> Self; pub fn with_kind(code: KeyCode, modifiers: KeyModifiers, kind: KeyEventKind) -> Self; pub fn with_location(code: KeyCode, modifiers: KeyModifiers, kind: KeyEventKind, location: KeyLocation) -> Self; pub fn is_down(self) -> bool; } ``` ### KeyState ```rust #[derive(Debug, Clone, Default)] pub struct KeyState { } ``` Tracks which keys are currently held down. Feed it every [`KeyEvent`] (or [`Event`](super::Event)) you receive and query [`is_held`](Self::is_held) each frame for held-key movement. A key is considered held from its first [`KeyEventKind::Press`] until a matching [`KeyEventKind::Release`], or until [`apply_event`](Self::apply_event) sees an [`Event::FocusLost`](super::Event::FocusLost), whichever comes first. Held keys are keyed by `(KeyCode, KeyLocation)`, so a held Numpad8 and a held digit-row 8 are tracked separately: [`is_held`](Self::is_held) takes the pair, and [`held`](Self::held) yields it. Release events are what actually clear a key, and not every backend emits them: plain terminals only ever report [`KeyEventKind::Press`](crate::event::KeyEventKind::Press) (see [`KeyEventKind`](crate::event::KeyEventKind)). On those press-only backends a key never leaves the held set on its own -- not even on focus loss, since there is no release to match -- so call [`clear`](Self::clear) at a suitable boundary (e.g. once per turn) if you rely on held-key state there. Backends rich enough to emit releases (winit, or a terminal with the kitty keyboard protocol) are exactly the ones that also emit [`Event::FocusLost`](super::Event::FocusLost), so [`apply_event`](Self::apply_event) clears the held set on it: without that, alt-tabbing or clicking away while a key is down would leave it stuck held forever, since the release is delivered to whichever window/app gains focus instead. ### impl KeyState ```rust impl KeyState { pub fn new() -> Self; pub fn apply(&mut self, event: KeyEvent); pub fn apply_event(&mut self, event: &super::Event); pub fn is_held(&self, code: KeyCode, location: KeyLocation) -> bool; pub fn held(&self) -> impl Trait; pub fn clear(&mut self); } ``` ### impl core::hash::Hasher for TestHasher ```rust impl core::hash::Hasher for TestHasher { } ``` ## src/frames/mod.rs ### clock::FrameClock ```rust pub use clock::FrameClock; ``` ### stats::FrameStats ```rust pub use stats::FrameStats; ``` ## src/frames/clock.rs ### FrameClock ```rust #[derive(Debug, Clone)] pub struct FrameClock { } ``` A fixed-timestep accumulator. Feed elapsed wall time with [`advance`](Self::advance), then call [`tick`](Self::tick) in a loop to drain whole logic steps. Use [`alpha`](Self::alpha) to interpolate rendering between logic frames. See the `08_animation` example for `FrameClock` in action: . ### impl FrameClock ```rust impl FrameClock { pub fn new(hz: u32) -> Self; pub fn step(&self) -> Duration; pub fn advance(&mut self, dt: Duration); pub fn tick(&mut self) -> bool; pub fn alpha(&self) -> f64; pub fn reset(&mut self); } ``` ## src/frames/stats.rs ### FrameStats ```rust #[derive(Debug, Clone)] pub struct FrameStats { } ``` A fixed-size ring buffer of recent per-frame durations, plus the min/max/average/fps readouts derived from it. `N` bounds memory and how far back the window looks; the default, 120 samples (about two seconds at 60fps), is a reasonable window for a live overlay. Every reducer ([`avg`](Self::avg), [`min`](Self::min), [`max`](Self::max), [`fps`](Self::fps)) is computed on demand from the current window rather than maintained incrementally, since a live overlay only calls them once per rendered frame, not once per sample. Readouts are [`Duration`], not a pre-chosen unit: a caller formats with whatever precision it needs (`as_millis()`, `as_secs_f32()`, ...) rather than being handed milliseconds it then has to convert back if it wants something else. [`current`](Self::current) is the single most recent sample, unsmoothed: pair it with the windowed [`min`](Self::min)/[`max`](Self::max) for a "current, min, max" readout, and [`samples`](Self::samples) for a frame-time graph (feed it, converted to milliseconds, straight into [`Sparkline`](https://docs.rs/retroglyph-ui/latest/retroglyph_ui/struct.Sparkline.html)). ### impl Default for FrameStats ```rust impl Default for FrameStats { } ``` ### impl FrameStats ```rust impl FrameStats { pub fn new() -> Self; pub fn record(&mut self, delta: Duration); pub fn frame_count(&self) -> u64; pub fn samples(&self) -> impl Trait; pub fn current(&self) -> Duration; pub fn avg(&self) -> Duration; pub fn min(&self) -> Duration; pub fn max(&self) -> Duration; pub fn fps(&self) -> f32; } ``` ## src/grid/mod.rs ### ixy::HasSize ```rust pub use ixy::HasSize; ``` `.width()`/`.height()` accessors for [`Size`](crate::grid::Size) (and [`Rect`](crate::grid::Rect)): re-exported so callers don't need a direct `ixy` dependency just to call them on this crate's own type aliases. ### BlendMode ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum BlendMode { Linear, Screen, Dodge, Burn, Overlay, Multiply, } ``` Blend mode for [`Grid::blit_alpha`](crate::grid::Grid::blit_alpha), selecting how source and destination colors combine before the `fg_alpha`/`bg_alpha` factor is applied. [`Linear`](Self::Linear) is a straight per-channel color lerp, delegated to [`gem::Mix`]. The remaining variants are the [W3C separable blend modes] libtcod also offers: each computes a fully blended color per channel via [`alpha_blend::BlendMode`] (imported here under its old name, [`SeparableBlendMode`], to avoid colliding with this module's own [`BlendMode`](crate::grid::BlendMode)), and *that* result is what gets lerped against the destination by the alpha factor, in place of the source color `Linear` would use. [W3C separable blend modes]: https://www.w3.org/TR/compositing-1/#blending ### impl BlendMode ```rust impl BlendMode { } ``` ### Size ```rust pub type Size = ixy::Size ``` Size of the grid. #### Examples ``` use retroglyph_core::grid::Size; let size = Size::new(80, 24); assert_eq!(size.width, 80); ``` This crate's `serde` feature forwards to [`ixy`]'s own `serde` feature, so `Size` gains `Serialize`/`Deserialize` from its upstream definition rather than one defined here. ### Pos ```rust pub type Pos = ixy::Pos ``` Pos in the grid, in (x = column, y = row) order. Implements [`Ord`] in row-major order (y primary, then x), which is the natural ordering for terminal rendering: top-to-bottom, left-to-right within each row. #### Examples ``` use retroglyph_core::grid::Pos; let pos = Pos::new(2, 1); assert_eq!(pos.x, 2); assert_eq!(pos.y, 1); ``` This crate's `serde` feature forwards to [`ixy`]'s own `serde` feature, so `Pos` gains `Serialize`/`Deserialize` from its upstream definition rather than one defined here. ### Rect ```rust pub type Rect = ixy::Rect ``` Rectangle in the grid. #### Examples ``` use retroglyph_core::grid::Rect; let rect = Rect::new(0, 0, 10, 4); assert_eq!(rect.width(), 10); assert_eq!(rect.height(), 4); ``` This crate's `serde` feature forwards to [`ixy`]'s own `serde` feature, so `Rect` gains `Serialize`/`Deserialize` from its upstream definition rather than one defined here. ### Offset ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct Offset { pub dx: i16, pub dy: i16, } ``` A sub-cell pixel offset `(dx, dy)`, distinct from [`Pos`] so a caller can't transpose a position and an offset in a call like [`Surface::put_offset`](crate::surface::Surface::put_offset). Visual only: an offset shifts where a glyph is painted within its cell on backends that support sub-cell placement (e.g. `retroglyph-software`); it never changes which cell a glyph occupies, and cell-mode backends (e.g. `retroglyph-crossterm`) ignore it entirely. This crate's `serde` feature adds `Serialize`/`Deserialize` impls for `Offset` directly (unlike [`Size`]/[`Pos`]/[`Rect`], which forward to [`ixy`]'s own `serde` feature). #### Examples ``` use retroglyph_core::grid::Offset; let offset = Offset::new(3, -2); assert_eq!(offset.dx, 3); assert_eq!(offset.dy, -2); ``` ### impl Offset ```rust impl Offset { pub fn new(dx: i16, dy: i16) -> Self; } ``` ### impl From<(i16, i16)> for Offset ```rust impl From<(i16, i16)> for Offset { } ``` ### impl From for Unknown ```rust impl From for Unknown { } ``` ### impl TileExtra ```rust impl TileExtra { } ``` ### impl LayerBuf ```rust impl LayerBuf { } ``` ### Grid ```rust #[derive(Clone)] pub struct Grid { } ``` A 2D buffer of [`Tile`](crate::tile::Tile)s, addressable across up to 256 stacked layers. Layer 0 is always allocated; higher layers are allocated on first write, growing the layer-table `Vec` up to that layer's id as needed (see [`Grid::new`](crate::grid::Grid::new)). Single-layer use pays no overhead: layers 1+ stay unallocated until used, and the layer table itself never grows past a single slot. #### Out-of-bounds drawing Drawing off the grid is a no-op, the same convention as drawing off-screen: every write method that names a position or region (e.g. [`put_tile`](Self::put_tile), [`write_grapheme`](Self::write_grapheme), [`write_span`](Self::write_span), [`blit`](Self::blit)) silently discards any part of the write that falls outside `0..width` / `0..height`, rather than panicking. The one deliberate exception is indexing (`Index`/`IndexMut`, and by extension anything built on it), which panics on an out-of-bounds `Pos` the same way indexing a slice does. Read accessors that take a position (e.g. [`tile`](Self::tile)) report an out-of-bounds position as `None`, indistinguishable from an unallocated layer. Requires an allocator (backed by `alloc::vec::Vec`), so it is unavailable in strictly static, no-alloc environments. #### Examples ``` use retroglyph_core::color::{Color, Style}; use retroglyph_core::grid::{Grid, Pos}; let mut grid = Grid::new(10, 5); grid.put_tile(0, Pos::new(2, 1), retroglyph_core::tile::Tile::new('@', Style::new().fg(Color::GREEN))); assert_eq!(grid[Pos::new(2, 1)].glyph(), '@'); ``` ### impl Grid ```rust impl Grid { } ``` ## src/grid/diff.rs ### impl Grid ```rust impl Grid { pub fn diff<'a>(&self, other: &'a Self) -> impl Trait; } ``` ### impl Iterator for LayerDiff ```rust impl<'a, F, D, C> Iterator for LayerDiff { } ``` ## src/grid/api.rs ### impl Grid ```rust impl Grid { pub fn new(width: u16, height: u16) -> Self; pub fn from_charmap(map: &str, f: F) -> Self where F: FnMut; pub fn width(&self) -> u16; pub fn height(&self) -> u16; pub fn size(&self) -> Size; pub fn max_layer(&self) -> u8; pub fn clear(&mut self, layer: u8); pub fn resize(&mut self, width: u16, height: u16); pub fn write_grapheme(&mut self, layer: u8, x: u16, y: u16, grapheme: &str, style: Style) -> bool; } ``` ## src/grid/spans.rs ### impl Grid ```rust impl Grid { pub fn write_span(&mut self, layer: u8, x: u16, y: u16, rows: &[S], style: Style) -> Option<()>; pub fn write_span_uniform(&mut self, layer: u8, pos: impl Trait, size: impl Trait, anchor: char, fill: char, style: Style) -> Option<()>; pub fn span_owner(&self, layer: u8, x: u16, y: u16) -> Option; pub fn clear_span(&mut self, layer: u8, x: u16, y: u16); } ``` ## src/grid/trait_impls.rs ### impl Index for Grid ```rust impl Index for Grid { } ``` ### impl IndexMut for Grid ```rust impl IndexMut for Grid { } ``` ### impl fmt::Display for Grid ```rust impl fmt::Display for Grid { } ``` Renders layer 0 only, one character per cell, with `·` in place of a plain space. ### impl fmt::Debug for Grid ```rust impl fmt::Debug for Grid { } ``` Shows `width`, `height`, `max_layer`, and `has_spans`; the layer buffers themselves are omitted (see [`Display`](fmt::Display) for a rendering of layer 0). ## src/grid/layers/mod.rs ### impl Grid ```rust impl Grid { pub fn put_tile(&mut self, layer: u8, pos: impl Trait, tile: Tile) -> Option<()>; pub fn fill_region(&mut self, layer: u8, rect: Rect, tile: Tile); pub fn tile(&self, layer: u8, pos: impl Trait) -> Option<&Tile>; pub fn tile_mut(&mut self, layer: u8, pos: impl Trait) -> Option<&mut Tile>; } ``` ## src/grid/layers/tint.rs ### impl Grid ```rust impl Grid { pub fn tint(&self, layer: u8, x: u16, y: u16) -> Tint; pub fn set_tint(&mut self, layer: u8, x: u16, y: u16, tint: Tint); } ``` ## src/grid/layers/flatten.rs ### impl Grid ```rust impl Grid { pub fn layers(&self) -> impl Trait; pub fn clear_all(&mut self); } ``` ## src/grid/layers/blit.rs ### impl Grid ```rust impl Grid { pub fn blit(&mut self, layer: u8, src: &Self, src_rect: Rect, dst_x: u16, dst_y: u16); pub fn blit_alpha(&mut self, layer: u8, src: &Self, src_rect: Rect, dst_x: u16, dst_y: u16, mode: BlendMode, fg_alpha: f32, bg_alpha: f32); } ``` ## src/text.rs ### width ```rust pub fn width(s: &str) -> u16 ``` The number of terminal cells `s` occupies, saturating at `u16::MAX`. Wide characters (CJK, most emoji) count as two columns; combining marks and most control characters count as zero. [`Span::width`](crate::text::Span::width) and [`Line::width`](crate::text::Line::width) are built on this function; reach for it directly to measure a borrowed `&str` without constructing either type first. See [`width_usize`] for the unsaturated measurement. #### Examples ``` use retroglyph_core::text::width; assert_eq!(width("hello"), 5); assert_eq!(width("中文"), 4); // each CJK char is 2 columns ``` ### width_usize ```rust pub fn width_usize(s: &str) -> usize ``` The number of terminal cells `s` occupies, without saturating to `u16`. Prefer [`width`] when the result feeds a `u16`-based geometry type such as `Rect`/`Size`; use this when the raw `unicode-width` measurement is needed instead. ### char_width ```rust pub fn char_width(c: char) -> u16 ``` The number of terminal cells a single character occupies. Returns `1` for most characters, including control characters (`unicode-width` reports no width for these; `1` matches what [`Surface`](crate::surface::Surface) actually draws and what [`Tile::width`](crate::tile::Tile::width) tells the terminal to advance by, so this function agrees with the rest of the crate instead of undercounting), `2` for wide characters (CJK, most emoji), and `0` for combining marks (these genuinely occupy no column of their own). #### Examples ``` use retroglyph_core::text::char_width; assert_eq!(char_width('a'), 1); assert_eq!(char_width('中'), 2); assert_eq!(char_width('\u{0301}'), 0); // combining acute accent assert_eq!(char_width('\u{7}'), 1); // BEL: a control character, not a combining mark ``` ### split_at_width ```rust pub fn split_at_width(s: &str, max_cols: u16) -> (&str, &str) ``` Splits `s` at the byte index where its display width reaches `max_cols`. Splits on a whole-character boundary; a character that would push the total over `max_cols` is left in the second half along with the rest of `s`. Returns `(prefix, rest)` such that `width(prefix) <= max_cols` and `prefix` is the longest prefix of `s` for which that holds. Each candidate prefix is measured with [`width_usize`] (the same `UnicodeWidthStr` logic behind the postcondition above), not a sum of individual [`char_width`]s: `UnicodeWidthStr` measures some multi-codepoint clusters (emoji presentation/ZWJ/modifier sequences) as a unit whose width differs from the sum of its parts, and per-char summing would let such a cluster violate the postcondition in either direction. That re-measurement is bounded to a trailing window of the last `CLUSTER_LOOKBACK` characters rather than the whole prefix seen so far: `UnicodeWidthStr`'s boundary effects never reach further back than a handful of codepoints (variation selectors, a single ZWJ join, an emoji modifier, or a short flag/tag run), so a bounded window reproduces the same result as re-measuring from byte `0` while keeping this function linear in `s`'s length instead of quadratic. #### Examples ``` use retroglyph_core::text::split_at_width; assert_eq!(split_at_width("hello world", 5), ("hello", " world")); assert_eq!(split_at_width("hi", 10), ("hi", "")); ``` ### truncate_measured ```rust pub fn truncate_measured(s: &str, max_cols: u16) -> (&str, u16) ``` Splits `s` at the byte index where its display width reaches `max_cols`, returning the prefix's own measured width alongside it. Equivalent to `let (prefix, _) = split_at_width(s, max_cols); (prefix, width(prefix))`, except the width is read back from [`split_at_width`]'s own internal accounting instead of re-measuring `prefix` with a second pass over it: reach for this instead of that pair whenever the caller needs the truncated width right after truncating (which is every truncate call in this crate's own callers), rather than only the truncated text. #### Examples ``` use retroglyph_core::text::truncate_measured; assert_eq!(truncate_measured("hello world", 5), ("hello", 5)); assert_eq!(truncate_measured("a\u{4e2d}b", 2), ("a", 1)); // "\u{4e2d}" (CJK) doesn't fit ``` ### Span ```rust #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Span { pub content: String, pub style: Style, } ``` A string with an associated [`Style`](crate::color::Style). The building block of styled terminal output. A [`Line`](crate::text::Line) is composed of one or more `Span`s, each with its own style. #### Examples ``` use retroglyph_core::text::Span; use retroglyph_core::color::Style; use retroglyph_core::color::Color; let plain = Span::raw("hello"); let colored = Span::styled("world", Style::new().fg(Color::GREEN)); ``` ### impl Span ```rust impl Span { pub fn raw(content: impl Trait) -> Self; pub fn styled(content: impl Trait, style: Style) -> Self; pub fn width(&self) -> usize; } ``` ### impl From for Span ```rust impl From for Span { } ``` ### Line ```rust #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Line { pub spans: Vec, } ``` A horizontal sequence of [`Span`](crate::text::Span)s rendered as a single line. #### Examples ``` use retroglyph_core::text::{Line, Span}; use retroglyph_core::color::Style; use retroglyph_core::color::Color; let line = Line::from(vec![ Span::raw("HP: "), Span::styled("100", Style::new().fg(Color::GREEN)), ]); assert_eq!(line.width(), 7); ``` ### impl Line ```rust impl Line { pub fn new() -> Self; pub fn raw(content: impl Trait) -> Self; pub fn width(&self) -> usize; } ``` ### impl From<&str> for Line ```rust impl From<&str> for Line { } ``` ### impl From for Line ```rust impl From for Line { } ``` ### impl From for Line ```rust impl From for Line { } ``` ### impl From> for Line ```rust impl From> for Line { } ``` ### spans! ```rust macro_rules! spans { // macro definition } ``` Build a [`Line`](crate::text::Line) from a list of `(Style, text)` pairs. Each pair becomes a [`Span`](crate::text::Span) with the given style. The resulting `Line` is equivalent to calling `Line::from(vec![Span::styled(t, s), ...])` but with less boilerplate for multi-segment event-log text. #### Examples ``` #### extern crate alloc; use retroglyph_core::spans; use retroglyph_core::color::Style; use retroglyph_core::color::Color; let line = spans![ (Style::new().fg(Color::CYAN), "snowtroop "), (Style::default(), "→ 1 hit"), ]; assert_eq!(line.width(), 17); ``` ## src/backend/mod.rs ### headless::Headless ```rust pub use headless::Headless; ``` ### BackendError ```rust pub trait BackendError: core::error::Error { } ``` Associated error type used by all fallible backend methods. Backends that are infallible (e.g. `Headless`, `SoftwareRenderer`) use [`core::convert::Infallible`]. Fallible backends (e.g. `Crossterm`) use [`std::io::Error`]. Requiring [`core::error::Error`] rather than just [`Display`](core::fmt::Display) + [`Debug`](core::fmt::Debug) lets generic code convert `B::Error` into `Box` or any error-trait-based caller error with a plain `?`, and exposes `source()` chains for concrete error types that wrap an inner error. #### Examples ``` use retroglyph_core::backend::BackendError; use core::fmt; #[derive(Debug)] struct MyBackendError; impl fmt::Display for MyBackendError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "my backend failed") } } impl core::error::Error for MyBackendError {} impl BackendError for MyBackendError {} ``` ### impl BackendError for core::convert::Infallible ```rust impl BackendError for core::convert::Infallible { } ``` ### impl BackendError for std::io::Error ```rust impl BackendError for std::io::Error { } ``` ### DrawCell ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct DrawCell<'a> { pub layer: u8, pub pos: Pos, pub tile: &'a Tile, pub grapheme: Option<&'a str>, pub tint: Tint, } ``` One cell handed to a backend at draw time: the tile, plus the state that does not fit in one. ### impl DrawCell ```rust impl<'a> DrawCell { pub fn new(pos: Pos, tile: &'a Tile) -> Self; pub fn on_layer(layer: u8, pos: Pos, tile: &'a Tile) -> Self; pub fn with_grapheme(self, grapheme: Option<&'a str>) -> Self; pub fn with_tint(self, tint: Tint) -> Self; } ``` ### Output ```rust pub trait Output { type Error; fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error> where I: Iterator; fn draw_layers<'a, I>(&mut self, content: I) -> Result<(), Self::Error> where I: Iterator; fn needs_full_frame(&self) -> bool; fn composites_layers(&self) -> bool; fn flush(&mut self) -> Result<(), Self::Error>; fn size(&self) -> Size; fn clear(&mut self) -> Result<(), Self::Error>; fn resize(&mut self, size: Size); } ``` Draws grid content to a display and reports its dimensions. This is the only one of the three backend facets ([`Output`](crate::backend::Output), [`Input`](crate::backend::Input), [`Cursor`](crate::backend::Cursor)) that's fallible: writing to a real display can fail (a broken pipe, a closed terminal, a lost surface), so every mutating method here returns `Result<(), Self::Error>`. #### Examples ``` use retroglyph_core::backend::{DrawCell, Output}; use retroglyph_core::grid::Size; struct NullOutput; impl Output for NullOutput { type Error = core::convert::Infallible; fn draw_layers<'a, I>(&mut self, _content: I) -> Result<(), Self::Error> where I: Iterator>, { Ok(()) } fn flush(&mut self) -> Result<(), Self::Error> { Ok(()) } fn size(&self) -> Size { Size::new(4, 2) } fn clear(&mut self) -> Result<(), Self::Error> { Ok(()) } } ``` ### Input ```rust pub trait Input { fn poll_event(&mut self, timeout: Duration) -> Option; fn push_event(&mut self, _event: Event); } ``` Polls for and accepts input events. Backends that never receive events from outside their own [`poll_event`](Self::poll_event) implementation (e.g. `Crossterm`, which reads its own event stream) can use the default no-op [`push_event`](Self::push_event) via an empty `impl Input for X {}`. #### Examples ``` use core::time::Duration; use retroglyph_core::backend::Input; use retroglyph_core::event::Event; use std::collections::VecDeque; struct QueuedInput(VecDeque); impl Input for QueuedInput { fn poll_event(&mut self, _timeout: Duration) -> Option { self.0.pop_front() } fn push_event(&mut self, event: Event) { self.0.push_back(event); } } ``` ### Cursor ```rust pub trait Cursor { fn set_cursor_visible(&mut self, _visible: bool); fn set_cursor_position(&mut self, _position: Pos); fn set_cursor_style(&mut self, _style: CursorStyle); } ``` Shows, hides, and moves a text cursor. Both methods default to a no-op so backends with no text cursor to manage (pixel/windowed backends, where games draw their own cursor if they want one) can use an empty `impl Cursor for X {}` instead of writing dead stub bodies by hand. #### Examples ``` use retroglyph_core::backend::Cursor; use retroglyph_core::grid::Pos; struct TrackedCursor { visible: bool, position: Pos, } impl Cursor for TrackedCursor { fn set_cursor_visible(&mut self, visible: bool) { self.visible = visible; } fn set_cursor_position(&mut self, position: Pos) { self.position = position; } } ``` ### CursorStyle ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum CursorStyle { BlinkingBlock, SteadyBlock, BlinkingUnderline, SteadyUnderline, BlinkingBar, SteadyBar, } ``` The text cursor's visual shape and blink behavior. Mirrors the six shapes a DEC-compatible terminal's `DECSCUSR` escape (`CSI Ps SP q`) supports: block, underline, and bar, each either blinking or steady. `#[non_exhaustive]` leaves room for a future shape (e.g. a hollow/outline block) without a breaking change. ### Backend ```rust pub trait Backend: Output + Input + Cursor { } ``` A rendering backend that presents grid content to a display and provides input events. This is a pure ergonomic bundle over [`Output`](crate::backend::Output), [`Input`](crate::backend::Input), and [`Cursor`](crate::backend::Cursor), with no members of its own: every type implementing all three gets `Backend` for free, and every generic call site that only needs one or two facets should bound on those directly instead of requiring all three through this trait. #### Examples There is nothing to implement directly: a type gets `Backend` for free the moment it implements all three facet traits. ``` use core::time::Duration; use retroglyph_core::backend::{Backend, Cursor, DrawCell, Input, Output}; use retroglyph_core::event::Event; use retroglyph_core::grid::Size; struct NullBackend; impl Output for NullBackend { type Error = core::convert::Infallible; fn draw_layers<'a, I>(&mut self, _content: I) -> Result<(), Self::Error> where I: Iterator>, { Ok(()) } fn flush(&mut self) -> Result<(), Self::Error> { Ok(()) } fn size(&self) -> Size { Size::new(1, 1) } fn clear(&mut self) -> Result<(), Self::Error> { Ok(()) } } impl Input for NullBackend { fn poll_event(&mut self, _timeout: Duration) -> Option { None } } impl Cursor for NullBackend {} fn assert_is_backend(_backend: &B) {} assert_is_backend(&NullBackend); ``` ### impl Backend for T ```rust impl Backend for T { } ``` ## src/backend/headless.rs ### Headless ```rust pub struct Headless { } ``` In-memory backend for testing. Stores presented content and allows injecting synthetic events. ### impl Headless ```rust impl Headless { pub fn new(width: u16, height: u16) -> Self; pub fn grid(&self) -> &Grid; pub fn layer_grid(&self) -> &Grid; pub fn cursor_visible(&self) -> bool; pub fn cursor_position(&self) -> Pos; pub fn cursor_style(&self) -> CursorStyle; pub fn push_event(&mut self, event: Event); pub fn format_view(&self) -> String; pub fn format_styled(&self) -> String; } ``` ### impl Output for Headless ```rust impl Output for Headless { } ``` ### impl Input for Headless ```rust impl Input for Headless { } ``` ### impl Cursor for Headless ```rust impl Cursor for Headless { } ```