Skip to main content

Module grid

Module grid 

Source
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::present calls flatten_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 from crate::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 (EMPTY set) is fully transparent: blit skips it, and flatten_into (crate-private) leaves whatever the layers below already drew.
  • An explicit space (Tile::new(' ', style), EMPTY clear) 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_COVERED and prints all w * h glyphs, 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 from Pos so a caller can’t transpose a position and an offset in a call like Surface::put_offset.

Enums§

BlendMode
Blend mode for Grid::blit_alpha, selecting how source and destination colors combine before the fg_alpha/bg_alpha factor is applied.

Traits§

HasSize
.width()/.height() accessors for Size (and Rect): re-exported so callers don’t need a direct ixy dependency just to call them on this crate’s own type aliases. A type that has a [Size<T>].

Type Aliases§

Pos
Pos in the grid, in (x = column, y = row) order.
Rect
Rectangle in the grid.
Size
Size of the grid.