PHPackages                             sugarcraft/sugar-veil - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [CLI &amp; Console](/categories/cli)
4. /
5. sugarcraft/sugar-veil

ActiveLibrary[CLI &amp; Console](/categories/cli)

sugarcraft/sugar-veil
=====================

PHP port of rmhubbert/bubbletea-overlay — modal/overlay compositing for terminal UIs. Composite foreground content over a background at any position (Top/Right/Bottom/Left/Center) with optional pixel offsets.

02421PHP

Since Jul 1Pushed 3w agoCompare

[ Source](https://github.com/sugarcraft/sugar-veil)[ Packagist](https://packagist.org/packages/sugarcraft/sugar-veil)[ RSS](/packages/sugarcraft-sugar-veil/feed)WikiDiscussions master Synced 3w ago

READMEChangelogDependenciesVersions (1)Used By (1)

[![sugar-veil](.assets/icon.png)](.assets/icon.png)

[![CI](https://github.com/detain/sugarcraft/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/detain/sugarcraft/actions/workflows/ci.yml)[![codecov](https://camo.githubusercontent.com/ff6f4038c9ca5b9f5549d5eaeb63a2c6219c26413ae4d2e65ec28cc9a5d90db4/68747470733a2f2f636f6465636f762e696f2f67682f64657461696e2f737567617263726166742f6272616e63682f6d61737465722f67726170682f62616467652e7376673f666c61673d73756761722d7665696c)](https://app.codecov.io/gh/detain/sugarcraft?flags%5B0%5D=sugar-veil)[![Packagist Version](https://camo.githubusercontent.com/502379d729dd1d4810c048a92816a3b34c85179380567aad3e188f051782cd96/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7375676172636f72652f73756761722d7665696c3f6c6162656c3d7061636b6167697374)](https://packagist.org/packages/sugarcore/sugar-veil)[![License](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](LICENSE)[![PHP](https://camo.githubusercontent.com/fd3accad83cd317a9843432403191b4e555c55d57d63e10897f3f1dff5ed169e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d254532253839254135382e332d3838393262662e737667)](https://www.php.net/)

SugarVeil
=========

[](#sugarveil)

PHP port of [rmhubbert/bubbletea-overlay](https://github.com/rmhubbert/bubbletea-overlay) — modal/overlay compositing for terminal UIs. Composite one string (foreground) over another (background) at any position with optional pixel offsets.

Features
--------

[](#features)

- **9 position modes**: Top, Right, Bottom, Left, Center, and the 4 corners (TopRight, BottomRight, BottomLeft, TopLeft)
- **Pixel-precise offsets**: X/Y offsets fine-tune any position
- **Pure rendering**: composites any background + foreground strings
- **Works with any TUI framework**: render your models first, then composite
- **Backdrop dimming**: apply ANSI dim overlay (0–100 opacity) to background
- **Animated transitions**: Slide, Fade, and Scale animations driven by honey-bounce CubicBezier easing
- **Z-index stacking**: control render order across multiple overlays via `withZIndex()`
- **Click-outside dismiss**: detect when a mouse click falls outside a veil's zone via `withClickOutsideDismiss()`
- **Auto-size**: compute veil dimensions from content rather than fixed width/height via `withAutoSize()`
- **Border chrome**: wrap veil content in a terminal border via `withBorder()`
- **VeilStack**: ordered collection of veils sorted by z-index for rendering layered overlays
- **Zone manager integration**: integrate with candy-zone `Manager` for hit testing

Install
-------

[](#install)

```
composer require sugarcraft/sugar-veil
```

Quick Start
-----------

[](#quick-start)

```
use SugarCraft\Veil\Veil;

$veil = Veil::new();

// Background: a 40x10 box
$bg = "┌──────────────────────────────────────┐\n" .
      "│         Main Application             │\n" .
      "│                                      │\n" .
      "│   [content]                          │\n" .
      "└──────────────────────────────────────┘";

// Foreground: a smaller overlay
$fg = "╔════════╗\n║ MODAL  ║\n╚════════╝";

// Composite fg centered over bg
$output = $veil->composite($fg, $bg, Position::CENTER, Position::CENTER);
echo $output;
```

Positioning
-----------

[](#positioning)

```
$veil->composite(
    string  $foreground,
    string  $background,
    Position $vertical,    // TOP | CENTER | BOTTOM
    Position $horizontal,  // LEFT | CENTER | RIGHT
    int      $xOffset = 0, // shift right (+N) or left (-N) cells
    int      $yOffset = 0  // shift down  (+N) or up   (-N) lines
): string
```

Corner positions
----------------

[](#corner-positions)

```
// Top-right corner
$veil->composite($fg, $bg, Position::TOP, Position::RIGHT);

// Bottom-left corner with offset
$veil->composite($fg, $bg, Position::BOTTOM, Position::LEFT, xOffset: 2, yOffset: -1);
```

Backdrop Dimming
----------------

[](#backdrop-dimming)

Dim the background behind the overlay using `withBackdrop(int $opacity)` where opacity ranges from 0 (no dimming) to 100 (fully dimmed). The backdrop is applied via ANSI SGR codes before compositing.

```
// Dim the background to 50% intensity
$veil = Veil::new()->withBackdrop(50);
$output = $veil->composite($fg, $bg, Position::CENTER, Position::CENTER);
```

Animations
----------

[](#animations)

Overlay transitions can be animated using `withAnimation(AnimationKind)`. The `animate()` method accepts a `float $progress` parameter (0.0–1.0) to drive the transition. Animations use honey-bounce `CubicBezier` easing internally.

### Available Animation Kinds

[](#available-animation-kinds)

KindBehavior`SLIDE`Foreground enters from the anchor direction`FADE`Foreground opacity increases from 0 to 1 (terminal-dependent)`SCALE`Lines appear from the center outward```
use SugarCraft\Veil\Veil;
use SugarCraft\Veil\Animation\AnimationKind;
use SugarCraft\Veil\Position;

$veil = Veil::new()
    ->withAnimation(AnimationKind::SLIDE)
    ->withBackdrop(30);

// Animate from 0% to 100% progress
for ($p = 0.0; $p animate($fg, $bg, Position::CENTER, Position::CENTER, progress: $p);
    // render $output ...
}
```

The `animate()` method composes the overlay with the animation applied at the given progress value. For `SLIDE`, the foreground is offset toward the anchor direction. For `SCALE`, lines are revealed from the center outward. For `FADE`, the foreground is returned unchanged but the easing progress is calculated for external use.

Z-Index Stacking
----------------

[](#z-index-stacking)

Use `withZIndex(int $zIndex)` to control the stacking order when rendering multiple overlays. Veils with higher z-index values render on top of those with lower values.

```
use SugarCraft\Veil\Veil;
use SugarCraft\Veil\Position;

$veil = Veil::new()
    ->withZIndex(10);  // renders above veils with zIndex < 10

$output = $veil->composite($fg, $bg, Position::CENTER, Position::CENTER);
```

Accessor: `zIndex(): int`

VeilStack (Multi-Veil Rendering)
--------------------------------

[](#veilstack-multi-veil-rendering)

`VeilStack` manages multiple veils ordered by z-index. When compositing, it sorts veils ascending by z-index and composites each onto the result of the previous one, so higher z-index veils appear on top of lower ones.

```
use SugarCraft\Veil\Veil;
use SugarCraft\Veil\VeilStack;
use SugarCraft\Veil\Position;

$stack = VeilStack::new()
    ->add(Veil::new()->withZIndex(0)->withBackdrop(30))           // base dim layer
    ->add(Veil::new()->withZIndex(10)->withBackdrop(0));           // modal on top

$output = $stack->composite($background, Position::CENTER, Position::CENTER);
```

### VeilStack API

[](#veilstack-api)

MethodDescription`add(Veil $veil): self`Add a veil (returns new stack)`clear(): self`Remove all veils`removeWhere(\Closure $pred): self`Remove veils matching predicate`filter(\Closure $pred): self`Keep only veils matching predicate`composite($background, $v, $h, $xOff, $yOff): string`Composite all with shared position`compositeAll($background): string`Composite all with their own positions`sorted(): list`Veils sorted by z-index ascending`all(): list`All veils in insertion order`maxZIndex(): int`Highest z-index in stack (0 if empty)`minZIndex(): int`Lowest z-index in stack (0 if empty)`isEmpty(): bool`True if stack has no veils`count(): int`Number of veils in stackAuto-Size
---------

[](#auto-size)

Use `withAutoSize(bool $enabled = true)` to compute veil dimensions from content rather than from fixed width/height. When combined with `withBorder()`, the border chrome is applied to the content before dimension computation.

```
use SugarCraft\Veil\Veil;
use SugarCraft\Sprinkles\Border;
use SugarCraft\Veil\Position;

$veil = Veil::new()
    ->withAutoSize()
    ->withBorder(Border::new()->style(Border::STYLE_ROUND));

$output = $veil->composite($content, $bg, Position::CENTER, Position::CENTER);
```

Accessor: `autoSize(): bool`

Border Chrome
-------------

[](#border-chrome)

Use `withBorder(Border $border)` to wrap veil content in a terminal border rendered by candy-sprinkles `Style`. The `applyBorderChrome(string $content)` method applies the border to arbitrary content.

```
use SugarCraft\Veil\Veil;
use SugarCraft\Sprinkles\Border;
use SugarCraft\Sprinkles\Style;

$veil = Veil::new()
    ->withBorder(Border::new()->style(Border::STYLE_ROUND));

// Apply border to arbitrary content
$bordered = $veil->applyBorderChrome("Hello, World!");
```

Accessors: `border(): ?Border`

Click-Outside Dismiss
---------------------

[](#click-outside-dismiss)

Use `withClickOutsideDismiss(bool $enabled = true)` to flag a veil for dismissal when a mouse click falls outside its rendered zone. The self-contained `Scanner` (from [candy-mouse](https://github.com/detain/sugarcraft-candy-mouse)) handles hit testing locally — call `scan($rendered)` after rendering to register the veil zone, then use `isClickOutside()` or `hit()` to query it.

```
use SugarCraft\Veil\Veil;

$veil = Veil::new()
    ->withClickOutsideDismiss();

// ... after rendering the veil output ...
$veiled = $veil->scan($renderedOutput);

// Check if a mouse message is outside the veil's zone
$outside = $veiled->isClickOutside($mouseMsg);
if ($outside) {
    // dismiss the veil
}
```

Accessors: `clickOutsideDismiss(): bool`

The `isClickOutside(MouseMsg $mouse): bool` method returns `true` when `clickOutsideDismiss` is enabled, a rendered output has been scanned, and the click falls outside all tracked zones. Returns `false` when no scan data is available or when click-outside-dismiss is disabled.

Zone Manager Integration (deprecated)
-------------------------------------

[](#zone-manager-integration-deprecated)

`withManager(Manager $manager)` is retained for backward compatibility only. It stores the manager but does **not** drive `isClickOutside()`. The self-contained `Scanner` is always used for hit-testing. New code should use `scan()` / `hit()` directly instead of wiring a `Manager`.

```
use SugarCraft\Veil\Veil;

$modal = Veil::new()->withClickOutsideDismiss();
// Use scan()/hit() for hit-testing — Manager is not consulted by isClickOutside()
```

Buffer diffing
--------------

[](#buffer-diffing)

The `composite()` method maintains a `?Buffer $previousFrame` across calls. On each call it builds the current Buffer, computes `current->diff(previous)` (from [candy-buffer](https://github.com/detain/sugarcraft-candy-buffer)), and emits only the delta ANSI ops via `DiffEncoder::encode($ops)`. The current frame then replaces `previousFrame` for the next render.

**SSH bandwidth + flicker win:** a one-character change in an 80×24 viewport produces ~8 bytes of delta ops instead of ~1 940 bytes for a full repaint. Over an SSH session this means far less per-frame data on the wire and eliminates the full-screen flicker of rewrite-based terminals. The first render after startup or a resize still emits a full Buffer (no diff possible), so behaviour is always correct.

Shared foundations
------------------

[](#shared-foundations)

Mouse hit-testing is self-contained via [candy-mouse](https://github.com/detain/sugarcraft-candy-mouse). The `Scanner` class handles zone registration and hit testing locally via `scan()` / `hit()`. `withManager()` is retained as a deprecated back-compat wrapper and is not consulted by `isClickOutside()`.

License
-------

[](#license)

[MIT](LICENSE)

###  Health Score

25

—

LowBetter than 35% of packages

Maintenance62

Regular maintenance activity

Popularity16

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/b1036e0717211b8030b83cbe729e8ba6ba442fdbd5285fb97a39d7dcfe339342?d=identicon)[detain](/maintainers/detain)

---

Top Contributors

[![detain](https://avatars.githubusercontent.com/u/1364504?v=4)](https://github.com/detain "detain (81 commits)")

### Embed Badge

![Health badge](/badges/sugarcraft-sugar-veil/health.svg)

```
[![Health](https://phpackages.com/badges/sugarcraft-sugar-veil/health.svg)](https://phpackages.com/packages/sugarcraft-sugar-veil)
```

###  Alternatives

[illuminate/console

The Illuminate Console package.

13046.0M6.8k](/packages/illuminate-console)[styleci/cli

The CLI tool for StyleCI

71470.5k9](/packages/styleci-cli)[winbox/args

Windows command-line formatter

20720.9k21](/packages/winbox-args)[mallardduck/laravel-traits

A collection of useful Laravel snippets in the form of easy to use traits.

136.2k2](/packages/mallardduck-laravel-traits)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
