Skip to main content

Crate matrix_rain

Crate matrix_rain 

Source
Expand description

Classic “Matrix digital rain” effect for terminals, packaged as both a ratatui StatefulWidget library and a standalone matrix binary.

The crate is published as matrix-rain on crates.io because the bare matrix name is taken; the installed binary is still called matrix.

§Quick start

Drop the widget into any ratatui layout. MatrixRainState carries the per-frame animation state (column streams, RNG, timing, cached color tier) across renders.

use matrix_rain::{MatrixConfig, MatrixRain, MatrixRainState};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::StatefulWidget;

let cfg = MatrixConfig::builder().fps(30).density(0.5).build().unwrap();
let mut state = MatrixRainState::with_seed(42);
let area = Rect::new(0, 0, 80, 24);
let mut buf = Buffer::empty(area);

MatrixRain::new(&cfg).render(area, &mut buf, &mut state);
assert_eq!(state.streams_len(), 80);

For a full embed inside a ratatui::Terminal event loop, see examples/embedded.rs in the source repo. For the standalone full-screen demo, see examples/standalone.rs.

§Driving frames

There are two ways to advance the animation:

  • Wall-clock (default). Each call to MatrixRain::render reads Instant::now() internally and applies as many ticks as the elapsed time buys (capped at MAX_CATCHUP_TICKS=4 so a process resumed from suspend doesn’t render hundreds of frames at once). This is what terminal.draw(…) does naturally and what the bundled binary uses.
  • Manual via MatrixRainState::tick. Each call advances exactly one frame regardless of wall-clock time. Useful for deterministic snapshot tests and external tick-loop apps.

Mixing both modes in the same session produces visible drift; the snapshot suite suppresses wall-clock advance by setting fps=1 together with a tiny speed (e.g. 0.001) so the elapsed-time conversion floors to zero ticks per render. See MatrixRainState::set_color_count if you also need to lock the rendering tier for reproducibility.

§Backends

The library is backend-agnostic — MatrixRain renders into a ratatui Buffer, so any ratatui backend works. Pick one via a feature flag:

  • crossterm (default; enabled by the binary feature)
  • termion
  • termwiz

Each feature simply forwards to ratatui’s same-named feature, so a single line in Cargo.toml covers both crates:

matrix-rain = { version = "0.3", default-features = false, features = ["termion"] }

The default binary feature pulls in crossterm (plus clap, anyhow, and signal-hook) for the standalone matrix binary. Library-only users who don’t need the binary should opt out with default-features = false and pick exactly one backend feature.

§no_std / embedded

The library is no_std-capable (alloc is required for Vec-backed per-column streams; the binary feature, the wall-clock animation path, and env-var-based terminal capability detection all require std).

To use the widget on a target without std (e.g. ESP32 with esp-hal + the mousefood embedded ratatui backend), opt out of every feature:

matrix-rain = { version = "0.3", default-features = false }
ratatui = { version = "0.30", default-features = false, features = ["underline-color"] }

Embedded usage differs from desktop in three places:

  1. Construction. MatrixRainState::new (which seeds from system entropy via getrandom) is unavailable; use MatrixRainState::with_seed with a seed of your choosing (e.g. an on-chip RNG, an HW counter, or just a constant).
  2. Driving frames. Without std, [MatrixRain::render] handles resize and paints the current state but does not advance the animation — there is no Instant::now(). Drive frames manually with MatrixRainState::tick at whatever cadence your main loop dictates (e.g. once per target_period - render_time).
  3. Color tier. Auto-detection (via COLORTERM / TERM) is disabled; call MatrixRainState::set_color_count once after construction to pick a tier. 16 is the safest default for an embedded display.

§Color tiers

Color depth is detected once per state on the first render via an env-var sniff: COLORTERM=truecolor|24bit (de-facto standard for advertising 24-bit support) wins; otherwise TERM is checked for *256color*. The result is cached on the state and drives one of three rendering paths:

  • Truecolor: linear RGB interpolation between the 5 stops in ColorRamp. Smoothest gradient.
  • 256-color: nearest-of-5-stops; the terminal handles any further RGB→256 quantization.
  • 16-color: 3-zone collapse (head, then bright/mid/fade zones) with each stop mapped to the nearest of the 16 named Color variants by euclidean RGB distance. Detection failure or any value the widget doesn’t recognize also falls back to this path rather than panicking.

Force a specific tier with MatrixRainState::set_color_count: pass 16 for accessibility, 256 for the quantized middle tier, or u16::MAX for the smooth-interpolation path.

§Caveats

  • Full-width and combining characters in CharSet::Custom are not detected. Each glyph must occupy exactly one terminal cell or the column layout misaligns. CJK ideographs, emoji with variation selectors, and zero-width combiners are all single chars in Rust but multi-cell in the terminal. Display width cannot reliably be detected across terminals; verifying single-cell-ness is the caller’s responsibility.
  • Mixing MatrixRainState::tick with wall-clock rendering produces visible drift over time. Tick driving is exact (each call advances exactly one frame); wall-clock driving advances based on elapsed Instants. Pick one mode per session.
  • 16-color fallback is a 3-zone collapse, not the original 5-stop gradient. If your theme has stops that map to the same named color (common with monochrome themes on 16-color), zones will visually merge. Use MatrixRainState::set_color_count to force a higher tier if your terminal actually supports it, or supply a Theme::Custom ramp whose stops are already in the named-color palette.
  • Non-TTY refusal (binary only). The standalone matrix binary exits with code 2 when stdout isn’t a terminal so it doesn’t garble logs when accidentally run under a pipe, in a CI runner, or as a systemd service. --help and --version still work in non-TTY contexts (they run before the check).

Structs§

ColorRamp
Five-stop color ramp. head is the brightest cell (typically white); stops degrade through bright, mid, dim, to the visible-but-faint fade at the tail.
MatrixConfig
Read-only configuration for MatrixRain.
MatrixConfigBuilder
Fluent builder for MatrixConfig.
MatrixRain
The ratatui widget rendering the Matrix digital rain effect.
MatrixRainState
Per-frame animation state for a MatrixRain widget.

Enums§

CharSet
Source of glyphs for the falling drops.
MatrixError
Errors produced by MatrixConfigBuilder::build and related validation routines.
Theme
Color theme controlling the trail gradient.

Constants§

MAX_TRAIL_LIMIT
Hard upper bound on MatrixConfig::max_trail.