Alaja.Buffer (Alaja v2.2.0)

Copy Markdown View Source

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 by y * 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

cell()

@type cell() :: Alaja.Cell.t()

coordinates()

@type coordinates() :: {non_neg_integer(), non_neg_integer()}

t()

@type t() :: %Alaja.Buffer{
  cells: tuple(),
  height: non_neg_integer(),
  offset_x: term(),
  offset_y: term(),
  width: non_neg_integer()
}

Functions

clear(buffer)

@spec clear(t()) :: t()

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
" "

create_empty_buffer()

@spec create_empty_buffer() :: t()

Creates an empty buffer (width 0, height 0).

crop(buffer, x, y, w, h)

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)

fill(buffer, x1, y1, x2, y2, char \\ " ", fg \\ nil, bg \\ nil)

Fills a rectangular region with a cell.

Parameters

  • buffer - The buffer to fill
  • x1 - Starting x coordinate
  • y1 - Starting y coordinate
  • x2 - Ending x coordinate
  • y2 - Ending y coordinate
  • char - Character to fill with
  • fg - Foreground color
  • bg - Background color

Returns

  • Updated buffer

fill_with_opts(buffer, x1, y1, x2, y2, opts)

@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).

get(buffer, x, y)

@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 from
  • x - 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
" "

get_cell(buffer, x, y)

@spec get_cell(t(), non_neg_integer(), non_neg_integer()) :: Alaja.Cell.t()

Gets the cell at the specified coordinates (alias for get/3).

hstack(buffers, gap \\ 0)

@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   '

merge(buffer1, buffer2)

@spec merge(t(), t()) :: t()

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 buffer
  • buffer2 - Overlay buffer

Returns

  • Merged buffer

merge_matrix(buffer, x_start, y_start, matrix)

@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 into
  • x_start - Starting x coordinate
  • y_start - Starting y coordinate
  • matrix - 2D list of cells to merge

Returns

  • Updated buffer

new(width, height)

@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 cells
  • height - The height of the buffer in cells

Returns

  • A new buffer struct

Examples

iex> Buffer.new(80, 24)
%Buffer{width: 80, height: 24, cells: {...}}

overlay(dest, src, x_offset, y_offset)

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

pad(buffer, target_w, target_h)

@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

positioned?(buffer)

@spec positioned?(t()) :: boolean()

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.

put(buffer, x, y, char, fg \\ nil, bg \\ nil)

Puts a cell at the specified coordinates.

Parameters

  • buffer - The buffer to modify
  • x - The x coordinate (0-based)
  • y - The y coordinate (0-based)
  • char - The character to place
  • fg - Optional foreground color as RGB tuple
  • bg - 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"

range(buffer)

@spec range(t()) :: [{non_neg_integer(), non_neg_integer()}]

Returns all coordinates in the buffer as a list of {x, y} tuples.

to_iodata(buffer)

@spec to_iodata(t()) :: iodata()

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    "

update_cell(buffer, x, y, cell)

@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.

update_cell(buffer, x, y, char, fg \\ nil, bg \\ nil)

@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.

valid_coord?(buffer, x, y)

@spec valid_coord?(t(), non_neg_integer(), non_neg_integer()) :: boolean()

Checks if the coordinates are within buffer bounds.

Parameters

  • buffer - The buffer to check
  • x - The x coordinate
  • y - The y coordinate

Returns

  • true if coordinates are valid
  • false otherwise

vstack(buffers, gap \\ 0)

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

with_offset(buffer, x, y)

@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

write(buffer, x, y, char)

@spec write(t(), non_neg_integer(), non_neg_integer(), String.t()) :: t()

Writes a character to the buffer (alias for put/5-6).

write(buffer, x, y, string, fg)

@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).

write(buffer, x, y, char, fg, bg)

Writes a character with colors to the buffer (alias for put/5-6).

write_string(buffer, x, y, string)

@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.