#pragma once // footer_bar — the REAPER-free, LICE-free layout + hit-test math for the bank_panel's L4 // footer LEFT group: the narrowed [Arrange|Design] mode toggle, its compact per-mode count // label, and the Tail button, laid out left-to-right at the footer's left. The panel shell // (bank_panel.cpp) owns the SWELL window, LICE drawing, and the click dispatch (cycle tail / // activate a mode); what is NOT DAW-bound — WHERE the toggle box, the count label, and the // Tail button sit, and which one a click lands on — lives here so it is unit-tested outside // the DAW (CLAUDE.md §load-bearing split). Mirror of action_bar / mode_switch / prune_button. // // -- Footer affordance order (L4, left -> right) ------------------------------- // // [Arrange|Design] toggle . count label . Tail button . ... . Prune (rightmost, warn) // // The two view/session controls (mode toggle, tail) group at the LEFT as the "how this // panel/capture behaves" cluster; Prune stays isolated at the far RIGHT, warn-colored and // set apart (it is the only byte-deleting affordance). This module lays out the LEFT group // ONLY — the rightmost Prune button remains owned by prune_button (computePruneButton), so // the two never fight over the same pixels. footer_bar reserves a right margin (rightReserve) // so its own affordances never run under the prune button's region. // // The mode toggle is drawn as an N-segment control (2 segments for Arrange|Design; N general). // footer_bar returns only the toggle's BOX (fit to its text width); the shell hands that box's // width to the pure mode_switch (computeSegmentRects / hitTestSegment) for the per-segment // tiling and hit-test, so mode_switch stays the ONE owner of segment geometry. footer_bar // decides the toggle's placement + overall width; mode_switch subdivides it. // // NAME NOTE (brief §name-collision): ButtonRect / FooterRect / SegmentRect / ActionBarRect / // KitBox / KitButtonBox are already owned in this namespace; grep-checked FooterBar* / FooterHit // FREE before minting. FooterRect (prune_button) is the input strip type and is REUSED here // (same concept — the footer strip); the new output/spec/hit types carry the FooterBar* prefix. // // PURE MODULE: NO REAPER types, NO SWELL, NO LICE, NO vendor/ includes. Standard library only. #include "prune_button.h" // FooterRect — the footer strip input type (shared, not re-minted) namespace reasampler { // One placed affordance's pixel rectangle within the footer, top-left origin. A zero-area rect // (empty()) means "not placed" (the footer was too narrow to host it after the ones before it), // so the shell draws/hit-tests nothing for it — graceful degradation, mirroring prune_button. struct FooterBarRect { int x = 0; int y = 0; int width = 0; int height = 0; bool empty() const { return width <= 0 || height <= 0; } bool operator==(const FooterBarRect& o) const { return x == o.x && y == o.y && width == o.width && height == o.height; } }; // The laid-out footer LEFT group: the mode toggle box, the count label box, and the Tail // button box, in left-to-right order. Any box may be empty (suppressed) when the footer is // too narrow to fit it left of the reserved right margin — placement is greedy left-to-right, // so an earlier affordance survives while a later one drops (the toggle is most important, // the Tail button drops first on a very narrow footer). struct FooterBarLayout { FooterBarRect toggle; // the [Arrange|Design] segmented control's overall box FooterBarRect count; // the compact per-mode count label (right of the toggle) FooterBarRect tail; // the Tail button (right of the count label) bool operator==(const FooterBarLayout& o) const { return toggle == o.toggle && count == o.count && tail == o.tail; } }; // Which footer LEFT-group affordance a point landed on (or None for a miss / a suppressed // affordance). Prune is NOT here — the shell hit-tests it separately via hitTestPruneButton. enum class FooterHit { None, Toggle, Tail }; // Layout inputs for the footer LEFT group, in pixels. Defaults are the bank_panel footer // metrics; the shell passes its own so draw and hit-test share ONE source of truth. // * toggleWidth — the [Arrange|Design] toggle's overall width. Sized to fit its two // segment labels comfortably (a NARROW control, per L4 §3 — no longer the // full-width top header). The shell picks this to fit its text; the pure // module treats it as a fixed input. // * countWidth — the compact per-mode count label's width (e.g. "2 tracks"). 0 hides it. // * tailWidth — the Tail button's width (fits "Tail: Manual 8.0s" comfortably). // * gap — horizontal gap between adjacent affordances. // * leftPad — inset from the footer left edge to the toggle's left edge. // * verticalInset — top/bottom gap inside the footer so the controls read as raised, not // full-height fills (matches prune_button's verticalInset). // * rightReserve — pixels reserved at the footer's RIGHT for the prune button + version // readout region; footer_bar never places an affordance whose right edge // would cross into (footer.right - rightReserve). Keeps the LEFT group // clear of the RIGHT prune/version region without those modules coupling. struct FooterBarSpec { int toggleWidth = 132; int countWidth = 64; int tailWidth = 132; int gap = 6; int leftPad = 8; int verticalInset = 4; int rightReserve = 168; // clears prune_button (rightInset 84 + width 72) + margin }; // Lays out the footer LEFT group inside `footer` per `spec`, left-to-right: toggle, then the // count label, then the Tail button, each `gap` px apart, starting at footer.left + leftPad, // vertically centred by verticalInset. Greedy: an affordance is placed only if its whole box // fits left of (footer.right - rightReserve); otherwise it (and, since placement is ordered, // it alone or the ones after it) is suppressed (empty box). A degenerate footer (width/height // <= 0) yields an all-empty layout. countWidth <= 0 suppresses the count label (and the gap // that would precede the Tail button collapses so the Tail sits right after the toggle). FooterBarLayout computeFooterBar(const FooterRect& footer, const FooterBarSpec& spec); // The footer LEFT-group affordance the point (px, py) (SWELL/LICE top-left client coords) lands // on, or FooterHit::None for a miss (outside every placed box, or on the count label — which is // a passive readout, not a control). Half-open bounds [x, x+width) x [y, y+height) match // computeFooterBar so draw and hit-test agree on the same pixels. An empty (suppressed) box // never claims a point. The shell checks the toggle hit FIRST for a segment sub-hit (via // mode_switch over the toggle box), then the Tail hit; this returns which region was struck. FooterHit hitTestFooterBar(int px, int py, const FooterBarLayout& layout); } // namespace reasampler