Skip to main content

Grid

Struct Grid 

Source
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

Source

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.

Source

pub fn from_charmap<F>(map: &str, f: F) -> Self
where F: FnMut(char) -> Tile,

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

pub const fn width(&self) -> u16

Returns the width of the grid.

Source

pub const fn height(&self) -> u16

Returns the height of the grid.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn clear(&mut self, layer: u8)

Clears a specific layer, resetting all tiles to the default.

Does nothing if the layer is unallocated.

Source

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.

Source

pub fn write_grapheme( &mut self, layer: u8, x: u16, y: u16, grapheme: &str, style: Style, )

Available on crate feature 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_CHAR on the primary cell and places a TileFlags::WIDE_CHAR_SPACER in 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

Source

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) ignores TileFlags::SPAN_COVERED and 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)));
Source

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

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.

Source

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

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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, )

Available on crate feature 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.

Source

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.

Source

pub fn clear_all(&mut self)

Clear every allocated layer.

Source

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 in other (newly allocated): all width × height tiles yielded.
  • Layer in both: only positions where the Tile or its grapheme text differs are yielded. self and other must have matching dimensions for this case; the crate never calls diff otherwise.

This iterator is zero-allocation: it walks the layer buffers inline.

Trait Implementations§

Source§

impl Clone for Grid

Source§

fn clone(&self) -> Grid

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Grid

Source§

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

Formats the value using the given formatter. Read more
Source§

impl Display for Grid

Source§

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

Formats the value using the given formatter. Read more
Source§

impl Index<Pos<u16>> for Grid

Source§

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.

Source§

type Output = Tile

The returned type after indexing.
Source§

impl IndexMut<Pos<u16>> for Grid

Source§

fn index_mut(&mut self, pos: Pos) -> &mut Tile

Mutably borrows the tile on layer 0 at pos.

§Panics

Panics if pos is outside the grid’s 0..width x 0..height bounds, the same bound as Index’s index. Reach for tile_mut when pos isn’t already known to be in bounds; it returns None instead of panicking.

Auto Trait Implementations§

§

impl Freeze for Grid

§

impl RefUnwindSafe for Grid

§

impl Send for Grid

§

impl Sync for Grid

§

impl Unpin for Grid

§

impl UnsafeUnpin for Grid

§

impl UnwindSafe for Grid

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

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

Source§

fn into(self) -> U

Calls U::from(self).

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

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

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

Source§

type Error = Infallible

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

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

Performs the conversion.
Source§

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

Source§

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

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

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

Performs the conversion.