Skip to main content

intuituive/
lib.rs

1//! intuiTUIve: a reactive, retained terminal UI framework built on rs-rich.
2//!
3//! You describe the screen once, as a tree of nodes, and keep your state in
4//! [signals](signal). When a signal changes, the nodes that read it draw
5//! again, and only the cells that changed are sent to the terminal. There
6//! is no draw loop to write, no layout pass to call, and no "what changed"
7//! to track.
8//!
9//! ```no_run
10//! use intuituive::prelude::*;
11//!
12//! fn main() -> std::io::Result<()> {
13//!     App::new(|| {
14//!         let count = signal(0);
15//!         column([
16//!             text!("[b]Count:[/] {count}").panel("Counter"),
17//!             label("[dim]+ adds one · q quits"),
18//!         ])
19//!         .on_key("+", move |_| count.update(|c| *c += 1))
20//!         .on_key("q", |cx| cx.quit())
21//!     })
22//!     .run()
23//! }
24//! ```
25//!
26//! - **Nodes** ([`text`](fn@text), [`label`], [`renderable`], [`column`](fn@column), [`row`],
27//!   [`grid`], [`each`], [`switch`], [`Node::panel`]) are built once and
28//!   kept. Any rich renderable is a node: a `Table`, `Markdown`, `Syntax`, a
29//!   chart.
30//! - **Layout** sizes children by [`Size`] (fixed, percentage, flexible or
31//!   their content), within minimums and maximums, with gaps and padding.
32//! - **State** is [`signal`]s and [`memo`]s. Reading one while drawing
33//!   subscribes the node; writing one redraws exactly its readers.
34//! - **Input**: [`Node::on_key`] bindings bubble from the focused node up;
35//!   Tab moves the focus; [`Node::on_click`] gets clicks, routed by the
36//!   layout the last frame kept.
37//! - **Screens**: [`Ctx::push`], [`Ctx::modal`] and [`Ctx::pop`] keep a
38//!   stack of screens, each with its own state and focus.
39//! - **Background work**: [`spawn`], [`spawn_future`] and [`resource`] run
40//!   slow work off the app's thread and write signals with the result; a
41//!   [`Proxy`] lets any thread do the same.
42//! - **Themes**: [`Theme`] styles the framework's parts and names styles
43//!   for markup; [`Ctx::set_theme`] switches at run time.
44//! - **Inline**: [`App::inline`] runs in a few rows below the prompt
45//!   instead of the alternate screen.
46//! - **Responsive**: [`Node::hide_below`] and a stylesheet's `@media`
47//!   rules follow the terminal's size; [`viewport`] reads it.
48//! - **The outside world**, behind off-by-default features (plugins: the
49//!   core stays generic): `audio::pcm` reads raw PCM a buffer at a time,
50//!   `audio::play` plays it at its rate and `audio::Capture::simulated`
51//!   stands in for a sound card in tests (`pcm`), `audio::input` captures
52//!   a sound card into a signal
53//!   (`audio`, with PulseAudio sources under `pulse`),
54//!   `audio::wav` reads and records WAV files (`wav`), `watch_path` wakes
55//!   the app when files change (`watch`), `image` draws an image file
56//!   in half blocks (`art`), `meter`, `sparkline`, `spectrogram` and
57//!   `bar_spectrum` show levels and spectra (`viz`), and `files` has a
58//!   file manager's components: Miller columns, file lists, previews,
59//!   background file jobs and a streaming finder (`files`).
60//! - **Generic building blocks**: [`miller_of`] (Miller columns over any
61//!   [`MillerSource`] tree), [`jobs`](fn@jobs) and [`jobs_panel`]
62//!   (cancellable work with progress), [`fuzzy_list`] and [`finder_of`]
63//!   (a fuzzy list over any stream of items), [`preview_of`] (a value
64//!   loaded off the app's thread for the current key), [`multi_select`]
65//!   (a selection by key with a visual range), [`list_search`] (find next
66//!   and previous over a list), [`tab_set`](fn@tab_set) (tabs' states with one
67//!   active), [`facts`] (a description list), [`digits`] (big digits
68//!   in any build), and [`state`]'s files and [`FrecencyStore`] (an
69//!   app's state between sessions). The core is generic only; the domain
70//!   plugins above build on these.
71//! - **Sprites and pixels**: [`sprite::stage`] draws [`sprite::Sprite`]s
72//!   (cell art with transparent cells, animated on the app's clock) over a
73//!   backdrop, with clicks, hovers and collisions by their cells;
74//!   [`pixels::pixels`] is an RGBA framebuffer drawn in half blocks,
75//!   quadrants or Braille, with blits, rectangles and lines. Plugins cut
76//!   an image into sprites (`sprite_sheet`, behind `art`) and make
77//!   rs-rich-micro's micro assets sprites (`micro_sprite`, behind `micro`).
78//! - **Tests**: [`App::render_with`] and [`App::run_on`] with
79//!   `rich-interact`'s headless driver run an app without a terminal;
80//!   [`App::driver`] steps one, and [`assert_screen!`] compares its screen
81//!   with a snapshot (see [`testing`]).
82//!
83//! The design, and how it compares with ratatui, is in
84//! `docs/design/intuituive.md`.
85
86// A builder method that returns `Self` is `#[must_use]`: dropping its
87// result drops the value (CONTRIBUTING.md, "API conventions").
88#![warn(clippy::return_self_not_must_use)]
89
90pub mod a11y;
91mod actions;
92pub mod app;
93#[cfg(feature = "pcm")]
94pub mod audio;
95#[cfg(feature = "art")]
96pub mod banner;
97mod bars;
98#[cfg(feature = "art")]
99mod big_text;
100mod bindings;
101pub mod border;
102mod builtin;
103mod calendar;
104mod clock;
105mod collapsible;
106pub mod command_line;
107mod commands;
108pub mod complete;
109mod controls;
110mod datetime;
111mod dialog;
112mod diff_view;
113mod digits;
114mod editor_theme;
115mod effect;
116mod facts;
117mod feed;
118#[cfg(feature = "files")]
119pub mod files;
120pub mod finder;
121mod float;
122pub mod focus;
123pub mod fx;
124mod help;
125mod hex;
126#[cfg(feature = "art")]
127mod image;
128mod input;
129pub mod inspect;
130pub mod jobs;
131pub mod layout;
132mod list;
133mod lockstep;
134pub mod log;
135#[cfg(feature = "syntax")]
136mod markdown;
137#[cfg(feature = "syntax")]
138mod markdown_viewer;
139pub mod menu;
140#[cfg(feature = "micro")]
141pub mod micro_sprite;
142pub mod miller;
143pub mod node;
144mod node_list;
145mod notify;
146mod palette;
147mod parity;
148pub mod pixels;
149mod place;
150pub mod preview;
151pub mod query;
152pub mod reactive;
153mod reorder;
154mod rule;
155pub mod screen;
156mod scroll_virtual;
157mod scrollbar;
158pub mod select;
159mod select_field;
160mod sequence;
161mod sheet;
162mod signals;
163mod solver;
164pub mod source;
165mod spinner;
166mod split;
167pub mod sprite;
168#[cfg(feature = "art")]
169pub mod sprite_sheet;
170pub mod state;
171pub mod tab_set;
172mod table_scroll;
173pub mod task;
174pub mod testing;
175mod text_area;
176pub mod text_edit;
177pub mod timedemo;
178pub mod transport;
179mod tree;
180mod tree_table;
181mod treemap;
182#[cfg(feature = "vim")]
183pub mod vim;
184#[cfg(feature = "viz")]
185mod viz;
186#[cfg(feature = "watch")]
187mod watch;
188pub mod widget;
189pub mod widgets;
190
191/// rs-rich, for renderables, styles and markup helpers, so an app needs
192/// only this crate as a dependency.
193pub use rich;
194/// rs-rich-interact, for its components (`interact::Input`,
195/// `interact::Select`, …) and the headless test driver.
196pub use rich_interact as interact;
197/// Dates as people type them (`tomorrow`, `eow`, `3d`): rs-rich-interact's.
198pub use rich_interact::dates;
199
200pub use actions::{Action, action_overlay};
201pub use app::DEFAULT_MODE;
202pub use app::{
203    Anchor, App, Ctx, Driver, Easing, FrameStats, Handoff, Holds, ModalOptions, Placement,
204    StatsHandle, Theme, Timer, after, every, fixed_step, held_keys, plain_key, rate,
205};
206#[cfg(feature = "art")]
207pub use big_text::big_text;
208pub use bindings::{ModePage, Scaled, Stepped};
209pub use border::{Border, BorderSet, Edge, Shadow, Sides};
210pub use collapsible::{Status, collapsible, collapsible_with};
211pub use command_line::{CommandLine, CommandLineOptions, command_line, command_line_with};
212pub use commands::{Hit, HitSink, Provider, SystemCommands, Themes};
213pub use complete::{
214    CompleteOptions, Completion, InputHistory, complete_words, history_file, history_in,
215    history_session,
216};
217pub use controls::{
218    ButtonVariant, CheckState, button, button_of, checkbox, checkbox_of, checkbox_tri, radio,
219    radio_keyed, slider, switch_of, switch_toggle,
220};
221pub use dialog::{ConfirmOptions, PickOptions, PromptOptions};
222pub use digits::digits;
223pub use editor_theme::EditorTheme;
224pub use effect::{effect, fill};
225pub use facts::{facts, facts_of};
226pub use feed::{
227    Feed, FeedEnd, FeedState, FeedTeller, Pace, Play, latest_feed, latest_iter, latest_with_state,
228};
229pub use finder::{FuzzyOptions, FuzzySource, finder_of, fuzzy_list, fuzzy_list_with};
230pub use focus::Direction;
231pub use fx::Fx;
232pub use help::{HelpOptions, KeyFormat, KeyHintsOptions};
233#[cfg(feature = "art")]
234pub use image::{
235    ImageAnchor, ImageFit, ImageMode, ImageView, Picture, Pixels, image, image_bytes,
236    image_bytes_of, image_graphics, image_of, image_with, picture_bytes, picture_of, tiles_of,
237};
238pub use input::{input, number_input};
239pub use jobs::{
240    Job, JobOutcome, JobProgress, JobState, Jobs, JobsPanelOptions, ProcessOutcome, ProcessOutput,
241    jobs, jobs_panel, jobs_panel_with,
242};
243pub use layout::{Align, AlignContent, Justify, Size, StackLayout, TextAlign};
244pub use list::list_optional;
245pub use lockstep::{Lockstep, Steps, stepped_worker, stepped_worker_with};
246pub use log::Log;
247#[cfg(feature = "syntax")]
248pub use markdown::{MarkdownStream, markdown};
249#[cfg(feature = "syntax")]
250pub use markdown_viewer::{MarkdownHeading, MarkdownViewer, markdown_viewer};
251pub use miller::{MillerSource, miller_in, miller_of};
252pub use node::{
253    Node, column, component, each, fit_text, grid, keyed, label, layers, leaf, list, renderable,
254    repeating, row, scroll, scroll_both, scroll_both_with, scroll_with, scroll_x, switch, text,
255};
256pub use node::{described, drawn, drawn_described};
257pub use node_list::{list_of, list_of_virtual};
258pub use notify::{Corner, Level};
259pub use palette::Palette;
260pub use parity::{
261    PlaceholderVariant, header, key_panel, link, masked_input, placeholder, progress_bar,
262};
263pub use place::{Offset, Origin, RectHandle};
264pub use preview::{PreviewOfOptions, preview_of, preview_of_with};
265pub use reactive::{Memo, Proxy, Signal, memo, on_write, on_write_ref, signal, viewport, watch};
266/// What a text does with what does not fit a line: rich's.
267pub use rich::Overflow;
268pub use rule::rule;
269pub use screen::{PaintStats, PaintStrategy};
270pub use scroll_virtual::{scroll_virtual, scroll_virtual_with};
271pub use select::{ListSearch, Mark, MultiSelect, list_search, multi_select};
272pub use select_field::{SelectOption, select_field, select_field_keyed};
273pub use sheet::{SheetError, Stylesheet};
274pub use signals::{Count, History, falling, history, recent, recent_of, recent_timed, smoothed};
275pub use solver::Solver;
276pub use spinner::spinner;
277pub use state::{
278    FrecencyStore, frecency_in, frecency_session, frecency_store, read_state, state_dir,
279    state_file, update_file, update_state, write_atomic, write_state,
280};
281pub use tab_set::{Scoped, TabSet, tab_set, tabs_for, tabs_for_indexed};
282pub use task::{
283    Latest, Load, Notifier, Resource, Stream, Task, latest, latest_every, resource, resource_for,
284    spawn, spawn_future, spawn_stream, spawn_then,
285};
286pub use text_area::Caret;
287pub use text_edit::{TextSearch, search_bar};
288pub use transport::{Transport, transport};
289pub use treemap::{TreemapOptions, treemap, treemap_with};
290#[cfg(feature = "viz")]
291pub use viz::{
292    bar_spectrum, bar_spectrum_with, meter, meter_with, persistence, persistence_with, sparkline,
293    sparkline_with, spectrogram, spectrogram_with, tuner,
294};
295#[cfg(feature = "watch")]
296pub use watch::{
297    ChangeKind, PathChange, PathEvent, PathWatch, WatchOptions, watch_path, watch_path_with,
298};
299pub use widget::{Widget, widget};
300pub use widgets::text_area;
301
302/// Everything an app usually needs.
303pub mod prelude {
304    // `text` is both the function and the `text!` macro.
305    pub use crate::{
306        Align, App, Border, Ctx, Edge, Justify, Load, Log, Memo, Node, Overflow, Proxy, Resource,
307        Shadow, Sides, Signal, Size, Task, TextAlign, Theme, Timer, Widget, after, column,
308        component, each, effect, every, fill, fixed_step, grid, input, keyed, label, latest,
309        latest_every, layers, leaf, list, list_optional, memo, number_input, on_write,
310        on_write_ref, rate, renderable, repeating, resource, resource_for, row, scroll,
311        scroll_both, scroll_virtual, scroll_with, scroll_x, signal, spawn, switch, text, text_area,
312        viewport, watch, widget,
313    };
314    pub use crate::{described, drawn, drawn_described};
315}
316
317/// Console markup with `format!` arguments, read again whenever a signal it
318/// names changes: `text!("{count} items")`. Signals format as their value.
319#[macro_export]
320macro_rules! text {
321    ($($arg:tt)*) => {
322        $crate::node::text(move || format!($($arg)*))
323    };
324}
325
326impl<T: std::fmt::Display + 'static> std::fmt::Display for Signal<T> {
327    /// The value, subscribing the node that is drawing.
328    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329        self.with(|value| value.fmt(f))
330    }
331}
332
333impl<T: std::fmt::Display + 'static> std::fmt::Display for Memo<T> {
334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335        self.with(|value| value.fmt(f))
336    }
337}