← All 41 libraries

SugarVeil

SugarVeil

Terminal overlay compositor

port of rmhubbert/bubbletea-overlay overlaycompositingui

SugarVeil code coverage

Composite overlay views (modals, tooltips, toasts) on top of a base TUI frame. Render your models first, then let the compositor handle positioning, backdrop dimming, z-ordering, animated transitions and click-outside hit-testing.

Install

composer require sugarcraft/sugar-veil

Quickstart

use SugarCraft\Veil\{Position, Veil, VeilStack};

$veil = Veil::new()->withBackdrop(40);   // dim the background to 40%

// Composite a foreground string centered over a background string
echo $veil->composite($modalView, $baseView, Position::CENTER, Position::CENTER);

// Layer several overlays by z-index
$stack = VeilStack::new()
    ->add(Veil::new()->withContent($modalView)->withZIndex(1))
    ->add(Veil::new()->withContent($tooltip)->withZIndex(2)
        ->withPosition(Position::TOP, Position::RIGHT));
echo $stack->compositeAll($baseView);

What's in the box

PositioningNine anchors — Top, Right, Bottom, Left, Center and the four corners — plus X/Y offsets in cells (columns/rows).
Backdrop dimmingTruecolor opacity blend (0–100) applied to plain background lines during assembly.
Animated transitionswithAnimation() + animate(…, $progress): SLIDE enters from the anchored edge, SCALE reveals lines from the center outward, and FADE brightens the overlay in from black through a truecolor gray pen (terminals cannot alpha-blend), returning the original bytes at full opacity.
VeilStackOrdered collection of veils rendered by ascending z-index; maxZIndex() / minZIndex() return null for an empty stack, since 0 is a real z-index.
Click-outside dismisswithClickOutsideDismiss() + scan() / isClickOutside() via candy-mouse zones. The scanned zones travel with the veil across later with*() calls, so a click is judged against the frame that was on screen.
Buffer diffingcomposite() diffs against the previous frame and emits only changed cells. An empty background returns the overlay itself as the frame (upstream's bg == "" early return), and colours are clamped when the diff pen is built: truecolor SGR components to 0–255, and 256-colour indices to 0–255, so 38;5;300 cannot widen a cell and a negative index no longer falls back to white.
Border chrome & auto-sizewithBorder() wraps veil content in a terminal border; withAutoSize() sizes the veil from its content.

Source & demos

Try the quickstart →

API

ClassMethodDescription
Veilnew()Create a new veil
Veilcomposite(foreground, background, Position $vertical, Position $horizontal, int $xOffset = 0, int $yOffset = 0)Composite one string over another; offsets are cells
Veilanimate(foreground, background, $vertical, $horizontal, float $progress, $xOffset = 0, $yOffset = 0)Composite with the configured animation applied at progress 0..1
VeilwithBackdrop(int) / withAnimation(?AnimationKind) / withZIndex(int) / withPosition(?Position, ?Position, x, y) / withContent(string) / withBorder(?Border) / withAutoSize() / withClickOutsideDismiss()Immutable configuration; scanned zones survive every with*()
Veilscan(rendered) / hit(col, row) / isClickOutside(MouseMsg)Zone hit-testing; isClickOutside() throws when dismissal is on but scan() has not run
VeilStacknew() / add(Veil) / removeWhere(\Closure) / filter(\Closure) / clear()Immutable stack of veils
VeilStackcomposite(background, $vertical, $horizontal, $xOffset = 0, $yOffset = 0) / compositeAll(background)Render every veil by ascending z-index, at a shared position or each at its own
VeilStackmaxZIndex(): ?int / minZIndex(): ?intHighest / lowest z-index, null for an empty stack
VeilStacksorted() / all() / isEmpty() / count()Queries (count() via Countable)

Demos.

VHS-recorded GIFs of every example shipped with the library. Regenerated automatically on every push that touches the source.

Modal overlay

Modal overlay

Modal pinned over a background frame.
Multiple overlays

Multiple overlays

Stacked overlays composited in z-order.