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::renderreadsInstant::now()internally and applies as many ticks as the elapsed time buys (capped atMAX_CATCHUP_TICKS=4so a process resumed from suspend doesn’t render hundreds of frames at once). This is whatterminal.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 thebinaryfeature)termiontermwiz
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:
- Construction.
MatrixRainState::new(which seeds from system entropy viagetrandom) is unavailable; useMatrixRainState::with_seedwith a seed of your choosing (e.g. an on-chip RNG, an HW counter, or just a constant). - Driving frames. Without
std, [MatrixRain::render] handles resize and paints the current state but does not advance the animation — there is noInstant::now(). Drive frames manually withMatrixRainState::tickat whatever cadence your main loop dictates (e.g. once pertarget_period - render_time). - Color tier. Auto-detection (via
COLORTERM/TERM) is disabled; callMatrixRainState::set_color_countonce after construction to pick a tier.16is 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/fadezones) with each stop mapped to the nearest of the 16 namedColorvariants 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::Customare 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 singlechars 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::tickwith wall-clock rendering produces visible drift over time. Tick driving is exact (each call advances exactly one frame); wall-clock driving advances based on elapsedInstants. 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_countto force a higher tier if your terminal actually supports it, or supply aTheme::Customramp whose stops are already in the named-color palette. - Non-TTY refusal (binary only). The standalone
matrixbinary 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.--helpand--versionstill work in non-TTY contexts (they run before the check).
Structs§
- Color
Ramp - Five-stop color ramp.
headis the brightest cell (typically white); stops degrade throughbright,mid,dim, to the visible-but-faintfadeat the tail. - Matrix
Config - Read-only configuration for
MatrixRain. - Matrix
Config Builder - Fluent builder for
MatrixConfig. - Matrix
Rain - The ratatui widget rendering the Matrix digital rain effect.
- Matrix
Rain State - Per-frame animation state for a
MatrixRainwidget.
Enums§
- CharSet
- Source of glyphs for the falling drops.
- Matrix
Error - Errors produced by
MatrixConfigBuilder::buildand related validation routines. - Theme
- Color theme controlling the trail gradient.
Constants§
- MAX_
TRAIL_ LIMIT - Hard upper bound on
MatrixConfig::max_trail.