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-text was last handed, in fg-banner bold. Truncated with an ellipsis when it doesn't fit.

  • The clock, HH:MM, flush against the right edge, in fg-banner blended 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

App::Moneymoor v0.4.2

YNAB-style envelope budgeting: a derivation engine

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

DBIish:ver<0.6.7>:auth<zef:raku-community-modules>Notcurses::Native:ver<0.6.5+>:auth<zef:apogee>Selkie:ver<0.16.0+>:auth<zef:apogee>JSON::Fast:ver<0.19>:auth<cpan:TIMOTIMO>MacOS::NativeLib:ver<0.0.6>:auth<zef:lizmat>

Test Dependencies

Provides

  • App::Moneymoor
  • App::Moneymoor::Config
  • App::Moneymoor::DB
  • App::Moneymoor::Gateway::Account
  • App::Moneymoor::Gateway::Assignment
  • App::Moneymoor::Gateway::Category
  • App::Moneymoor::Gateway::Payee
  • App::Moneymoor::Gateway::Transaction
  • App::Moneymoor::Handlers::Boot
  • App::Moneymoor::Model::Account
  • App::Moneymoor::Model::Assignment
  • App::Moneymoor::Model::Category
  • App::Moneymoor::Model::CategoryGroup
  • App::Moneymoor::Model::Payee
  • App::Moneymoor::Model::Split
  • App::Moneymoor::Model::Transaction
  • App::Moneymoor::Screen::Accounts
  • App::Moneymoor::Screen::Budget
  • App::Moneymoor::Screen::Login
  • App::Moneymoor::Screen::Main
  • App::Moneymoor::Screen::Main::Keybinds
  • App::Moneymoor::Screen::Main::Modals
  • App::Moneymoor::Screen::Main::Subscriptions
  • App::Moneymoor::Screen::Reports
  • App::Moneymoor::Service::Budget
  • App::Moneymoor::Service::Icons
  • App::Moneymoor::Service::Target
  • App::Moneymoor::Service::Workspace
  • App::Moneymoor::StoreHandlers
  • App::Moneymoor::Theme
  • App::Moneymoor::Theme::Catppuccin
  • App::Moneymoor::Theme::Dracula
  • App::Moneymoor::Theme::Everforest
  • App::Moneymoor::Theme::Gruvbox
  • App::Moneymoor::Theme::Kanagawa
  • App::Moneymoor::Theme::Monokai
  • App::Moneymoor::Theme::Nord
  • App::Moneymoor::Theme::OneDark
  • App::Moneymoor::Theme::RosePine
  • App::Moneymoor::Theme::Solarized
  • App::Moneymoor::Theme::TokyoNight
  • App::Moneymoor::Themes
  • App::Moneymoor::UI
  • App::Moneymoor::Util::Money
  • App::Moneymoor::Util::Period
  • App::Moneymoor::View::BudgetRow
  • App::Moneymoor::View::EmptyState
  • App::Moneymoor::View::HintBar
  • App::Moneymoor::View::InspectorPane
  • App::Moneymoor::View::ModalChrome
  • App::Moneymoor::View::RegisterRow
  • App::Moneymoor::View::ReportRow
  • App::Moneymoor::Widget::BannerBar
  • App::Moneymoor::Widget::BootProgressModal

The Camelia image is copyright 2009 by Larry Wall. "Raku" is a trademark of the Yet Another Society. All rights reserved.

Built with Podlite — the markup and publishing tools behind this site.