Skip to main content

Progress

Collection sometimes triggers slow work: a discovery scan of a target directory, a computed option set, a value resolved against external state. Left silent, the form looks frozen. The progress() primitive wraps that work and shows it running - a spinner when the length is unknown, a determinate bar when it is - and passes the callback's result straight back.

progress() is one of the facade's primitives: it collects no answer and never runs inside the interactive panel. It is for slow work that happens around the form - before it opens, after it closes, or wrapping a slow ->default / ->discover. The active theme draws it, so it matches the panel's look and honours the colour and Unicode switches. Off a TTY, or in headless collection, it degrades to a single plain caption line with no control sequences. The callback receives the primitive and drives it with advance().

use DrevOps\Tui\Primitive\Progress;

// No total: an indeterminate spinner. Each advance() ticks a frame.
$total = $tui->progress(null, 'Counting the baskets', function (Progress $progress): int {
$count = 0;
foreach (shelves() as $shelf) {
$count += count_baskets($shelf); // slow, one shelf at a time
$progress->advance(); // tick the spinner between shelves
}

return $count;
});

// A known total: a determinate bar. Each advance() fills one step.
$tui->progress(count($items), 'Packing the order', function (Progress $progress) use ($items): void {
foreach ($items as $item) {
pack($item);
$progress->advance('packed ' . $item);
}
});

Spinner

With a null total the indicator is an animated spinner: an accent glyph beside the caption, cycled one frame per advance(), until the callback returns.

Progress spinnerProgress spinner

Runnable in playground/15-progress-spinner.php.

In all four display modes - Unicode or ASCII, colour on or off:

ANSINo ANSI
UnicodeSpinner: Unicode + ANSISpinner: Unicode + ANSISpinner: Unicode + No ANSISpinner: Unicode + No ANSI
ASCIISpinner: ASCII + ANSISpinner: ASCII + ANSISpinner: ASCII + No ANSISpinner: ASCII + No ANSI

Progress bar

With a known total the indicator is a determinate bar: it fills as it advances, showing a step count and a trailing label. Each advance() fills one step and can replace the label.

Progress barProgress bar

Runnable in playground/15-progress-bar.php.

ANSINo ANSI
UnicodeBar: Unicode + ANSIBar: Unicode + ANSIBar: Unicode + No ANSIBar: Unicode + No ANSI
ASCIIBar: ASCII + ANSIBar: ASCII + ANSIBar: ASCII + No ANSIBar: ASCII + No ANSI

Theme-drawn

The glyphs and the accent come from the active theme, the same way every widget does - the spinner glyph and the bar fill carry the theme's accent, and the theme picks Unicode or ASCII. So ->theme('ember') spins and fills in ember's orange, ->theme('frost') in frost's blue, with no extra configuration.

Degrading off a TTY

Feedback is chrome, not data, so it is drawn on standard error and animates only when standard error is an interactive terminal. Piped, redirected or collected headlessly, progress() prints the caption once as a plain line and emits no cursor, colour or carriage-return sequences, so a captured log stays clean:

php playground/15-progress-bar.php 2>&1 | cat
# Packing the order

Inside the form

progress() runs around the form. Three counterparts show feedback inside the interactive panel, drawn by the same theme and resolved the first time a panel opens - so a drill-in fetches only what that panel needs, once.

  • Loading a field's options. ->options() takes a callback instead of a fixed list; it resolves when the field's panel opens, the field showing a themed Loading… until it returns. Headless collection resolves it up front.
  • Preloading a panel. ->preload(closure) on a panel runs once, before the panel's fields first draw - prep the panel needs, fetched on entry rather than up front, so one fetch can feed several fields.
  • The progress widget. A panel row that runs its work when activated, filling a bar or ticking a spinner in the row itself. Unlike progress(), it lives among the fields and collects no value.
$form->panel('order', 'New order', function (PanelBuilder $p) use ($pack): void {
// Resolved when the panel opens; the field shows "Loading…" until it returns.
$p->select('fruit', 'Fruit')->options(fn(): array => load_fruit());

// A row that runs its work in place when activated.
$p->progress('pack', 'Packing the box')->steps(6)->run($pack);
});

Runnable in playground/16-loading-data.php and playground/02-widgets-progress.php.