Expand description
The layered tile grid: Grid, plus the Size, Pos, and Rect
coordinate types used throughout the crate.
§Layers, draw order, and compositing
A Grid holds up to 256 independent layers (u8 ids 0..=255), one
Tile per cell on each. Layer 0 is always allocated; layers 1-255 are
allocated lazily, on first write to that layer (see
put_tile): a
single-layer game pays zero overhead for layers it never writes to. This is the
crate’s most distinctive feature and the one most worth understanding
before reaching for a second layer.
Each cell carries a glyph, foreground/background Color, and
sub-cell pixel offsets. Color covers the full spectrum: the
terminal’s default foreground/background, the 16 standard ANSI colors, the 256-color
palette, and 24-bit RGB.
Style has no text modifiers (bold, italic, underline, …);
see its own doc comment for the full rationale.
§Draw order
painted last -> layer 3 UI / effects (topmost)
layer 2 actors
layer 1 items
painted first -> layer 0 terrain (always allocated)
ascending id == ascending z; no separate depth field.
each layer holds one Tile per cell of the whole grid.Layers composite bottom-to-top, in ascending id order: 0 first, then every
allocated layer up to max_layer, each painted over
whatever the layers below it produced. Layer id is z-order: there is
no separate depth or z-index to set. A common convention is layer 0 for
terrain, 1 for items, 2 for actors, 3+ for UI/effects, but the crate
enforces nothing; any id can hold any content.
For overlapping UI specifically (chrome, popups, debug overlays, as opposed to a tile map’s
own terrain/items/actors split), see crate::surface::Layer and
Surface::on_tier for a small named convention and why
it beats ordering draw calls.
Compositing itself happens in one of two places, chosen by the backend
(see crate::backend::Output::composites_layers):
- Cell backends (
Headless,retroglyph-crossterm) do not composite layers themselves.crate::terminal::Terminal::presentcallsflatten_into(crate-private) to collapse every allocated layer into a single-layer frame before handing it to the backend, so layers 1+ behave identically on every cell backend. - Pixel backends (
retroglyph-software) composite per pixel: they receive the raw layered stream fromcrate::backend::Output::draw_layers(layer-major, ascending id) and paint each layer’s cells directly onto the pixel buffer in that order.
§The EMPTY flag: transparency vs. opaque occlusion
Every Tile carries TileFlags::EMPTY, set on Tile::default and
cleared by every write (put_tile, write_grapheme, indexing, …).
Compositing treats it as the transparency bit:
- An untouched cell (
EMPTYset) is fully transparent:blitskips it, andflatten_into(crate-private) leaves whatever the layers below already drew. - An explicit space (
Tile::new(' ', style),EMPTYclear) is opaque: it overwrites the glyph and foreground below it, same as any other character. This is the one sharp edge in the model:' 'painted on a higher layer erases content underneath, it does not reveal it.
Background color follows its own rule, independent of EMPTY: a tile’s
background only overwrites the composited background when it is not
Color::Default. A non-empty tile with a Default
background still lets a lower layer’s background show through even though its glyph is
opaque. See flatten_into (crate-private) for the exact rule.
§Multi-cell spans
write_span writes one piece of artwork across a w x h block of cells:
the top-left cell is the anchor (TileFlags::SPAN_ANCHOR, carrying the footprint), and
every other cell is covered (TileFlags::SPAN_COVERED, carrying its offset back to the
anchor). span_owner resolves any cell of a span to its anchor in O(1),
so hit-testing a multi-cell sprite is one lookup rather than a rectangle scan.
Covered cells keep real glyphs, and that is the point: they are the span’s text fallback.
One write_span call renders correctly on every backend without a capability check.
- A cell backend ignores
SPAN_COVEREDand prints allw * hglyphs, so["C=", "[]"]reads as a little piece of ASCII art. - A pixel backend looks the anchor glyph up in its sprite cache, draws that one sprite across the whole footprint, and skips every covered cell’s glyph.
This is the deliberate difference from TileFlags::WIDE_CHAR_SPACER, which every backend
skips: a wide character’s spacer has no content of its own, whereas a covered cell does.
A span is written and cleared whole. Any ordinary write into one of its cells
(put_tile, write_grapheme)
clears the entire span first, so an anchor can never be left claiming cells it no longer owns.
The exceptions are the escape hatches that hand out a &mut Tile directly
(tile_mut, IndexMut), which cannot intercept the
write; use clear_span first if you reach for one of those on a grid
that uses spans.
§No short-circuiting: every allocated layer is visited, for every cell
Compositing does not stop early when it hits an opaque tile on a high
layer. Both flatten_into (crate-private) and the software
backend’s per-pixel compositor walk layers 0..=max_layer in order for
every cell, unconditionally, even if a fully opaque tile on layer 5
makes layers 6-50 invisible at that position. An opaque high layer hides
the layers below it visually but never occludes them from the pass, so
prefer low, contiguous layer ids for frequently-updated content and
reserve high ids for rarely-touched overlays (e.g. a debug HUD pinned to
layer 255). See max_layer for the iteration cost this
implies and Grid::new for the allocation cost of a first write.
Structs§
- Grid
- A 2D buffer of
Tiles, addressable across up to 256 stacked layers. - Offset
- A sub-cell pixel offset
(dx, dy), distinct fromPosso a caller can’t transpose a position and an offset in a call likeSurface::put_offset.
Enums§
- Blend
Mode - Blend mode for
Grid::blit_alpha, selecting how source and destination colors combine before thefg_alpha/bg_alphafactor is applied.
Traits§
- HasSize
.width()/.height()accessors forSize(andRect): re-exported so callers don’t need a directixydependency just to call them on this crate’s own type aliases. A type that has a [Size<T>].