2D grid of cells for rendering terminal content.
Uses a flat tuple for O(1) access per cell and minimal GC pressure. All operations are pure — they return a new buffer without mutating the original.
Fields
width— visible width in columns.height— visible height in rows.cells— flat tuple indexed byy * width + x.
Usage
iex> buffer = Alaja.Buffer.new(80, 24)
iex> buffer = Alaja.Buffer.put(buffer, 10, 5, "X", {255, 0, 0})
iex> Alaja.Buffer.get(buffer, 10, 5)
Summary
Functions
Clears all cells in the buffer.
Creates an empty buffer (width 0, height 0).
Crops a buffer to a sub-region. Out-of-range bounds are clamped. Useful for slicing a layout into a window.
Fills a rectangular region with a cell.
Fill with keyword options (alternative API).
Gets the cell at the specified coordinates.
Gets the cell at the specified coordinates (alias for get/3).
Stacks buffers horizontally (left to right) with an optional gap
(in columns) between them. Resulting buffer has sum(widths) + gap * (n - 1) columns and max(heights) rows. Buffers are
top-aligned; shorter buffers get padded with empty rows at the
bottom.
Merges two buffers. Cells from buffer2 override cells from buffer1.
Merges a matrix of cells into the buffer at specified position.
Creates a new buffer with the specified dimensions.
Overlays src onto dest at offset (x, y). Cells outside the
destination bounds are clipped. Empty cells in src (char " ",
no fg, no bg, no effects) are skipped so they don't paint over
content in dest.
Pads a buffer to a target size. Negative padding is clamped to 0. Content stays at the top-left.
Returns true if the buffer has a non-zero offset set via with_offset/3.
Puts a cell at the specified coordinates.
Returns all coordinates in the buffer as a list of {x, y} tuples.
Converts a buffer to iodata by iterating cells row-by-row and emitting
ANSI escapes via Alaja.Cell.to_ansi/1. Empty cells are coalesced into
a single space; row ends get a .
Updates a cell at the specified coordinates with a pre-existing cell struct.
Updates a cell at the specified coordinates with character and optional colors.
Checks if the coordinates are within buffer bounds.
Stacks buffers vertically (top to bottom) with an optional gap (in
rows) between them. Resulting buffer has sum(heights) + gap * (n - 1)
rows and max(widths) columns. Buffers are left-aligned; narrower
buffers get padded with empty columns on the right.
Attaches a logical offset (x, y) to a buffer without copying cells. The
buffer can later be rendered at this offset via
Alaja.Printer.print_buffer/2 or composed with Alaja.Buffer.overlay/4.
Writes a character to the buffer (alias for put/5-6).
Writes a string with foreground color only (background transparent).
Writes a character with colors to the buffer (alias for put/5-6).
Writes a string (without colour parsing) to the buffer at the given position. Each grapheme is written individually left-to-right.
Types
@type cell() :: Alaja.Cell.t()
@type coordinates() :: {non_neg_integer(), non_neg_integer()}
@type t() :: %Alaja.Buffer{ cells: tuple(), height: non_neg_integer(), offset_x: term(), offset_y: term(), width: non_neg_integer() }
Functions
Clears all cells in the buffer.
Parameters
buffer- The buffer to clear
Returns
- Buffer with all cells cleared
Examples
iex> buffer = Buffer.new(10, 10) |> Buffer.put(5, 5, "X", {255, 0, 0})
iex> cleared = Buffer.clear(buffer)
iex> Buffer.get(cleared, 5, 5).char
" "
@spec create_empty_buffer() :: t()
Creates an empty buffer (width 0, height 0).
@spec crop( t(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer() ) :: t()
Crops a buffer to a sub-region. Out-of-range bounds are clamped. Useful for slicing a layout into a window.
Examples
buf = Alaja.Buffer.new(10, 5)
Alaja.Buffer.crop(buf, 2, 1, 5, 3) # 5 cols x 3 rows starting at (2,1)
@spec fill( t(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), String.t(), Alaja.Cell.color(), Alaja.Cell.color() ) :: t()
Fills a rectangular region with a cell.
Parameters
buffer- The buffer to fillx1- Starting x coordinatey1- Starting y coordinatex2- Ending x coordinatey2- Ending y coordinatechar- Character to fill withfg- Foreground colorbg- Background color
Returns
- Updated buffer
@spec fill_with_opts( t(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), keyword() ) :: t()
Fill with keyword options (alternative API).
@spec get(t(), non_neg_integer(), non_neg_integer()) :: Alaja.Cell.t()
Gets the cell at the specified coordinates.
Parameters
buffer- The buffer to read fromx- The x coordinate (0-based)y- The y coordinate (0-based)
Returns
- The cell at the coordinates, or an empty cell if out of bounds
Examples
iex> buffer = Buffer.new(10, 10) |> Buffer.put(5, 5, "A", {255, 0, 0})
iex> Buffer.get(buffer, 5, 5).char
"A"
iex> buffer = Buffer.new(10, 10)
iex> Buffer.get(buffer, 100, 100).char
" "
@spec get_cell(t(), non_neg_integer(), non_neg_integer()) :: Alaja.Cell.t()
Gets the cell at the specified coordinates (alias for get/3).
@spec hstack([t()], non_neg_integer()) :: t()
Stacks buffers horizontally (left to right) with an optional gap
(in columns) between them. Resulting buffer has sum(widths) + gap * (n - 1) columns and max(heights) rows. Buffers are
top-aligned; shorter buffers get padded with empty rows at the
bottom.
Examples
a = Alaja.Buffer.new(3, 1) |> Alaja.Buffer.put(0, 0, "A")
b = Alaja.Buffer.new(3, 1) |> Alaja.Buffer.put(0, 0, "B")
Alaja.Buffer.hstack([a, b], 1)
# 7 cols x 1 row: 'A B '
Merges two buffers. Cells from buffer2 override cells from buffer1.
Useful for Z-index layering where higher Z buffers are merged on top.
Parameters
buffer1- Base bufferbuffer2- Overlay buffer
Returns
- Merged buffer
@spec merge_matrix(t(), non_neg_integer(), non_neg_integer(), [[Alaja.Cell.t()]]) :: t()
Merges a matrix of cells into the buffer at specified position.
Parameters
buffer- The buffer to merge intox_start- Starting x coordinatey_start- Starting y coordinatematrix- 2D list of cells to merge
Returns
- Updated buffer
@spec new(non_neg_integer(), non_neg_integer()) :: t()
Creates a new buffer with the specified dimensions.
Uses a flat tuple for O(1) access.
Parameters
width- The width of the buffer in cellsheight- The height of the buffer in cells
Returns
- A new buffer struct
Examples
iex> Buffer.new(80, 24)
%Buffer{width: 80, height: 24, cells: {...}}
@spec overlay(t(), t(), non_neg_integer(), non_neg_integer()) :: t()
Overlays src onto dest at offset (x, y). Cells outside the
destination bounds are clipped. Empty cells in src (char " ",
no fg, no bg, no effects) are skipped so they don't paint over
content in dest.
Examples
dest = Alaja.Buffer.new(10, 1)
src = Alaja.Buffer.new(3, 1) |> Alaja.Buffer.put(0, 0, "X", {255, 0, 0})
Alaja.Buffer.overlay(dest, src, 2, 0)
@spec pad(t(), non_neg_integer(), non_neg_integer()) :: t()
Pads a buffer to a target size. Negative padding is clamped to 0. Content stays at the top-left.
Examples
Alaja.Buffer.pad(buf, 10, 5) # buf becomes 10 cols x 5 rows, padded right/bottom
Returns true if the buffer has a non-zero offset set via with_offset/3.
Useful when composing layouts: a positioned buffer overlays differently than an unpositioned one.
@spec put( t(), non_neg_integer(), non_neg_integer(), String.t(), Alaja.Cell.color(), Alaja.Cell.color() ) :: t()
Puts a cell at the specified coordinates.
Parameters
buffer- The buffer to modifyx- The x coordinate (0-based)y- The y coordinate (0-based)char- The character to placefg- Optional foreground color as RGB tuplebg- Optional background color as RGB tuple
Returns
- Updated buffer
Examples
iex> buffer = Buffer.new(10, 10)
iex> buffer = Buffer.put(buffer, 5, 5, "X", {255, 0, 0}, {0, 0, 0})
iex> Buffer.get(buffer, 5, 5).char
"X"
@spec range(t()) :: [{non_neg_integer(), non_neg_integer()}]
Returns all coordinates in the buffer as a list of {x, y} tuples.
Converts a buffer to iodata by iterating cells row-by-row and emitting
ANSI escapes via Alaja.Cell.to_ansi/1. Empty cells are coalesced into
a single space; row ends get a .
Examples
iex> buffer = Alaja.Buffer.new(5, 1) |> Alaja.Buffer.put(0, 0, "H", {255, 0, 0})
iex> Alaja.Buffer.to_iodata(buffer) |> IO.iodata_to_binary()
"\e[38;2;255;0;0mH\e[0m "
@spec update_cell(t(), non_neg_integer(), non_neg_integer(), Alaja.Cell.t()) :: t()
Updates a cell at the specified coordinates with a pre-existing cell struct.
@spec update_cell( t(), non_neg_integer(), non_neg_integer(), String.t(), Alaja.Cell.color(), Alaja.Cell.color() ) :: t()
Updates a cell at the specified coordinates with character and optional colors.
@spec valid_coord?(t(), non_neg_integer(), non_neg_integer()) :: boolean()
Checks if the coordinates are within buffer bounds.
Parameters
buffer- The buffer to checkx- The x coordinatey- The y coordinate
Returns
trueif coordinates are validfalseotherwise
@spec vstack([t()], non_neg_integer()) :: t()
Stacks buffers vertically (top to bottom) with an optional gap (in
rows) between them. Resulting buffer has sum(heights) + gap * (n - 1)
rows and max(widths) columns. Buffers are left-aligned; narrower
buffers get padded with empty columns on the right.
Examples
a = Alaja.Buffer.new(1, 2) |> Alaja.Buffer.put(0, 0, "A")
b = Alaja.Buffer.new(1, 2) |> Alaja.Buffer.put(0, 0, "B")
Alaja.Buffer.vstack([a, b], 1)
@spec with_offset(t(), non_neg_integer(), non_neg_integer()) :: t()
Attaches a logical offset (x, y) to a buffer without copying cells. The
buffer can later be rendered at this offset via
Alaja.Printer.print_buffer/2 or composed with Alaja.Buffer.overlay/4.
Returns a new buffer struct with offset_x and offset_y set. Cells
are NOT moved — this is purely metadata for layout composition.
Examples
buf = Alaja.Components.Table.render_buffer(headers: ["A"], rows: [["1"]])
positioned = Alaja.Buffer.with_offset(buf, 10, 5)
positioned.offset_x # => 10
positioned.offset_y # => 5
@spec write(t(), non_neg_integer(), non_neg_integer(), String.t()) :: t()
Writes a character to the buffer (alias for put/5-6).
@spec write(t(), non_neg_integer(), non_neg_integer(), String.t(), Alaja.Cell.color()) :: t()
@spec write(t(), non_neg_integer(), non_neg_integer(), String.t(), keyword()) :: t()
Writes a string with foreground color only (background transparent).
@spec write( t(), non_neg_integer(), non_neg_integer(), String.t(), Alaja.Cell.color(), Alaja.Cell.color() ) :: t()
Writes a character with colors to the buffer (alias for put/5-6).
@spec write_string(t(), non_neg_integer(), non_neg_integer(), String.t()) :: t()
Writes a string (without colour parsing) to the buffer at the given position. Each grapheme is written individually left-to-right.
Characters beyond the buffer width are silently skipped.