aisling 0.2.0

Embeddable terminal text effects and loaders for Rust TUI applications.
Documentation
  • Coverage
  • 4.48%
    13 out of 290 items documented1 out of 98 items with examples
  • Size
  • Source code size: 301.64 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 4.27 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 4s Average build duration of successful builds.
  • all releases: 4s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • Tknott95

Aisling

Embeddable terminal text effects and loader animations for Rust TUI applications.

Aisling renders animated text effects, spinners, and progress loaders as plain Rust data. It does not own your terminal, start an event loop, sleep, or print unless you run the included examples. Your app drives the iterator or asks for the current loader frame and maps each styled Frame into Ratatui, Crossterm, ANSI output, tests, logs, or any custom renderer.

The effect catalog is ported from the Python terminaltexteffects project shape and includes every built-in reference effect exposed by the current snapshot.

Install

After publishing:

cargo add aisling

Or in Cargo.toml:

[dependencies]
aisling = "0.1"

For local development from this repository:

[dependencies]
aisling = { path = "../aisling" }

Quick Start

use aisling::{Effect, EffectConfig, EffectKind};

let config = EffectConfig::default()
    .with_duration(80)
    .with_seed(42);

let effect = Effect::with_config(EffectKind::Matrix, "Aisling", config);

for frame in effect.iter() {
    // Map frame.cells() into your TUI buffer.
    let plain = frame.to_plain_string();
    let ansi = frame.to_ansi_string();
}

Progress loaders are also frame-based:

use aisling::{Loader, LoaderConfig, LoaderKind, LoaderProgress};

let loader = Loader::with_config(
    LoaderKind::Tqdm,
    LoaderConfig::default()
        .with_width(64)
        .with_label("download")
        .with_unit("B")
        .with_fraction(true),
);

let downloaded = 42;
let total = 100;
let frame = loader.frame(7, LoaderProgress::from_counts(downloaded, total));
let line = loader.line(7, LoaderProgress::from_counts(downloaded, total));

TUI Integration

Effect::iter() yields Frame values. A Frame is a fixed-size grid with row-major Cell data.

Important types:

Type Purpose
Effect Owns the effect kind, input text, and config.
EffectKind Selects one of the built-in effects.
EffectConfig Controls duration, seed, gradient, canvas size, color handling, and more.
Loader Owns a loader kind and config; render with the current tick and progress.
LoaderKind Selects a built-in spinner/progress/download animation.
LoaderConfig Controls loader width, height, label, units, colors, and duration.
LoaderProgress Carries a progress fraction or current/total counters.
Frame Rendered 2D grid for a single animation tick.
Cell Character plus foreground/background colors and simple styles.
Color, Gradient, Easing Color and timing helpers.

Minimal renderer loop shape:

use aisling::{Effect, EffectKind};

let effect = Effect::new(EffectKind::Wipe, "ship it");

for frame in effect.iter() {
    for y in 0..frame.height() {
        for x in 0..frame.width() {
            let cell = frame.cell(x, y).unwrap();
            // Write cell.ch and cell.colors into your TUI buffer here.
        }
    }
}

Loaders

Loaders are for status lines, downloads, installs, background tasks, and tqdm-style progress indicators. Unlike text effects, real app progress usually comes from your own IO loop, so call Loader::frame(tick, progress) whenever your UI redraws.

use aisling::{Loader, LoaderKind, LoaderProgress};

let loader = Loader::new(LoaderKind::Download);
let tick = 12;
let frame = loader.frame(tick, LoaderProgress::from_counts(512, 1024));

Built-in loaders:

bar, tqdm, blocks, meter, battery, gauge, dots, pulse, line, bounce,
scanner, snake, pong, marquee, steps, download, upload, equalizer,
wave, matrix, spark, orbit, brackets, crawl, braided, burn, decrypt,
rain, thunderstorm, laseretch, vhstape, blackhole, fireworks, pour,
synthgrid, beams, smoke, binarypath, bouncyballs, bubbles, colorshift,
crumble, errorcorrect, expand, highlight, middleout, orbittingvolley,
overflow, print, randomsequence, rings, scattered, slice, slide,
spotlights, spray, swarm, sweep, unstable, waves, wipe

The effect-inspired loaders use the same Frame/Cell model but are designed for long-running tasks: burn smolders across the progress line, decrypt cascades cipher characters downward into the status, laseretch scans a hot beam, rings orbits around the transfer cursor, swarm throws particles around the active edge, and thunderstorm layers rain with lightning.

For quick plain-line output, use Loader::line(tick, progress). For TUI rendering, use Loader::frame(tick, progress) and map its cells into your buffer.

Effects

All built-in effects are available through EffectKind and EffectKind::all().

Command Enum
beams EffectKind::Beams
binarypath EffectKind::BinaryPath
blackhole EffectKind::Blackhole
bouncyballs EffectKind::BouncyBalls
bubbles EffectKind::Bubbles
burn EffectKind::Burn
colorshift EffectKind::ColorShift
crumble EffectKind::Crumble
decrypt EffectKind::Decrypt
errorcorrect EffectKind::ErrorCorrect
expand EffectKind::Expand
fireworks EffectKind::Fireworks
highlight EffectKind::Highlight
laseretch EffectKind::LaserEtch
matrix EffectKind::Matrix
middleout EffectKind::MiddleOut
orbittingvolley EffectKind::OrbittingVolley
overflow EffectKind::Overflow
pour EffectKind::Pour
print EffectKind::Print
rain EffectKind::Rain
randomsequence EffectKind::RandomSequence
rings EffectKind::Rings
scattered EffectKind::Scattered
slice EffectKind::Slice
slide EffectKind::Slide
smoke EffectKind::Smoke
spotlights EffectKind::Spotlights
spray EffectKind::Spray
swarm EffectKind::Swarm
sweep EffectKind::Sweep
synthgrid EffectKind::SynthGrid
thunderstorm EffectKind::Thunderstorm
unstable EffectKind::Unstable
vhstape EffectKind::VhsTape
waves EffectKind::Waves
wipe EffectKind::Wipe

String parsing is supported:

use std::str::FromStr;
use aisling::EffectKind;

let kind = EffectKind::from_str("laseretch").unwrap();
assert_eq!(kind, EffectKind::LaserEtch);

Configuration

use aisling::{Color, EffectConfig, Easing, Gradient, GradientDirection};

let config = EffectConfig::default()
    .with_duration(120)
    .with_seed(9001)
    .with_canvas_size(48, 8)
    .with_gradient(
        Gradient::new(
            vec![Color::rgb(255, 170, 60), Color::rgb(80, 110, 95)],
            24,
        ),
        GradientDirection::Horizontal,
    );

Notable EffectConfig fields:

Field Meaning
duration Number of animation frames.
hold_final_frames Extra frames that hold the final text.
seed Deterministic random seed for repeatable playback.
gradient Final/effect color gradient.
gradient_direction Horizontal, vertical, diagonal, or radial mapping.
easing Global easing used by many effect renderers.
existing_color_handling Preserve or ignore parsed ANSI input colors.
no_color Disable color output.
canvas_width, canvas_height Optional fixed frame dimensions.
tab_width Spaces per tab when parsing input text.

ANSI Input

Aisling parses common ANSI SGR sequences in input text, including foreground/background 3-bit, bright 3-bit, 8-bit xterm, and 24-bit RGB colors. Parsed styling is kept in each final Cell when the color-handling mode allows it.

use aisling::{Effect, EffectKind};

let effect = Effect::new(EffectKind::Print, "\x1b[38;2;255;80;80mred text\x1b[0m");
let final_frame = effect.final_frame();
assert_eq!(final_frame.to_plain_string(), "red text");

Examples

Run a single effect in the terminal:

cargo run --example demo -- wipe "Hello from Aisling"
cargo run --example demo -- matrix "opencode style"
cargo run --example loaders -- tqdm --label download
cargo run --example loaders -- all --seconds 20 --fps 6
cargo run --example loader_lab -- --seconds 120
cargo run --example story -- --seconds 90 --fps 12

Use the helper script while developing locally:

./run.sh wipe "Hello from Aisling"
./run.sh matrix "opencode style"
./run.sh list
./run.sh all "Aisling"
./run.sh loader tqdm --label download
./run.sh loaders --seconds 20 --fps 6
./run.sh list-loaders
./loaders.sh
./loaders.sh --seconds 120 --focus-seconds 8
./story.sh
./story.sh --seconds 90 --fps 12

Run the multi-pane all-effects showcase:

./run.sh showcase
./run.sh showcase --rainbow
./run.sh showcase --theme industrial
./run.sh showcase --seconds 60
./run.sh showcase --forever

The showcase defaults to a curated high-contrast palette and a masonry pane layout. Wider/taller effects such as matrix, blackhole, rain, thunderstorm, and vhstape receive larger panes when the terminal has room. Use --rainbow for the original full-spectrum theme or --industrial for a steel/amber palette.

test.sh launches the showcase with a longer embedded text sample:

./test.sh
./test.sh 90
./test.sh --rainbow
./test.sh --industrial
./test.sh --industrial 90
./test.sh -loaders

./run.sh loaders and ./test.sh -loaders show the full loader catalog in a grid so the loader styles can be compared at once. The test.sh -loaders path defaults to slower playback.

loaders.sh runs the full loader command deck. It keeps every loader visible in a catalog strip, gives one loader a large story-style focus pane, keeps six surrounding live mock transfer lanes running at different speeds, and supports arrow-key focus changes in a real terminal.

story.sh runs a full TUI-style Knott Dynamics narrative demo. It uses the embedded text from test.sh, then composes matrix, burn, thunderstorm, laser etch, rings, swarm, fireworks, fake downloads, and exotic loaders into one animated dashboard.

Package Verification

Before publishing:

cargo fmt
cargo test
cargo package --allow-dirty

Publish when ready:

cargo publish

License

MIT