# scrin
Made by KnottDynamics.
`scrin` is a Rust terminal UI toolkit for building polished command-line
interfaces without Ratatui. It provides Scrin-native buffers, colors, layout,
styled text, widgets, panes, overlays, command palettes, status bars, input
routing, terminal lifecycle helpers, and animation utilities.
Scrin also ships with Aisling-powered effects and loaders. The `aisling` crate
is baked in as a first-class dependency, and Scrin adapts Aisling frames into
Scrin buffers so animated text effects and progress loaders can live beside
normal widgets.
## Install
```toml
[dependencies]
scrin = "0.1.76"
```
## Why Scrin
- Scrin-native terminal rendering with no Ratatui dependency.
- Composable widgets for blocks, paragraphs, lists, tables, tabs, charts,
gauges, markdown output, forms, popups, toggles, and more.
- App structure primitives for panes, overlays, modals, toasts, status bars,
command palettes, event routing, scrolling, and text expansion.
- Rich terminal text with spans, lines, wrapping, scrolling, alignment, colors,
and modifiers.
- Aisling integration for cinematic effects and loaders rendered directly into
Scrin buffers.
- Demos and scripts that exercise widgets, overlays, Aisling effects, loaders,
and a full Scrin shell showcase.
## Draw A Widget
```rust
use scrin::core::buffer::Buffer;
use scrin::core::color::Color;
use scrin::core::rect::Rect;
use scrin::style::Style;
use scrin::widgets::block::Block;
use scrin::widgets::paragraph::Paragraph;
use scrin::widgets::Widget;
let mut buffer = Buffer::new(48, 8);
buffer.fill(Rect::new(0, 0, 48, 8), ' ', Color::WHITE, None);
let block = Block::bordered()
.title("scrin")
.border_style(Style::new().fg(Color::CYAN));
block.render(&mut buffer, Rect::new(0, 0, 48, 8));
let text = Paragraph::new("Scrin renders terminal UI from its own buffer model.");
text.render(&mut buffer, Rect::new(2, 2, 44, 4));
```
## Use The Terminal API
`Terminal::draw` builds a frame, renders into Scrin's back buffer, and presents a
diff to the terminal. The diff presenter repositions for separated dirty runs on
the same row and handles wide-glyph continuation cells.
```rust,no_run
use scrin::terminal::{Terminal, TerminalOptions};
use scrin::style::Style;
use scrin::widgets::block::Block;
let mut terminal = Terminal::init_with(TerminalOptions::default())?;
let block = Block::bordered()
.title("app")
.border_style(Style::new().fg(scrin::Color::CYAN));
frame.render_widget(block, area);
})?;
terminal.restore()?;
# Ok::<(), std::io::Error>(())
```
For dense animation or integrations that prefer repainting every cell,
`Terminal::draw_full` and `Terminal::present_full` are available.
## Aisling Effects
Scrin exposes Aisling through `scrin::effects`. You can select an Aisling
`EffectKind`, tune the effect with Scrin colors and sizing, and render the
current Aisling frame into any Scrin `Buffer` area.
```rust
use scrin::core::buffer::Buffer;
use scrin::core::color::Color;
use scrin::core::rect::Rect;
use scrin::effects::{EffectKind, EffectPlayer};
let mut buffer = Buffer::new(64, 8);
let mut effect = EffectPlayer::new(EffectKind::Matrix, "Scrin + Aisling")
.with_size(64, 8)
.with_duration(32)
.with_seed(7)
.with_accent(Color::rgb(88, 166, 255));
effect.render_to_buffer(&mut buffer, Rect::new(0, 0, 64, 8));
effect.advance();
```
Useful effect APIs:
- `EffectPlayer::new(kind, text)` creates an Aisling-backed text effect.
- `EffectPlayer::with_config(kind, text, config)` accepts an Aisling
`EffectConfig` re-exported from `scrin::effects`.
- `with_size`, `with_duration`, `with_seed`, `with_accent`, and
`with_gradient_colors` tune how the effect is generated and mapped.
- `render_to_buffer` and `render_frame_to_buffer` copy Aisling frame cells into a
Scrin buffer region.
- `get_ansi_string` returns Aisling's raw ANSI frame when you want standalone
effect output.
- `EffectPlayer::all_kinds()` returns every `EffectKind` available in the baked
Aisling version.
## Aisling Loaders
Loaders are also Aisling-backed. Scrin maps loader frames into buffers and keeps
progress helpers on `LoaderPlayer`.
```rust
use scrin::core::buffer::Buffer;
use scrin::core::color::Color;
use scrin::core::rect::Rect;
use scrin::effects::{LoaderKind, LoaderPlayer};
let mut buffer = Buffer::new(52, 5);
let loader = LoaderPlayer::new(LoaderKind::Bar)
.with_size(52, 5)
.with_label("indexing".to_string())
.with_unit("files")
.with_fraction(true)
.with_accent(Color::rgb(255, 128, 64));
let progress = LoaderPlayer::progress_from_counts(42, 100);
loader.render(8, progress, &mut buffer, Rect::new(0, 0, 52, 5));
```
Useful loader APIs:
- `LoaderPlayer::new(kind)` creates an Aisling-backed loader.
- `LoaderPlayer::with_config(kind, config)` accepts an Aisling `LoaderConfig`
re-exported from `scrin::effects`.
- `with_size`, `with_label`, `with_unit`, `with_fraction`, `with_accent`, and
`with_gradient_colors` tune the loader presentation.
- `render(tick, progress, buffer, area)` draws the current loader frame into a
Scrin buffer.
- `get_ansi_string(tick, progress)` returns the raw ANSI loader frame.
- `progress_from_counts` and `progress_from_fraction` create Aisling progress
values without importing Aisling directly.
- `LoaderPlayer::all_kinds()` returns every `LoaderKind` available in the baked
Aisling version.
## Modules
- `scrin::core`: `Buffer`, `Cell`, `Color`, `Gradient`, and `Rect`.
- `scrin::layout`: horizontal and vertical layout constraints.
- `scrin::widgets`: blocks, paragraphs, rich text, lists, tables, forms, charts,
gauges, markdown output, popups, toggles, scrollbars, and clearing helpers.
- `scrin::terminal`: raw-mode setup, frame drawing, diff/full presenters,
cursor control, mouse capture, bracketed paste, and restore-once lifecycle.
- `scrin::effects`: Aisling-backed `EffectPlayer`, `LoaderPlayer`, effect kinds,
loader kinds, progress, and config types.
- `scrin::panes`, `scrin::overlays`, `scrin::command_palette`,
`scrin::status_bar`, and `scrin::input`: higher-level application structure.
- `scrin::sanitize`: display-width-safe terminal string helpers.
## Scrin-Only Porting Helpers
Scrin includes small ergonomic APIs for downstream crates that previously used a
Ratatui-style structure:
- `Block::bordered().title(...).border_style(...)` for concise block builders.
- `Block::inner(area)` defaults to one border cell per side; extra padding is
opt-in with `with_inner_margin(...)`.
- `Frame::render_widget(widget, area)` for frame-centric rendering.
- `Buffer::cell_mut(x, y)` plus `Cell::set_symbol`, `set_fg`, `set_bg`, and
`set_style` for post-render effects.
- `Rect::is_empty()` for direct zero-area checks.
## Demos
Run demos with the scripts in `scripts/`:
```bash
scripts/demo_widgets
scripts/demo_overlays
scripts/demo_scrin_shell
scripts/demo_toggle_exotic
scripts/demo_aisling
scripts/demo_loaders
scripts/demo_aisling_story
```
The Aisling story demo is the broadest visual smoke test: it walks the available
effect inventory and rotates through loader systems using the Aisling bridge.
## Publishing Notes
The crate is intended to publish cleanly to crates.io and build on docs.rs. The
package excludes generated `target/**` artifacts and includes source, examples,
tests, scripts, and this README.
Recommended pre-publish checks:
```bash
cargo fmt
cargo check
cargo check --examples
cargo test
cargo doc --no-deps
cargo publish --dry-run --allow-dirty
```