Skip to main content

matrix_rain/
lib.rs

1//! Classic "Matrix digital rain" effect for terminals, packaged as both a
2//! [ratatui](https://ratatui.rs/) [`StatefulWidget`](ratatui::widgets::StatefulWidget)
3//! library and a standalone `matrix` binary.
4//!
5//! The crate is published as `matrix-rain` on crates.io because the bare `matrix`
6//! name is taken; the installed binary is still called `matrix`.
7//!
8//! # Quick start
9//!
10//! Drop the widget into any ratatui layout. `MatrixRainState` carries the
11//! per-frame animation state (column streams, RNG, timing, cached color tier)
12//! across renders.
13//!
14//! ```
15//! use matrix_rain::{MatrixConfig, MatrixRain, MatrixRainState};
16//! use ratatui::buffer::Buffer;
17//! use ratatui::layout::Rect;
18//! use ratatui::widgets::StatefulWidget;
19//!
20//! let cfg = MatrixConfig::builder().fps(30).density(0.5).build().unwrap();
21//! let mut state = MatrixRainState::with_seed(42);
22//! let area = Rect::new(0, 0, 80, 24);
23//! let mut buf = Buffer::empty(area);
24//!
25//! MatrixRain::new(&cfg).render(area, &mut buf, &mut state);
26//! assert_eq!(state.streams_len(), 80);
27//! ```
28//!
29//! For a full embed inside a [`ratatui::Terminal`] event loop, see
30//! `examples/embedded.rs` in the source repo. For the standalone full-screen
31//! demo, see `examples/standalone.rs`.
32//!
33//! # Driving frames
34//!
35//! There are two ways to advance the animation:
36//!
37//! - **Wall-clock (default).** Each call to
38//!   [`MatrixRain::render`](ratatui::widgets::StatefulWidget::render) reads
39//!   `Instant::now()` internally and applies as many ticks as the elapsed time
40//!   buys (capped at `MAX_CATCHUP_TICKS=4` so a process resumed from suspend
41//!   doesn't render hundreds of frames at once). This is what `terminal.draw(…)`
42//!   does naturally and what the bundled binary uses.
43//! - **Manual via [`MatrixRainState::tick`].** Each call advances exactly one
44//!   frame regardless of wall-clock time. Useful for deterministic snapshot
45//!   tests and external tick-loop apps.
46//!
47//! Mixing both modes in the same session produces visible drift; the snapshot
48//! suite suppresses wall-clock advance by setting `fps=1` together with a tiny
49//! `speed` (e.g. `0.001`) so the elapsed-time conversion floors to zero ticks
50//! per render. See [`MatrixRainState::set_color_count`] if you also need to
51//! lock the rendering tier for reproducibility.
52//!
53//! # Backends
54//!
55//! The library is backend-agnostic — `MatrixRain` renders into a ratatui
56//! [`Buffer`](ratatui::buffer::Buffer), so any ratatui backend works. Pick
57//! one via a feature flag:
58//!
59//! - `crossterm` (default; enabled by the `binary` feature)
60//! - `termion`
61//! - `termwiz`
62//!
63//! Each feature simply forwards to ratatui's same-named feature, so a single
64//! line in `Cargo.toml` covers both crates:
65//!
66//! ```toml
67//! matrix-rain = { version = "0.1", default-features = false, features = ["termion"] }
68//! ```
69//!
70//! The default `binary` feature pulls in `crossterm` (plus `clap`, `anyhow`,
71//! and `signal-hook`) for the standalone `matrix` binary. Library-only users
72//! who don't need the binary should opt out with `default-features = false`
73//! and pick exactly one backend feature.
74//!
75//! # Color tiers
76//!
77//! Color depth is detected once per state on the first render via an env-var
78//! sniff: `COLORTERM=truecolor|24bit` (de-facto standard for advertising
79//! 24-bit support) wins; otherwise `TERM` is checked for `*256color*`.
80//! The result is cached on the state and drives one of three rendering paths:
81//!
82//! - **Truecolor**: linear RGB interpolation between the 5 stops in
83//!   [`ColorRamp`]. Smoothest gradient.
84//! - **256-color**: nearest-of-5-stops; the terminal handles any further
85//!   RGB→256 quantization.
86//! - **16-color**: 3-zone collapse (head, then `bright`/`mid`/`fade` zones)
87//!   with each stop mapped to the nearest of the 16 named [`Color`] variants
88//!   by euclidean RGB distance. Detection failure or any value the widget
89//!   doesn't recognize also falls back to this path rather than panicking.
90//!
91//! Force a specific tier with [`MatrixRainState::set_color_count`]: pass `16`
92//! for accessibility, `256` for the quantized middle tier, or `u16::MAX` for
93//! the smooth-interpolation path.
94//!
95//! [`Color`]: ratatui::style::Color
96//!
97//! # Caveats
98//!
99//! - **Full-width and combining characters in [`CharSet::Custom`] are not
100//!   detected.** Each glyph must occupy exactly one terminal cell or the
101//!   column layout misaligns. CJK ideographs, emoji with variation selectors,
102//!   and zero-width combiners are all single `char`s in Rust but multi-cell
103//!   in the terminal. Display width cannot reliably be detected across
104//!   terminals; verifying single-cell-ness is the caller's responsibility.
105//! - **Mixing [`MatrixRainState::tick`] with wall-clock rendering** produces
106//!   visible drift over time. Tick driving is exact (each call advances
107//!   exactly one frame); wall-clock driving advances based on elapsed
108//!   [`Instant`](std::time::Instant)s. Pick one mode per session.
109//! - **16-color fallback is a 3-zone collapse**, not the original 5-stop
110//!   gradient. If your theme has stops that map to the same named color
111//!   (common with monochrome themes on 16-color), zones will visually merge.
112//!   Use [`MatrixRainState::set_color_count`] to force a higher tier if your
113//!   terminal actually supports it, or supply a [`Theme::Custom`] ramp whose
114//!   stops are already in the named-color palette.
115//! - **Non-TTY refusal (binary only).** The standalone `matrix` binary exits
116//!   with code 2 when stdout isn't a terminal so it doesn't garble logs when
117//!   accidentally run under a pipe, in a CI runner, or as a systemd service.
118//!   `--help` and `--version` still work in non-TTY contexts (they run before
119//!   the check).
120
121#![warn(missing_docs)]
122
123mod charset;
124mod config;
125mod error;
126mod state;
127mod stream;
128mod theme;
129mod widget;
130
131pub use charset::CharSet;
132pub use config::{MatrixConfig, MatrixConfigBuilder, MAX_TRAIL_LIMIT};
133pub use error::MatrixError;
134pub use state::MatrixRainState;
135pub use theme::{ColorRamp, Theme};
136pub use widget::MatrixRain;