matrix-rain
The classic Matrix digital rain effect — glowing columns of characters falling from the top of the terminal — packaged as both a ratatui StatefulWidget and a standalone matrix binary.
- Smooth animation at 30–60 FPS, configurable density / speed / trail length.
- Five built-in themes (classic green, amber, cyan, red, rainbow) and five charsets (matrix, ascii, hex, binary, or a UTF-8 file you supply).
- Three rendering tiers — smooth RGB interpolation on truecolor terminals, quantized 5-stop ramp on 256-color, nearest-named collapse on 16-color — automatically detected and cached per state.
- Drop-in widget that respects
area.x/area.yoffsets and handles resize, suspend/resume, broken-pipe shutdown, and panic-during-raw-mode without stranding the terminal.
Install
Or from a local checkout:
The package is matrix-rain; the installed binary is matrix.
Run
Quit with q, Esc, or Ctrl-C (or use --quit-on-any-key).
CLI options
| Flag | Default | Description |
|---|---|---|
-f, --fps <FPS> |
30 |
Frames per second (must be ≥ 1) |
-s, --speed <FLOAT> |
1.0 |
Global speed multiplier (> 0, finite) |
-d, --density <0..1> |
0.6 |
Fraction of columns kept active |
--charset <NAME|PATH> |
matrix |
matrix, ascii, hex, binary, or a path to a UTF-8 charset file (≤ 1 MiB) |
--theme <NAME> |
green |
green, amber, cyan, red, rainbow |
--no-head-white |
off | Disable the classic white head; use the theme's "bright" color instead |
--no-bold |
off | Disable bold rendering on the head cell |
--seed <U64> |
random | Deterministic RNG seed (system entropy by default) |
-q, --quit-on-any-key |
off | Exit on any keypress instead of just q/Esc/Ctrl-C |
Library usage
Add to Cargo.toml:
[]
= { = "0.1", = false }
= "0.26"
(Disabling default features drops clap, anyhow, and signal-hook, which are only used by the standalone binary.)
Embed the widget inside a larger ratatui layout:
use *;
use Block;
use ;
let cfg = builder
.fps
.density
.build?;
let mut state = new;
terminal.draw?;
Runnable examples in examples/:
cargo run --example standalone— full-screen democargo run --example embedded— widget inside a larger layoutcargo run --example custom_charset—CharSet::Customwith hand-picked glyphs
The widget is intentionally StatefulWidget-only. Animation needs state that persists across frames (MatrixRainState holds per-column streams, RNG, timing, and a cached color count); a stateless wrapper would either reset every frame or hide global mutable state behind the API.
Built-in character sets
CharSet |
Contents |
|---|---|
Matrix (default) |
Half-width katakana U+FF66..=U+FF9D + digits 0-9 (66 glyphs) |
Ascii |
Printable ASCII 0x21..=0x7E (94 glyphs, space excluded) |
Hex |
0-9 a-f (lowercase) |
Binary |
0 and 1 |
Custom(Vec<char>) |
User-supplied glyphs. Builder rejects empty / control chars. |
Built-in themes
Theme |
Look |
|---|---|
ClassicGreen (default) |
White head over green trail (0xCCFFCC → 0x00FF00 → 0x009900 → 0x003300) |
Amber |
Phosphor amber |
Cyan |
Cyan glow |
Red |
Hostile red |
Rainbow |
White head → red → yellow → green → blue across the trail |
Custom(ColorRamp) |
Hand-built 5-stop ramp |
Determinism
Pass --seed <U64> (or use MatrixRainState::with_seed) to get a reproducible animation. The library's snapshot test suite drives frames purely via state.tick() so output is identical across runs given the same seed, area, and config.
MatrixRainState::set_color_count(count) overrides the cached terminal color count, letting you force a specific rendering tier (e.g. 16 for accessibility, u16::MAX to force smooth interpolation, or any value in tests where TERM/COLORTERM shouldn't matter).
Caveats
- Full-width and combining characters in custom charsets 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 can't 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 elapsedInstants. Pick one mode per session. - Non-TTY refusal. The 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.
--helpand--versionstill work in non-TTY contexts (they run before the check).
License
MIT. See LICENSE (or Cargo.toml's license field) for details.