1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! Classic "Matrix digital rain" effect for terminals, packaged as both a
//! [ratatui](https://ratatui.rs/) [`StatefulWidget`](ratatui::widgets::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`](ratatui::widgets::StatefulWidget::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`](ratatui::buffer::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:
//!
//! ```toml
//! 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:
//!
//! ```toml
//! 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.
//!
//! [`Color`]: ratatui::style::Color
//!
//! # 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 `char`s 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
//! [`Instant`](std::time::Instant)s. 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).
extern crate alloc;
pub use CharSet;
pub use ;
pub use MatrixError;
pub use MatrixRainState;
pub use ;
pub use MatrixRain;