Raxol.Effects.BorderBeam.Effect behaviour (Raxol v2.6.0)

View Source

Contract + shared helpers for border-effect implementations.

Each effect (Stroke, Pulse, Flames, Electric, Clouds) implements apply/4, taking a cell map, the box bounds, the hint opts, and the current monotonic time in milliseconds. Effects return a new cell map with style and/or character mutations applied.

Cell representation

A cell_map is %{{x, y} => {x, y, char, fg, bg, attrs}} -- the same six-tuple shape produced by Raxol.UI.Renderer, indexed by coordinate for O(1) modification. Raxol.Effects.BorderBeam.CellApplier builds the index, dispatches to the effect by :type, and unrolls back to a list.

Time-based determinism

Animation progress is derived from now_ms only. No effect maintains state between calls; the same now_ms always produces the same output for a given hint+bounds. For random-looking effects (flames, electric), use time_bucket/2 + bucket_hash/3 to produce reproducible-per-tick noise.

Summary

Callbacks

Apply the effect to the cell map.

Functions

Reproducible non-negative integer hash from a {bucket, x, y} triple. Use to drive char/color choices that should be stable within a bucket but vary across cells and time.

Maps an intensity (0.0-1.0) to terminal style attrs. :bold for bright, plain for mid-range, :dim for low. Strips any pre-existing intensity attrs so re-applying is idempotent.

Returns the perimeter as a tuple for O(1) elem/2 access. Order is clockwise from the top-left corner: top edge, right edge, bottom edge (right-to-left), left edge (bottom-to-top).

Replace the character, foreground, and intensity attrs of a cell.

Replace the foreground color and intensity attrs of a cell.

Returns a discrete time bucket for the given millisecond clock and bucket size. Used to keep random-but-deterministic effects stable for ~bucket_ms between changes.

Types

bounds()

@type bounds() :: %{
  x: non_neg_integer(),
  y: non_neg_integer(),
  width: pos_integer(),
  height: pos_integer()
}

cell()

@type cell() ::
  {non_neg_integer(), non_neg_integer(), String.t(), any(), any(), list()}

cell_map()

@type cell_map() :: %{required({non_neg_integer(), non_neg_integer()}) => cell()}

Callbacks

apply(cells, bounds, opts, now_ms)

@callback apply(
  cells :: cell_map(),
  bounds :: bounds(),
  opts :: map(),
  now_ms :: integer()
) :: cell_map()

Apply the effect to the cell map.

  • cells is the keyed cell index produced by CellApplier.
  • bounds are the positioned box dimensions.
  • opts is the raw hint map (:variant, :strength, plus per-effect keys).
  • now_ms is the monotonic millisecond clock at frame time.

Functions

bucket_hash(bucket, x, y)

@spec bucket_hash(integer(), integer(), integer()) :: non_neg_integer()

Reproducible non-negative integer hash from a {bucket, x, y} triple. Use to drive char/color choices that should be stable within a bucket but vary across cells and time.

intensity_attrs(attrs, intensity)

@spec intensity_attrs(list(), float()) :: list()

Maps an intensity (0.0-1.0) to terminal style attrs. :bold for bright, plain for mid-range, :dim for low. Strips any pre-existing intensity attrs so re-applying is idempotent.

Empty-attrs fast path skips the Enum.reject allocation, which is the vast majority of cells in a freshly rendered border.

perimeter(map)

@spec perimeter(bounds()) :: {tuple(), pos_integer()}

Returns the perimeter as a tuple for O(1) elem/2 access. Order is clockwise from the top-left corner: top edge, right edge, bottom edge (right-to-left), left edge (bottom-to-top).

restyle_char(cells, x, y, char, color, intensity)

@spec restyle_char(
  cell_map(),
  integer(),
  integer(),
  String.t(),
  atom(),
  float()
) :: cell_map()

Replace the character, foreground, and intensity attrs of a cell.

Used by char-replacing effects (flames, electric). No-op if the cell does not exist.

restyle_fg(cells, x, y, color, intensity)

@spec restyle_fg(cell_map(), integer(), integer(), atom(), float()) :: cell_map()

Replace the foreground color and intensity attrs of a cell.

Returns the cell map unchanged if no cell exists at {x, y}.

time_bucket(now_ms, bucket_ms)

@spec time_bucket(integer(), pos_integer()) :: integer()

Returns a discrete time bucket for the given millisecond clock and bucket size. Used to keep random-but-deterministic effects stable for ~bucket_ms between changes.