Skip to main content

Status bar and indicators

Three widgets that only draw. None of them takes the focus, and all three implement IArlecchinoWidget rather than the interactive one.

StatusBar

private readonly Spinner _spinner = new();
private readonly ArlecchinoKeymap keymap;

new StatusBar
{
Left = [() => Loc(LocString.ItemCount, count), () => _spinner.Current],
Right =
[
() => $"{keymap.NextField} {Loc(LocString.Panes)}",
() => $"{keymap.Cancel} {Loc(LocString.Back)}",
],
}.Draw(region.Rows(region.Height - 1, 1));

Widgets are built in the view's constructor, so options is the ArlecchinoOptions the container hands it, and region is the region the view draws the widget into.

Left and right groups joined with three spaces; the right side is dropped when it would collide with the left instead of overwriting it, so a narrow terminal loses the least important half rather than producing a mess.

Empty entries are skipped, so a part that is only sometimes relevant can return "":

private string _filter = "";

Left = [() => _filter.Length > 0 ? $"filter: {_filter}" : ""],

Interpolating a KeyBinding rather than writing Tab is what keeps the bar truthful when a key is rebound.

The status bar and the output line

They are different things. The output line is framework chrome on the last row, turned on and off with options.ShowOutputLine. A StatusBar is a widget a screen draws wherever it likes — usually on the last row of its own region, which is why applications that want the row for themselves run with the output line off.

ProgressBar

var progress = new ProgressBar { Value = 68, Caption = value => $"{value:0}%" };

progress.Draw(region.Rows(0, 1));
MemberMeaning
ValueWhere it is
Minimum / MaximumDefault to 0 and 100
CaptionTurns the value into the text beside the bar
StyleColors it

The bar fills the region width minus the caption, so the caption never pushes it off the edge.

For a bar measured against a range that need not start at zero, and colored by the bands it crosses, see Gauge.

Spinner

_spinner.Advance(); // once per frame or per tick
_spinner.Draw(region.SplitLeft(region.Width - 1).Right);

Spinner cycles a set of frames — braille dots by default, replaceable through Frames — and paints the top-left cell of whatever region it is given, so hand it the one cell it belongs in. Current is the frame as a string, for putting it in a status bar instead.

Nothing advances it for you. Two places usually do:

private readonly Ticker _ticker;

_ticker.Every(TimeSpan.FromMilliseconds(80), () => _spinner.Advance());

— which spins at a steady rate whether or not anything else is drawing, and is what to use beside an async atom; or a call in Draw, which spins once per frame and therefore only while something else is already asking for frames.