BannerBar
NAME
App::Moneymoor::Widget::BannerBar - the one-row top banner: a horizontal colour ramp, the perspective breadcrumb, and a right-aligned clock.
SYNOPSIS
use App::Moneymoor::Widget::BannerBar;
use Selkie::Sizing;
my $banner = App::Moneymoor::Widget::BannerBar.new(
palette => $theme,
text => ' App::Moneymoor',
sizing => Sizing.fixed(1),
);
$root.add($banner);
# The perspective subscription keeps the left text current...
$banner.set-text(' App::Moneymoor ā Inbox āø Roof repair');
# ...and one frame callback keeps the clock current, without the
# store ever hearing about it.
install-banner-clock($app, $banner);
# A live palette swap re-reads the stops and repaints.
$banner.set-theme-colours($new-theme);
DESCRIPTION
The banner is the only chrome in Moneymoor that carries a gradient. It
paints App::Moneymoor::Theme.banner-gradient ā the flat banner
background on the left, that background pulled a third of the way
toward the palette's accent on the right ā across its whole width, then
writes two pieces of text over it:
The left text, whatever
set-textwas last handed, infg-bannerbold. Truncated with an ellipsis when it doesn't fit.The clock,
HH:MM, flush against the right edge, infg-bannerblended 70% of the way toward the ramp's right stop so it reads as a timestamp rather than as a second headline.
Text on a gradient
Selkie::Gradient's banner idiom is "gradient-fill at the top of
your own render, then putstr over it", and that is what happens
here ā with one refinement. ncplane_putstr paints the plane's
current background into every cell it writes, so a naive
fill-then-putstr leaves the text sitting in a flat rectangle of
whatever background was set last, with a visible seam where it ends.
So the text is stamped in runs: the background is set to the ramp's
own value at that column and a run of characters sharing that value is
written in one call. banner-cell-colour is the interpolation, and it
is deliberately notcurses's own integer formula
(calc_gradient_component's single-row case: truncating division, no
rounding), so a stamped cell is bit-identical to the one
gradient-fill painted underneath it ā the text really is on the
ramp, not near it. How long the runs get is the palette's business: a
ramp whose components move less than a step per column collapses whole
words into one call, and a steeper one degrades to a call per column.
Either way the cost is bounded by the banner's width and paid only on a
frame where something actually changed ā which, once the perspective
settles, is once a minute.
banner-cell-colour is exported so that relationship is testable
without a terminal: assert its endpoints against
Theme.banner-gradient, then assert the rendered cells against it.
The clock does not go through the store
The clock is wall-clock state that nothing else in the app reads, and
dispatching it would be actively harmful: a store dispatch on a fixed
period is exactly the metronomic same-shape work pattern documented in
Screen::Main.!wire-time-signals as having tripped a MoarVM
specialiser bug. So the clock is a plain frame callback
(install-banner-clock) that owns a cached formatted minute and calls
tick-clock, which marks the widget dirty only when the string it
is handed differs from the one already showing ā 59 of every 60 ticks
are a single string comparison and nothing else.
The idle-frame cost is one now.Int and one integer comparison. The
detector is a second boundary rather than a minute boundary on purpose:
Instant counts TAI seconds, so its minute buckets sit a
leap-second offset away from wall-clock minutes and a minute-granular
gate would show the new minute up to half a minute late. Second buckets
are offset by the same constant but recur 60 times as often, so the
displayed minute is never more than a second stale, and the once-a-second
DateTime.now + sprintf is the only allocation on the path.
Layout
The right five columns (plus one of separation) are reserved for the clock whether or not one has ticked yet, so the left text does not jump the first time the clock appears. Below six columns the reservation is dropped and the text gets the whole row; below five the clock is not painted at all ā a half-written time reads as corruption.
visible-text resolves that budget and is public, plane-free and
width-parameterised: a widget that has not been laid out yet reports
zero columns, so truncating at set-text time would truncate against
the wrong width and never recover on resize.
SEE ALSO
Selkie::Gradient ā the ramp primitive and its three idioms
App::Moneymoor::Theme ā
banner-gradientandblend