Budget
NAME
App::Moneymoor::Screen::Budget - the budget tab: the Ready-to-Assign pill, the envelope table and the detail rail.
SYNOPSIS
use App::Moneymoor::Screen::Budget;
# Built by Screen::Main's content-host seam, never directly:
my $tab = App::Moneymoor::Screen::Budget.new(main => $main);
$tab.build($content-host); # mounts the panes
$tab.install-subscriptions; # store โ rows / rail / pill
$tab.install-keybinds; # a m f x n g e h u i, and Enter
# What the modals in Screen::Main::Modals read back:
$tab.selected-row; # the row hash under the cursor
$tab.selected-category-id; # Int, or the type object on a header
$tab.selected-group-id; # Int, or the type object on a category
DESCRIPTION
VBox (the content host)
โ Border(fixed 3, 'August 2026') the Ready-to-Assign pill
โ โ HBox: RichText(flex) 'Ready to Assign ยฃ412.50'
โ Text(flex, right) 'ยฃ300.00 assigned in future periods'
โ HBox(flex)
โ Border(flex, 'Envelopes ยท August 2026') โ Table
โ Border(fixed 30, '<category>') โ RichText rail
The Table's five columns are Category (flex), Assigned, Activity,
Available and Target; View::BudgetRow renders every cell.
Everything this class paints comes out of two pure modules โ
View::BudgetRow for the grid and the pill, View::InspectorPane
for the rail โ and this file is the wiring: which widget, which
subscription, which key.
Group headers live in the same Table as the rows
Selkie::Widget::Table has no notion of a section, so the group
headers are ordinary rows carrying < kind => 'group' >. That buys
one grid, one cursor and one scrollbar; it costs the two things this
class has to handle by hand:
The cursor can sit on a header. Every action checks C<selected-row><kind> and either does the group-flavoured thing (
eedits the group,ddeletes it, Enter folds it) or says why it can't. It never silently acts on the wrong row.Row indices are not stable across a rebuild. Folding a group or hiding an envelope changes what lives at index 7. So the cursor is restored by category id after every
set-rows, and only then is the selection re-announced to the store.
set-rows and select-index both fire on-select, which is how
the store learns where the cursor is โ so both are wrapped in a
suppression flag during a rebuild, and one deliberate announcement is
made at the end. Without it, a rebuild would announce the row that
happens to be under the old index, the rail would repaint for a
category the user never chose, and (once the rebuild was itself
triggered by a selection-keyed subscription) it would do it every
frame.
Sorting is off
Every column is < sortable => False >. Sorting reorders the view
rows without knowing that some of them are headers, which would scatter
the categories away from their groups. The grid's order is the user's
own sort_order, which is what the category and group editors exist
to change.
The rail is mounted, not hidden
i toggles budget/inspector, and the tab is rebuilt rather than
the rail being resized to nothing: a zero-allocation child of an
HBox has to be parked or it paints outside its parent, and "rebuild
the tab" is a path that already exists, already restores the cursor
and already re-registers every subscription. The rebuild is driven by a
subscription anchored on the content host โ the one widget in the
tab that survives its own rebuild.
METHODS
build($host)โ construct and mount. Registers the table as the tab'sbudgethint-context pane and its initial focus target.install-subscriptions()/install-keybinds()โ the two seamsScreen::Main::Subscriptions::install-contentandScreen::Main::Keybinds::install-contentcall.refresh-rows()/refresh-inspector()/refresh-rta()โ the subscription callbacks, public because a modal that has just mutated something outside the store's sight can ask for a repaint.table,selected-row,selected-category-id,selected-group-id,inspector-mounted.
SEE ALSO
App::Moneymoor::View::BudgetRow โ the rows, the pill, ยง2.
App::Moneymoor::View::InspectorPane โ the rail's lines.
App::Moneymoor::Screen::Main::Modals โ what the keys open.
App::Moneymoor::StoreHandlers โ
budget/select-category,budget/toggle-groupand the two change tokens.
the rows, keyed on both change tokens plus the period and the two view switches;
the rail, keyed on both change tokens and the cursor;
the pill, keyed on the derivation and the period;
the rail's presence, which rebuilds the tab.