Files
reasampler/src/prune_button.h
T
daniel ef2d3fa846 fix(prune): honest reclaim/skip counts; cross-ref layout coupling
Already-absent files no longer inflate reclaimedCount (outAlreadyAbsent
distinguishes vanished-at-delete from real failures). Stale skip count
de-dups confirmed before subtraction so duplicates aren't tallied as
stale. prune_button.h rightInset and drawTailFooter vrc.right now
cross-reference each other by name.
2026-07-26 19:15:54 -04:00

97 lines
5.1 KiB
C++

#pragma once
// prune_button — the REAPER-free layout math behind the bank_panel's Prune button
// (Phase R, Wave 3 — R3, fork R-E). A single labelled button drawn in the panel's
// tail-footer strip that fires the "Prune bank folder" command. The panel shell
// (bank_panel.cpp) owns the SWELL window, LICE drawing, and the Main_OnCommand
// dispatch of the registered command id — all REAPER-bound, DAW-verified. What is
// NOT DAW-bound — WHERE the button sits in the footer and whether a click lands on
// it — lives here so it is unit-tested outside the DAW (CLAUDE.md §load-bearing
// split). Mirror of mode_switch / tab_strip.
//
// PURE MODULE: NO REAPER types, NO SWELL, NO vendor/ includes. Standard library
// only. Builds and unit-tests without REAPER.
//
// -- Placement contract --------------------------------------------------------
//
// The footer already hosts a LEFT-aligned tail-mode label and a RIGHT-aligned
// version readout (bank_panel drawTailFooter). The prune button is a fixed-width
// button anchored to the RIGHT of the footer, inset from the right edge, sitting
// just LEFT of the version readout's inset region. It never overlaps the tail label
// at the left. When the footer is too narrow to fit the button without colliding
// with the left inset, the button is suppressed (empty rect) rather than drawn on
// top of the label — the action is always reachable via its bindable command, so a
// hidden button is a graceful degradation, not a lost affordance.
namespace reasampler {
// The footer strip the button is drawn into, top-left origin (SWELL/LICE
// convention). (x, y) is the top-left corner; width/height are the strip extents.
// bank_panel derives this from panelFooter() and passes it here.
struct FooterRect {
int x = 0;
int y = 0;
int width = 0;
int height = 0;
bool operator==(const FooterRect& o) const {
return x == o.x && y == o.y && width == o.width && height == o.height;
}
};
// A button's pixel rectangle within the footer, top-left origin. A zero-area rect
// (width <= 0 or height <= 0) means "no button" — the footer is too narrow to place
// it, or the footer itself is degenerate; the caller must not draw or hit-test it.
struct ButtonRect {
int x = 0;
int y = 0;
int width = 0;
int height = 0;
bool empty() const { return width <= 0 || height <= 0; }
bool operator==(const ButtonRect& o) const {
return x == o.x && y == o.y && width == o.width && height == o.height;
}
};
// Layout inputs for the prune button, in pixels. Defaults match the bank_panel footer
// metrics; the shell passes its own so draw and hit-test share one source of truth.
// * buttonWidth — the button's fixed width.
// * rightInset — gap from the footer's right edge to the button's right edge (the
// button sits left of this inset, clearing the right-aligned version
// readout). COUPLED TO drawTailFooter (bank_panel.cpp): the version
// readout uses `vrc.right -= 8` (8 px right margin). The button's
// right edge lands at footer.right - 84, i.e. 76 px left of the
// readout's right margin — enough clearance for the ~10-char label.
// If the version readout's inset changes in drawTailFooter, update
// this value to maintain clearance.
// * verticalInset — top/bottom gap inside the footer (the button is shorter than the
// strip so it reads as a raised control, not a full-height fill).
// * minLeftInset — the button's left edge must stay at least this far from the footer
// left edge (reserving room for the left-aligned tail label). If the
// button would encroach past this, computePruneButton yields an empty
// rect (button suppressed — see header placement contract).
struct PruneButtonSpec {
int buttonWidth = 72;
int rightInset = 84; // COUPLED: version readout in drawTailFooter uses vrc.right -= 8
int verticalInset = 4;
int minLeftInset = 120;
};
// Computes the prune button's rect within `footer` per `spec`. Right-anchored: the
// button's right edge is footer.x + footer.width - rightInset, its width is buttonWidth,
// and it is vertically centred by verticalInset. Returns an EMPTY rect (button
// suppressed) when: the footer is degenerate (width/height <= 0), OR the resulting left
// edge would fall closer to the footer left than minLeftInset (too narrow to place
// without colliding with the tail label). The action stays reachable via its command in
// that case — a suppressed button is graceful, not a lost feature.
ButtonRect computePruneButton(const FooterRect& footer, const PruneButtonSpec& spec);
// True iff the point (px, py) (SWELL/LICE top-left client coords) falls inside `button`.
// Half-open bounds [x, x+width) x [y, y+height) — matches computePruneButton so draw and
// hit-test agree on the same pixels. An empty button never claims a point (always false),
// so a suppressed button cannot be accidentally clicked.
bool hitTestPruneButton(int px, int py, const ButtonRect& button);
} // namespace reasampler