HintBar
NAME
App::Moneymoor::View::HintBar - the one canonical keybind-hint table,
rendered as styled RichText spans that truncate a group at a time.
SYNOPSIS
use App::Moneymoor::View::HintBar;
# Every hint the app can show, keyed by context.
hint-contexts(); # (budget generic login reconcile
# register reports sidebar)
hint-groups('sidebar').head; # Enter => open
# The footer paints these straight into a Selkie::Widget::RichText.
$footer.set-content(
hint-spans('budget', $footer.cols, theme => $main.theme,
icons => $main.icons),
);
DESCRIPTION
The bottom line of every App::Moneymoor screen is a keybind
cheat-sheet that changes with focus. Screen::Main owns the footer
widget and Screen::Main::Subscriptions decides which context is
active, so this module owns the one %HINTS table and the one
renderer rather than letting the two hand-roll their own copies β
one flat fg-dim line each, hard-truncated by the terminal edge
with no indication that anything had been cut off, is the failure
mode this exists to avoid.
This module owns all of it: one %HINTS table, one renderer.
Contexts
A context is a bare string key β 'budget', 'register' β not
a style bundle and not a span list. That matters: the screens pick the
context inside a store subscription, and
Selkie::Store's change digest keys objects by .WHICH, so a
selector that returned freshly-built Selkie::Style or Span
objects would read as "changed" on every tick and re-push the whole
footer every frame. Selectors return the key; the callback builds the
spans.
genericβ no recognised focus target: the app-wide keys only.loginβ the login screen's three keys. Rendered once, at build time, into the fixed-width dialog's bottom line: unlike the main screens,Screen::Loginnever resizes, so it neither subscribes to a context nor repaints on resize.budgetβ the budget tab's envelope table.registerβ the accounts tab's transaction register.reconcileβ register focus while reconcile mode (Ctrl+R) is active; overridesregisterfor the duration.sidebarβ the accounts tab's account list.reportsβ the reports tab.
An unrecognised context falls back to generic rather than throwing β
same fallback semantics as App::Moneymoor::View::EmptyState and
App::Moneymoor::Themes::load.
Truncation
Hints are dropped a whole group at a time, never mid-group: half a
keybind ("Ctrl+1..0 pers") is worse than no keybind. Trailing groups
are dropped until what remains β plus the β¦ ^h tail β fits the
requested width. The tail says two things at once: "there is more" and
"Ctrl+H shows all of it". It is ^h rather than ? because ? is
deliberately not a global bind (a payee name or memo field may
legitimately contain one β see the comment on the ctrl+h bind in
Screen::Main::Keybinds).
Widths narrower than a single group plus the tail degrade to the tail alone; a width of zero or less returns no spans at all. Neither throws β the footer is laid out by whatever the terminal happens to be, and a one-column terminal must not take the app down.
Rendering contract
Each group renders as < key label > β a key span in the palette
accent, bold, then a label span in fg-dim carrying the separating
space. Groups are joined by a Β· in fg-dimmer, and the tail is
fg-dimmer too, so the eye lands on the keys first and the dividers
disappear.
The returned spans total at most $width columns
($width - $reserve when a reservation is given), so they occupy
exactly one line of a Selkie::Widget::RichText. There is no leading
margin space: RichText's wrapper drops a whitespace token at column
0, so a margin span would be silently eaten β the bar starts flush
left.
EXAMPLES
Repainting a footer
Width comes from the widget, because that is what the spans have to fit
in; a screen that has not been laid out yet reports zero columns, so
callers substitute a sane default and repaint when the real width
arrives (both screens do this from an App.on-resize callback):
method !repaint-hints() {
my $w = $!hint-footer.cols > 0 ?? $!hint-footer.cols !! 80;
$!hint-footer.set-content(
hint-spans($!hint-context, $w, theme => $!theme, icons => self.icons),
);
}
Reserving room on the right
:reserve subtracts columns before any fitting is done, for a
right-aligned segment the caller paints itself (a clock, a sync
indicator). The hint groups simply behave as though the terminal were
that much narrower:
my @spans = hint-spans('budget', 100, :$theme, reserve => 12);
@spansΒ».text.join.chars; # <= 88
Inspecting the table
hint-groups is the table itself, so a test (or a future help
overlay) can assert against the pairs rather than parsing a rendered
string:
for hint-contexts() -> $ctx {
for hint-groups($ctx) -> $group {
say "$ctx: {$group.key} = {$group.value}";
}
}
EXPORTS
hint-contexts(--List)> β every context key, sorted.hint-groups(Str $context --List)> β that context's ordered< key => label> pairs, falling back togeneric.hint-spans(Str $context, Int $width, :$theme!, :$icons, :$reserve --List)> β theSelkie::Widget::RichText::Spans to paint.
SEE ALSO
App::Moneymoor::Screen::Main::Subscriptions β routes focus to a context key (
hint-for-focus).App::Moneymoor::View::ModalChrome β the modals' equivalent canonical style bundle.