// Standalone tests for reasampler::instrument::ui::curve_popup — no VST3, no REAPER, no framework. Same // fast assert loop as the sibling pure tests. Assert the r11 popup-sheet geometry at the size // clamps (the spec's width clamp(60%, 360..520) / height clamp(55%, 260..380)), the centering, // the title-row/close-button placement, the curve-box remainder, and the outside-sheet // dismissal test. #include "../src/core/instrument/ui/curve_popup.h" #include "../src/core/instrument/engine/velocity_curve.h" #include using namespace reasampler; using namespace reasampler::instrument::ui; using reasampler::instrument::engine::CurveDomain; using reasampler::instrument::engine::VelocityCurve; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) static void testDefaultWindowMidClamp() { // 840x620: 60% = 504 (inside 360..520), 55% = 341 (inside 260..380). const CurvePopupLayout pl = computeCurvePopup(840, 620); CHECK(pl.sheet.width == 504); CHECK(pl.sheet.height == 341); // Centered (within the integer-division pixel). CHECK(pl.sheet.x == (840 - 504) / 2); CHECK(pl.sheet.y == (620 - 341) / 2); } static void testMinClamp() { // The 560x460 constraint floor: 60% = 336 -> clamps UP to 360; 55% = 253 -> up to 260. const CurvePopupLayout pl = computeCurvePopup(560, 460); CHECK(pl.sheet.width == kCurvePopupMinW); CHECK(pl.sheet.height == kCurvePopupMinH); CHECK(pl.sheet.x >= 0 && pl.sheet.right() <= 560); CHECK(pl.sheet.y >= 0 && pl.sheet.bottom() <= 460); } static void testMaxClamp() { // A large window: 60% of 1600 = 960 -> clamps DOWN to 520; 55% of 900 = 495 -> down to 380. const CurvePopupLayout pl = computeCurvePopup(1600, 900); CHECK(pl.sheet.width == kCurvePopupMaxW); CHECK(pl.sheet.height == kCurvePopupMaxH); } static void testDegenerateWindowNeverOverhangs() { // A window smaller than the min clamp: the sheet caps at the window dimension (defensive — // below checkSizeConstraint, but geometry must stay sane). const CurvePopupLayout pl = computeCurvePopup(300, 200); CHECK(pl.sheet.width == 300); CHECK(pl.sheet.height == 200); CHECK(pl.sheet.x == 0 && pl.sheet.y == 0); } static void testTitleRowAndCurveBox() { const CurvePopupLayout pl = computeCurvePopup(840, 620); // Close: 18x18, right-anchored inside the title row. CHECK(pl.close.width == kCurvePopupCloseSize && pl.close.height == kCurvePopupCloseSize); CHECK(pl.close.right() == pl.sheet.right() - kCurvePopupPad); CHECK(pl.close.y >= pl.sheet.y); CHECK(pl.close.bottom() <= pl.sheet.y + kCurvePopupTitleH); // Title text: left of the close button, in the title row. CHECK(pl.title.x == pl.sheet.x + kCurvePopupPad); CHECK(pl.title.right() <= pl.close.x); // Curve box: fills the remainder below the title row, inside the sheet margins. CHECK(pl.curveBox.y >= pl.sheet.y + kCurvePopupTitleH); CHECK(pl.curveBox.x == pl.sheet.x + kCurvePopupPad); CHECK(pl.curveBox.right() == pl.sheet.right() - kCurvePopupPad); CHECK(pl.curveBox.bottom() == pl.sheet.bottom() - kCurvePopupPad); CHECK(pl.curveBox.width > 0 && pl.curveBox.height > 0); } static void testOutsideSheetDismissTest() { const CurvePopupLayout pl = computeCurvePopup(840, 620); // On the wash: outside. CHECK(popupOutsideSheet(pl, 0, 0)); CHECK(popupOutsideSheet(pl, pl.sheet.x - 1, pl.sheet.y + 10)); CHECK(popupOutsideSheet(pl, pl.sheet.right(), pl.sheet.y + 10)); // half-open right edge // On the sheet (title row, curve box, padding): inside. CHECK(!popupOutsideSheet(pl, pl.sheet.x, pl.sheet.y)); CHECK(!popupOutsideSheet(pl, pl.curveBox.x + 5, pl.curveBox.y + 5)); CHECK(!popupOutsideSheet(pl, pl.sheet.right() - 1, pl.sheet.bottom() - 1)); } // The sheet hosts all three curves, and its box is domain-agnostic: the SAME curveBox drives a // unipolar and a bipolar editor, and only the curve's own y map differs. Asserted here, against // the popup's real geometry, because that is what makes one popup code path legitimate. static void testTheSameCurveBoxHostsBothDomains() { const CurvePopupLayout pl = computeCurvePopup(840, 620); // The shell insets this rect before mapping; the inset is uniform, so any box inside the // curveBox exercises the same relationship. Use the rect itself. const VelocityCurve::Box box{pl.curveBox.x, pl.curveBox.y, pl.curveBox.width, pl.curveBox.height}; CHECK(box.width > 1 && box.height > 1); const VelocityCurve amp = VelocityCurve::flat(); const VelocityCurve mod = VelocityCurve::zero(); const int top = box.top; const int bottom = box.top + box.height - 1; // Both domains put their MAX on the top row and their MIN on the bottom row... CHECK(amp.pixelFromPoint(box, {0.0, 1.0}).y == top); CHECK(amp.pixelFromPoint(box, {0.0, 0.0}).y == bottom); CHECK(mod.pixelFromPoint(box, {0.0, 1.0}).y == top); CHECK(mod.pixelFromPoint(box, {0.0, -1.0}).y == bottom); // ...so value 0 is the FLOOR for the amp curve and the MIDLINE for a modulation curve. CHECK(mod.pixelFromPoint(box, {0.0, 0.0}).y == (top + bottom) / 2); // And a click at the vertical centre adds a point at 0 in the bipolar editor, at 0.5 in // the unipolar one — one hit-test path, two correct answers. const int midY = (top + bottom) / 2; CHECK(mod.pointFromPixel(box, box.left, midY).value == 0.0); CHECK(amp.pointFromPixel(box, box.left, midY).value > 0.49); CHECK(amp.pointFromPixel(box, box.left, midY).value < 0.51); } int main() { testTheSameCurveBoxHostsBothDomains(); testDefaultWindowMidClamp(); testMinClamp(); testMaxClamp(); testDegenerateWindowNeverOverhangs(); testTitleRowAndCurveBox(); testOutsideSheetDismissTest(); if (g_fail) { std::printf("%d FAILURE(S)\n", g_fail); return 1; } std::printf("curve_popup tests passed\n"); return 0; }