L4: re-home dock-panel buttons into three zones
Top toolbar = capture+placement, bottom toolbar = Design-View verbs, footer = narrow Arrange|Design toggle + count + Tail button + set-apart Prune. New pure footer_bar module; Tail is now a real kit button.
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
// Standalone tests for reasampler::footer_bar — no REAPER, no test framework. Same fast loop
|
||||
// as the sibling pure tests (prune_button / mode_switch / action_bar): assert the L4 footer
|
||||
// LEFT-group layout (toggle . count . Tail) and hit-testing directly.
|
||||
//
|
||||
// Covers (L4 brief §footer test cases):
|
||||
// * Layout: toggle + count + Tail placed left-to-right in the specified order without
|
||||
// overlap, starting at leftPad, vertically inset; the narrow toggle sits at the LEFT.
|
||||
// * The group stays clear of the reserved RIGHT region (prune + version) — nothing crosses
|
||||
// footer.right - rightReserve.
|
||||
// * Suppression: a too-narrow footer drops later affordances (Tail first, then count),
|
||||
// the toggle surviving longest; a degenerate footer yields an all-empty layout.
|
||||
// * countWidth <= 0 hides the count label and the Tail sits right after the toggle.
|
||||
// * Hit-test: Toggle / Tail returned for in-bounds points, None outside AND on the count
|
||||
// label (a passive readout, never a control); half-open bounds; suppressed box claims none.
|
||||
|
||||
#include "../src/footer_bar.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
using namespace reasampler;
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond) do { if(!(cond)) { \
|
||||
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
|
||||
|
||||
// A spec with round numbers so expected pixels are hand-checkable.
|
||||
static FooterBarSpec roundSpec() {
|
||||
FooterBarSpec s;
|
||||
s.toggleWidth = 130;
|
||||
s.countWidth = 60;
|
||||
s.tailWidth = 130;
|
||||
s.gap = 6;
|
||||
s.leftPad = 8;
|
||||
s.verticalInset = 4;
|
||||
s.rightReserve = 168;
|
||||
return s;
|
||||
}
|
||||
|
||||
// --- Layout: wide footer, all three placed, left-to-right ---------------------
|
||||
|
||||
// A wide footer fits all three. Verify order, gaps, no overlap, vertical inset, and that the
|
||||
// Tail (last) stays clear of the reserved right region.
|
||||
static void testWideFooterAllPlaced() {
|
||||
const FooterBarSpec s = roundSpec();
|
||||
// Need room for toggle(130)+gap(6)+count(60)+gap(6)+tail(130) = 332 from leftPad(8) => 340,
|
||||
// plus rightReserve(168). Width 700 is comfortable.
|
||||
FooterRect f{0, 100, 700, 26};
|
||||
const FooterBarLayout L = computeFooterBar(f, s);
|
||||
|
||||
const int top = 100 + 4, boxH = 26 - 8; // 104, 18
|
||||
CHECK((L.toggle == FooterBarRect{8, top, 130, boxH}));
|
||||
CHECK((L.count == FooterBarRect{8 + 130 + 6, top, 60, boxH})); // x = 144
|
||||
CHECK((L.tail == FooterBarRect{8 + 130 + 6 + 60 + 6, top, 130, boxH})); // x = 210
|
||||
|
||||
// Order + no overlap: each starts at-or-after the previous end.
|
||||
CHECK(L.count.x >= L.toggle.x + L.toggle.width);
|
||||
CHECK(L.tail.x >= L.count.x + L.count.width);
|
||||
// The whole group clears the reserved right region.
|
||||
CHECK(L.tail.x + L.tail.width <= f.x + f.width - s.rightReserve);
|
||||
}
|
||||
|
||||
// Origin offset honoured: the group anchors to THIS footer's left, not 0.
|
||||
static void testOffsetFooterAnchorsLeft() {
|
||||
const FooterBarSpec s = roundSpec();
|
||||
FooterRect f{20, 200, 700, 26};
|
||||
const FooterBarLayout L = computeFooterBar(f, s);
|
||||
CHECK(L.toggle.x == 20 + 8);
|
||||
CHECK(!L.toggle.empty() && !L.count.empty() && !L.tail.empty());
|
||||
}
|
||||
|
||||
// --- Suppression: narrowing drops later affordances first ---------------------
|
||||
|
||||
// A footer wide enough for toggle + count but not the Tail: Tail suppressed, toggle+count stay.
|
||||
static void testTailSuppressedWhenTight() {
|
||||
const FooterBarSpec s = roundSpec();
|
||||
// Right bound = width - 168. Toggle ends at 8+130=138; count ends at 144+60=204.
|
||||
// Tail would start at 210 and end at 340; need 340 <= width-168 => width >= 508 for Tail.
|
||||
// count needs 204 <= width-168 => width >= 372. Pick width 480: count fits, Tail does not.
|
||||
FooterRect f{0, 0, 480, 26};
|
||||
const FooterBarLayout L = computeFooterBar(f, s);
|
||||
CHECK(!L.toggle.empty());
|
||||
CHECK(!L.count.empty());
|
||||
CHECK(L.tail.empty());
|
||||
}
|
||||
|
||||
// Narrower still: only the toggle survives (count and Tail both drop). When the count does not
|
||||
// fit, the Tail is offered the count's slot — but if the Tail also does not fit there, both are
|
||||
// empty and only the toggle remains.
|
||||
static void testOnlyToggleSurvives() {
|
||||
const FooterBarSpec s = roundSpec();
|
||||
// Toggle needs 138 <= width-168 => width >= 306. count needs width >= 372.
|
||||
// At the count's slot (x=144) the Tail (130) would end at 274; needs 274 <= width-168 =>
|
||||
// width >= 442. Pick width 330: toggle fits (306), count does not (372), Tail-at-count-slot
|
||||
// does not (442) -> only toggle.
|
||||
FooterRect f{0, 0, 330, 26};
|
||||
const FooterBarLayout L = computeFooterBar(f, s);
|
||||
CHECK(!L.toggle.empty());
|
||||
CHECK(L.count.empty());
|
||||
CHECK(L.tail.empty());
|
||||
}
|
||||
|
||||
// When the count does NOT fit but the Tail WOULD fit at the count's slot, the Tail takes it
|
||||
// (a passive label yields to the interactive button). Right after the toggle, no count gap.
|
||||
static void testTailTakesCountSlotWhenCountDrops() {
|
||||
const FooterBarSpec s = roundSpec();
|
||||
// Want: count does not fit, Tail-at-count-slot fits. count fits at width >= 372.
|
||||
// Tail at count slot x=144, ends 274, fits at width >= 442. Choose width 420:
|
||||
// 372 > 420? no — count fits at 420. Need width in [442-eps? ] hmm: count fits iff
|
||||
// width>=372, Tail-at-slot fits iff width>=442. For "count drops but tail-at-slot fits"
|
||||
// we need width < 372 AND width >= 442 — impossible with countWidth==tailWidth region.
|
||||
// So shrink countWidth to make its slot further right than tail's need. Use a spec where
|
||||
// countWidth is large so it drops before a smaller Tail.
|
||||
FooterBarSpec s2 = s;
|
||||
s2.countWidth = 200; // large passive label
|
||||
s2.tailWidth = 80; // small button
|
||||
// toggle ends 138. count slot x = 144, count ends 344; fits iff 344 <= width-168 =>
|
||||
// width >= 512. Tail at count slot x=144 ends 224; fits iff width >= 392.
|
||||
// Choose width 420: count drops (needs 512), Tail-at-slot fits (needs 392).
|
||||
FooterRect f{0, 0, 420, 26};
|
||||
const FooterBarLayout L = computeFooterBar(f, s2);
|
||||
CHECK(!L.toggle.empty());
|
||||
CHECK(L.count.empty());
|
||||
CHECK(!L.tail.empty());
|
||||
// Tail sits at the count's slot (right after the toggle + gap), NOT past a phantom count.
|
||||
CHECK(L.tail.x == L.toggle.x + L.toggle.width + s2.gap);
|
||||
}
|
||||
|
||||
// A degenerate footer yields an all-empty layout (nothing placed, no crash).
|
||||
static void testDegenerateFooterEmpty() {
|
||||
const FooterBarSpec s = roundSpec();
|
||||
const FooterBarLayout a = computeFooterBar(FooterRect{0, 0, 0, 26}, s);
|
||||
CHECK(a.toggle.empty() && a.count.empty() && a.tail.empty());
|
||||
const FooterBarLayout b = computeFooterBar(FooterRect{0, 0, 700, 0}, s);
|
||||
CHECK(b.toggle.empty() && b.count.empty() && b.tail.empty());
|
||||
// Height <= 2*verticalInset -> boxH <= 0 -> all empty.
|
||||
const FooterBarLayout c = computeFooterBar(FooterRect{0, 0, 700, 8}, s);
|
||||
CHECK(c.toggle.empty() && c.count.empty() && c.tail.empty());
|
||||
}
|
||||
|
||||
// A footer too narrow even for the toggle: all empty (toggle drops last, so nothing survives).
|
||||
static void testTooNarrowForToggle() {
|
||||
const FooterBarSpec s = roundSpec();
|
||||
// Toggle fits iff 8+130 <= width-168 => width >= 306. Width 200 suppresses everything.
|
||||
const FooterBarLayout L = computeFooterBar(FooterRect{0, 0, 200, 26}, s);
|
||||
CHECK(L.toggle.empty() && L.count.empty() && L.tail.empty());
|
||||
}
|
||||
|
||||
// countWidth <= 0 hides the count label and the Tail sits right after the toggle (no count gap).
|
||||
static void testCountHiddenByZeroWidth() {
|
||||
FooterBarSpec s = roundSpec();
|
||||
s.countWidth = 0;
|
||||
FooterRect f{0, 0, 700, 26};
|
||||
const FooterBarLayout L = computeFooterBar(f, s);
|
||||
CHECK(!L.toggle.empty());
|
||||
CHECK(L.count.empty());
|
||||
CHECK(!L.tail.empty());
|
||||
CHECK(L.tail.x == L.toggle.x + L.toggle.width + s.gap); // right after the toggle
|
||||
}
|
||||
|
||||
// --- Hit-test -----------------------------------------------------------------
|
||||
|
||||
static void testHitToggleAndTail() {
|
||||
const FooterBarSpec s = roundSpec();
|
||||
FooterRect f{0, 100, 700, 26};
|
||||
const FooterBarLayout L = computeFooterBar(f, s);
|
||||
|
||||
// Toggle interior.
|
||||
CHECK(hitTestFooterBar(L.toggle.x, L.toggle.y, L) == FooterHit::Toggle);
|
||||
CHECK(hitTestFooterBar(L.toggle.x + L.toggle.width - 1,
|
||||
L.toggle.y + L.toggle.height - 1, L) == FooterHit::Toggle);
|
||||
// Tail interior.
|
||||
CHECK(hitTestFooterBar(L.tail.x + L.tail.width / 2,
|
||||
L.tail.y + L.tail.height / 2, L) == FooterHit::Tail);
|
||||
// The count label is a passive readout — never a hit target.
|
||||
CHECK(hitTestFooterBar(L.count.x + L.count.width / 2,
|
||||
L.count.y + L.count.height / 2, L) == FooterHit::None);
|
||||
// Between the toggle and the count (the gap) is a clean miss.
|
||||
CHECK(hitTestFooterBar(L.toggle.x + L.toggle.width, L.toggle.y, L) == FooterHit::None);
|
||||
}
|
||||
|
||||
// Half-open bounds: far edges excluded; points outside every box miss.
|
||||
static void testHitEdgesAndOutside() {
|
||||
const FooterBarSpec s = roundSpec();
|
||||
FooterRect f{0, 100, 700, 26};
|
||||
const FooterBarLayout L = computeFooterBar(f, s);
|
||||
CHECK(hitTestFooterBar(L.toggle.x - 1, L.toggle.y, L) == FooterHit::None);
|
||||
CHECK(hitTestFooterBar(L.tail.x + L.tail.width, L.tail.y, L) == FooterHit::None); // right edge excl
|
||||
CHECK(hitTestFooterBar(L.toggle.x, L.toggle.y - 1, L) == FooterHit::None); // above
|
||||
CHECK(hitTestFooterBar(L.toggle.x, L.toggle.y + L.toggle.height, L) == FooterHit::None); // below excl
|
||||
// Far right (the prune/version region) is not this module's — a clean None here.
|
||||
CHECK(hitTestFooterBar(f.x + f.width - 10, L.toggle.y, L) == FooterHit::None);
|
||||
}
|
||||
|
||||
// A suppressed box claims no point — a Tail hit-test where the Tail was dropped is None.
|
||||
static void testSuppressedClaimsNothing() {
|
||||
const FooterBarSpec s = roundSpec();
|
||||
FooterRect f{0, 0, 480, 26}; // Tail suppressed (see testTailSuppressedWhenTight)
|
||||
const FooterBarLayout L = computeFooterBar(f, s);
|
||||
CHECK(L.tail.empty());
|
||||
// A point where the Tail WOULD have been claims nothing.
|
||||
CHECK(hitTestFooterBar(210 + 10, 104, L) != FooterHit::Tail);
|
||||
}
|
||||
|
||||
// --- Resize sweep: no overlap or cross-into-reserved across a width range -----
|
||||
|
||||
static void testResizeSweepNoOverlap() {
|
||||
const FooterBarSpec s = roundSpec();
|
||||
const int rightBoundFor = -s.rightReserve; // + width applied per iteration
|
||||
for (int w = 40; w <= 1200; w += 7) {
|
||||
FooterRect f{0, 0, w, 26};
|
||||
const FooterBarLayout L = computeFooterBar(f, s);
|
||||
const int rightBound = f.x + w + rightBoundFor; // = w - rightReserve
|
||||
// Order + no overlap among placed boxes.
|
||||
if (!L.toggle.empty() && !L.count.empty())
|
||||
CHECK(L.count.x >= L.toggle.x + L.toggle.width);
|
||||
if (!L.count.empty() && !L.tail.empty())
|
||||
CHECK(L.tail.x >= L.count.x + L.count.width);
|
||||
if (!L.toggle.empty() && L.count.empty() && !L.tail.empty())
|
||||
CHECK(L.tail.x >= L.toggle.x + L.toggle.width);
|
||||
// Nothing placed crosses into the reserved right region.
|
||||
if (!L.toggle.empty()) CHECK(L.toggle.x + L.toggle.width <= rightBound);
|
||||
if (!L.count.empty()) CHECK(L.count.x + L.count.width <= rightBound);
|
||||
if (!L.tail.empty()) CHECK(L.tail.x + L.tail.width <= rightBound);
|
||||
// A later affordance is never placed while an earlier one is dropped (greedy order):
|
||||
// count/tail present implies toggle present.
|
||||
if (!L.count.empty() || !L.tail.empty()) CHECK(!L.toggle.empty());
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
testWideFooterAllPlaced();
|
||||
testOffsetFooterAnchorsLeft();
|
||||
testTailSuppressedWhenTight();
|
||||
testOnlyToggleSurvives();
|
||||
testTailTakesCountSlotWhenCountDrops();
|
||||
testDegenerateFooterEmpty();
|
||||
testTooNarrowForToggle();
|
||||
testCountHiddenByZeroWidth();
|
||||
testHitToggleAndTail();
|
||||
testHitEdgesAndOutside();
|
||||
testSuppressedClaimsNothing();
|
||||
testResizeSweepNoOverlap();
|
||||
|
||||
if (g_fail == 0) std::printf("footer_bar: all tests passed\n");
|
||||
else std::printf("footer_bar: %d CHECK(s) FAILED\n", g_fail);
|
||||
return g_fail == 0 ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user