pub struct Grid { /* private fields */ }Expand description
A 2D buffer of Tiles, 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). 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, write_grapheme,
write_span, 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<Pos>/IndexMut<Pos>, 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) 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, Grid, Pos, Style};
let mut grid = Grid::new(10, 5);
grid.put_tile(0, Pos::new(2, 1), retroglyph_core::Tile::new('@', Style::new().fg(Color::GREEN)));
assert_eq!(grid[Pos::new(2, 1)].glyph(), '@');Implementations§
Source§impl Grid
impl Grid
Sourcepub fn new(width: u16, height: u16) -> Self
pub fn new(width: u16, height: u16) -> Self
Creates a new grid of the given dimensions.
Layer 0 is allocated immediately. Layers 1–255 are None until first
write via put_tile; the layer table itself only
grows as far as the highest layer id ever written, not all 256 slots
up front.
Sourcepub fn from_charmap<F>(map: &str, f: F) -> Self
pub fn from_charmap<F>(map: &str, f: F) -> Self
Build a grid from a rectangular character map, one Tile per cell.
map is split on \n; the grid width is the longest line’s character
count and the height is the number of lines. Lines shorter than the
widest are padded with the default tile. f maps each character to its
tile, called once per character in reading order.
Characters are counted as Unicode scalar values (one column each), which matches ASCII / CP437 maps and level/prefab strings. Wide characters are not width-adjusted.
§Examples
use retroglyph_core::{Grid, Pos, Style, Tile};
// A ragged map: the second line is shorter than the first.
let grid = Grid::from_charmap("###\n#.", |c| match c {
'#' => Tile::new('#', Style::default()),
_ => Tile::default(),
});
// Width comes from the longest line; the shorter line is padded with the default
// tile rather than truncating the grid to the shortest line.
assert_eq!((grid.width(), grid.height()), (3, 2));
assert_eq!(grid[Pos::new(0, 0)].glyph(), '#');
assert_eq!(grid[Pos::new(1, 1)].glyph(), ' '); // '.' maps to the default tile
assert_eq!(grid[Pos::new(2, 1)].glyph(), ' '); // padding past the short line's endSourcepub const fn max_layer(&self) -> u8
pub const fn max_layer(&self) -> u8
Returns the highest layer id that has ever been allocated.
Always at least 0 (layer 0 is always allocated). This only grows: clearing a layer does not deallocate it, so the value does not shrink once a higher layer has been written.
Sourcepub fn grapheme(&self, layer: u8, x: u16, y: u16) -> Option<&str>
pub fn grapheme(&self, layer: u8, x: u16, y: u16) -> Option<&str>
Returns the full grapheme cluster stored for the tile at (x, y) on
layer, if any.
Some only when the tile has TileFlags::HAS_EXTRA set, i.e. it
was written via write_grapheme with a
multi-codepoint EGC (combining marks, ZWJ sequences, etc.). For the
common single-codepoint case, or without the egc feature, this is
always None; use tile’s
Tile::glyph and
encode_utf8 to reconstruct the string instead.
Returns None if the layer is unallocated or the coordinates are out
of bounds.
Sourcepub fn cells(&self, layer: u8) -> Option<Cells<'_>>
pub fn cells(&self, layer: u8) -> Option<Cells<'_>>
Iterates all tiles on layer with their (x, y) coordinates.
Returns None if the layer is unallocated.
Sourcepub fn cells_mut(&mut self, layer: u8) -> Option<CellsMut<'_>>
pub fn cells_mut(&mut self, layer: u8) -> Option<CellsMut<'_>>
Iterates all tiles on layer mutably with their (x, y) coordinates.
Returns None if the layer is unallocated, mirroring cells’s
fallibility. Use cells_mut_or_alloc to allocate the layer
first instead of failing.
Sourcepub fn cells_mut_or_alloc(&mut self, layer: u8) -> CellsMut<'_> ⓘ
pub fn cells_mut_or_alloc(&mut self, layer: u8) -> CellsMut<'_> ⓘ
Iterates all tiles on layer mutably with their (x, y) coordinates, allocating the
layer first if it has not been written to yet.
Prefer cells_mut unless an empty layer legitimately needs to exist
after this call returns; unlike that method, this one never fails, at the cost of always
allocating.
Sourcepub fn clear(&mut self, layer: u8)
pub fn clear(&mut self, layer: u8)
Clears a specific layer, resetting all tiles to the default.
Does nothing if the layer is unallocated.
Sourcepub fn resize(&mut self, width: u16, height: u16)
pub fn resize(&mut self, width: u16, height: u16)
Resize the grid to width × height tiles.
Content within the overlapping region is preserved on all allocated layers. New cells are initialised to the default tile. Shrinking discards tiles outside the new bounds.
Sourcepub fn write_grapheme(
&mut self,
layer: u8,
x: u16,
y: u16,
grapheme: &str,
style: Style,
)
Available on crate feature egc only.
pub fn write_grapheme( &mut self, layer: u8, x: u16, y: u16, grapheme: &str, style: Style, )
egc only.Write a grapheme cluster at (x, y) on layer 0, enforcing wide-
character invariants.
This is the canonical way to place content into the grid when the egc
feature is enabled. It:
- Clears any wide character whose primary or spacer cell would be overwritten.
- Sets
TileFlags::WIDE_CHARon the primary cell and places aTileFlags::WIDE_CHAR_SPACERin the adjacent cell for 2-column characters. - Stores multi-codepoint EGCs (combining marks, ZWJ sequences) in the
layer’s EGC side-table (see
grapheme), capped at 8 codepoints total.
Also does nothing if the grapheme has zero display width, or if a 2-column wide character would overflow the grid (the last column needs both its own cell and a spacer).
§Panics
Panics if the grapheme’s display width exceeds u16::MAX. In
practice this cannot happen: the maximum Unicode grapheme width is 2.
Only present when the egc feature is enabled.
Source§impl Grid
impl Grid
Sourcepub fn write_span<S: AsRef<str>>(
&mut self,
layer: u8,
x: u16,
y: u16,
rows: &[S],
style: Style,
) -> Option<()>
pub fn write_span<S: AsRef<str>>( &mut self, layer: u8, x: u16, y: u16, rows: &[S], style: Style, ) -> Option<()>
Writes a multi-cell span at (x, y) on layer: one piece of artwork occupying a block of
cells rather than one.
rows holds one string per row of the footprint, so the span is rows.len() cells tall
and rows[0]’s character count wide, and every row must be that same width. Any
AsRef<str> row works, so a literal footprint (&["[==]", "|__|"]) and a computed one
(&Vec<String>) both pass without a borrowing pass over the rows. The first
character goes to the anchor cell at (x, y) with TileFlags::SPAN_ANCHOR; each
remaining character goes to its own cell with TileFlags::SPAN_COVERED. style applies
to every cell.
§Text fallback
The covered cells keep real glyphs, which is what lets one call render correctly on every backend with no capability check:
- A cell backend (
Headless,retroglyph-crossterm,retroglyph-terminal) ignoresTileFlags::SPAN_COVEREDand prints all of them, so["[==]", "|__|"]reads as a small piece of ASCII art. - A pixel backend (
retroglyph-software,retroglyph-gl) 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.
Any existing span or wide character the footprint would partially overwrite is cleared
first, in full, as write_grapheme does for its own 1- or 2-cell
write.
For the common sprite case (one runtime-chosen anchor glyph, blanks in every covered
cell), write_span_uniform says the same thing without
building the rows.
§Returns
Some(()) once the whole span is written, or None having written nothing at all when
rows is empty, its first row is empty, its rows differ in width, either axis exceeds 255
cells, or the footprint would not fit in the grid at (x, y).
§Examples
use retroglyph_core::{Grid, Pos, Style};
let mut grid = Grid::new(8, 4);
grid.write_span(0, 1, 1, &["[==]", "|__|"], Style::default())?;
assert_eq!(grid.tile(0, Pos::new(1, 1))?.span(), (4, 2));
// Covered cells keep their fallback glyphs, and name their anchor.
assert_eq!(grid.tile(0, Pos::new(4, 2))?.glyph(), '|');
assert_eq!(grid.span_owner(0, 4, 2), Some(Pos::new(1, 1)));Sourcepub fn write_span_uniform(
&mut self,
layer: u8,
pos: impl Into<Pos>,
size: impl Into<Size>,
anchor: char,
fill: char,
style: Style,
) -> Option<()>
pub fn write_span_uniform( &mut self, layer: u8, pos: impl Into<Pos>, size: impl Into<Size>, anchor: char, fill: char, style: Style, ) -> Option<()>
Writes a size multi-cell span at pos on layer: anchor in the anchor cell, fill
in every other cell of the footprint.
The uniform case of write_span, and the shape a sheet-driven
renderer usually wants: one sprite, chosen at runtime, with the cells it covers blanked so
nothing shows through its transparent pixels. Spelling that as an array of blank rows
carries no information and, for a computed anchor, has to be allocated per draw.
fill is what a cell backend prints for the covered cells (a pixel backend skips them
and draws the sprite instead), so it is the span’s text fallback: ' ' blanks them, and a
visible character keeps the footprint legible in a terminal. See
write_span for the full write semantics.
§Returns
Some(()) once the whole span is written, or None having written nothing at all when
either axis of size is 0 or exceeds 255 cells, or the footprint would not fit in the
grid at pos.
§Examples
use retroglyph_core::{Grid, Pos, Style};
let mut grid = Grid::new(8, 4);
let anchor = '\u{E000}'; // chosen at runtime from a tilesheet
grid.write_span_uniform(0, (1, 1), (2, 2), anchor, ' ', Style::default())?;
assert_eq!(grid.tile(0, Pos::new(1, 1))?.span(), (2, 2));
assert_eq!(grid.span_owner(0, 2, 2), Some(Pos::new(1, 1)));Sourcepub fn span_owner(&self, layer: u8, x: u16, y: u16) -> Option<Pos>
pub fn span_owner(&self, layer: u8, x: u16, y: u16) -> Option<Pos>
The anchor of the multi-cell span occupying (x, y) on layer, or None when the cell
belongs to no span or is out of bounds.
An anchor cell reports itself, so every cell of one span answers with the same position and hit-testing multi-cell artwork is a single comparison:
grid.write_span(0, 2, 1, &["[==]", "|__|"], Style::default())?;
let chest = Pos::new(2, 1);
// Any of the eight cells counts as standing on the chest.
assert_eq!(grid.span_owner(0, 2, 1), Some(chest));
assert_eq!(grid.span_owner(0, 5, 2), Some(chest));
assert_eq!(grid.span_owner(0, 6, 2), None);O(1): a covered tile stores its offset back to the anchor (see Tile::span_offset), so
this is a lookup and a subtraction, not a scan.
Sourcepub fn clear_span(&mut self, layer: u8, x: u16, y: u16)
pub fn clear_span(&mut self, layer: u8, x: u16, y: u16)
Clears the whole multi-cell span that (x, y) on layer belongs to, anchor included,
resetting every one of its cells to the default (empty) tile.
Works from any cell of the span, so it pairs with span_owner: hit-test
a cell, then clear the artwork it belongs to. Does nothing if the cell is not part of a
span, is out of bounds, or the layer is unallocated.
Source§impl Grid
impl Grid
Sourcepub fn put_tile(
&mut self,
layer: u8,
pos: impl Into<Pos>,
tile: Tile,
) -> Option<()>
pub fn put_tile( &mut self, layer: u8, pos: impl Into<Pos>, tile: Tile, ) -> Option<()>
Write a tile to layer at pos.
Allocates the layer if it has not been written to yet. Returns None
if pos is out of bounds.
To read back, use tile.
Any tile written this way has its extra grapheme text cleared, since a
caller-constructed Tile can never legitimately carry
TileFlags::HAS_EXTRA (the flag is crate-private). Internal callers
that need to preserve EGC text across a copy (e.g. blit)
follow up with a direct extras-table write. Any multi-cell span the
cell belongs to is cleared first, so a write can never leave an anchor
pointing at cells it no longer owns.
Sourcepub fn tint(&self, layer: u8, x: u16, y: u16) -> Tint
pub fn tint(&self, layer: u8, x: u16, y: u16) -> Tint
How a pixel backend recolours the sprite drawn for the cell at (x, y) on layer.
Tint::None for a cell that has never been tinted, for a cell whose glyph was
overwritten since (a glyph write drops the tint with the artwork it belonged to), and for
coordinates outside the grid or on an unallocated layer.
A tint is grid state rather than Tile state, for the same reason a multi-codepoint
grapheme is (see grapheme): it is rare per cell and Tile has no room
left. So it is read here, not through Tile::style.
Cell backends have no sprite to recolour and ignore this entirely.
Sourcepub fn set_tint(&mut self, layer: u8, x: u16, y: u16, tint: Tint)
pub fn set_tint(&mut self, layer: u8, x: u16, y: u16, tint: Tint)
Sets how a pixel backend recolours the sprite drawn for the cell at (x, y) on layer.
Applies to the cell as it stands, so it belongs after the write that put the glyph there: writing a glyph over a tinted cell drops the tint, on the grounds that a tint describes the artwork rather than the position. For a multi-cell span, tint the anchor; that is the cell a pixel backend draws the sprite from.
Setting Tint::None clears the tint, and drops the cell’s side-table entry entirely if
it held nothing else. Does nothing if (x, y) is out of bounds.
Sourcepub fn tile(&self, layer: u8, pos: impl Into<Pos>) -> Option<&Tile>
pub fn tile(&self, layer: u8, pos: impl Into<Pos>) -> Option<&Tile>
Read a tile on layer at pos, or None if the layer is
unallocated or pos is out of bounds.
Sourcepub fn tile_mut(&mut self, layer: u8, pos: impl Into<Pos>) -> Option<&mut Tile>
pub fn tile_mut(&mut self, layer: u8, pos: impl Into<Pos>) -> Option<&mut Tile>
Mutably borrow a tile on layer at pos, or None if the layer is
unallocated or pos is out of bounds.
This hands out a direct &mut Tile, so it cannot intercept a write the way
put_tile does: it does not clear a multi-cell span pos belongs to,
and it does not clear grapheme extras stored for the tile. Call
clear_span first if pos may belong to a span.
Sourcepub fn blit(
&mut self,
layer: u8,
src: &Self,
src_rect: Rect,
dst_x: u16,
dst_y: u16,
)
pub fn blit( &mut self, layer: u8, src: &Self, src_rect: Rect, dst_x: u16, dst_y: u16, )
Copy tiles from src within src_rect to self at (dst_x, dst_y)
on layer. Empty tiles (nothing written; see Tile::is_empty) are
treated as transparent and skipped. An explicit space is copied and
overwrites the destination.
Multi-cell spans (see write_span) do not survive a blit: copied
tiles keep their glyphs but lose TileFlags::SPAN_ANCHOR/TileFlags::SPAN_COVERED,
so a span degrades to exactly its text fallback. src_rect can clip a span in half, and
half a span is not a thing the grid can represent; degrading to the fallback glyphs is
both representable and the same content a cell backend would have drawn anyway.
Walks src’s and self’s layer buffers directly by flat index instead of going through
tile/put_tile per cell (see retroglyph#263):
each of those recomputes a coordinate conversion and a bounds check per cell, which this
does once per row instead. The destination layer is allocated once, up front, rather than
as a side effect of the first written cell, but only if src_rect (clamped to src’s
bounds) contains at least one non-empty tile, matching put_tile’s original
allocate-on-first-write behavior for a src_rect that is entirely transparent.
Sourcepub 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,
)
Available on crate feature color-space only.
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, )
color-space only.Same as blit but blends foreground and background
colors with the given alpha factors, using mode to compute the
blended color. fg_alpha and bg_alpha are in 0.0-1.0 range where
0.0 = keep destination, 1.0 = replace with src; for a non-
Linear mode, “replace with src” instead means
“replace with mode’s fully blended color” (see BlendMode).
Blending operates on packed RGB values; Color::Default preserves
the destination. Non-RGB color variants (Ansi/Indexed) are passed
through unblended, regardless of mode.
Requires the color-space feature (default on): BlendMode::Linear’s
per-channel color lerp is delegated to gem::Mix; the other
modes delegate to alpha_blend::BlendMode (imported in this module as
SeparableBlendMode to avoid colliding with this crate’s own BlendMode).
Like blit (see retroglyph#262/#263), walks src’s and self’s layer
buffers directly by flat index instead of per-cell tile/
put_tile, and allocates the destination layer once, up front, rather
than as a side effect of the first written cell.
Sourcepub fn layers(&self) -> impl Iterator<Item = DrawCell<'_>> + '_
pub fn layers(&self) -> impl Iterator<Item = DrawCell<'_>> + '_
Yield (layer_id, Pos, &Tile, Option<&str>) for every allocated cell
across all layers, in layer-major (0 → max_layer) then row-major
order. The last element is the tile’s grapheme text (see
grapheme), Some only when
TileFlags::HAS_EXTRA is set.
Unallocated layers are skipped. This is used by backends that need
the full frame on every draw (see crate::Output::needs_full_frame).
This iterator is zero-allocation: it walks the layer buffers inline.
Sourcepub fn diff<'a>(
&'a self,
other: &'a Self,
) -> impl Iterator<Item = DrawCell<'a>> + 'a
pub fn diff<'a>( &'a self, other: &'a Self, ) -> impl Iterator<Item = DrawCell<'a>> + 'a
Yield (layer_id, Pos, &Tile, Option<&str>) for every changed
position across all layers, in layer-major (0 → max_layer) then
row-major order. The last element is the changed tile’s grapheme text
(see grapheme).
Three cases per layer:
- Layer absent in
self: nothing yielded. - Layer in
self, absent inother(newly allocated): allwidth × heighttiles yielded. - Layer in both: only positions where the
Tileor its grapheme text differs are yielded.selfandothermust have matching dimensions for this case; the crate never callsdiffotherwise.
This iterator is zero-allocation: it walks the layer buffers inline.
Trait Implementations§
Source§impl Index<Pos<u16>> for Grid
impl Index<Pos<u16>> for Grid
Source§fn index(&self, pos: Pos) -> &Tile
fn index(&self, pos: Pos) -> &Tile
Reads the tile on layer 0 at pos.
§Panics
Panics if pos is outside the grid’s 0..width x 0..height bounds. This is the
unchecked, layer-0-only counterpart to tile, which instead returns None
on either an out-of-bounds pos or an unallocated layer; reach for tile when pos
isn’t already known to be in bounds.