Q-W1 pt2: core/shell/app relocation + sub-namespaces; one concrete ui::Rect (LTRB fork retired); slot_map split from bank_book; BankIndex→BankModel; 59/59 green
This commit is contained in:
+64
-63
@@ -1,4 +1,4 @@
|
||||
// Standalone tests for reasampler::vst::param_slider — no VST3, no REAPER, no framework.
|
||||
// Standalone tests for reasampler::instrument::ui::param_slider — no VST3, no REAPER, no framework.
|
||||
// Same fast assert loop as the sibling pure editor tests (capture_browser / keyboard_strip):
|
||||
// assert the S12/S15/S16 control-surface layout, toggle-segment split + hit-test, slider
|
||||
// value<->pixel mapping (round-trip + clamping + endpoints), and point->control routing.
|
||||
@@ -16,13 +16,14 @@
|
||||
// o'clock), wrap-boundary + un-normalized arc inputs, the needle endpoint on the circle, and
|
||||
// the vertical-drag delta->value map (up = increase) with clamping at 0/1.
|
||||
|
||||
#include "../src/vst/param_slider.h"
|
||||
#include "../src/core/instrument/ui/param_slider.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
|
||||
using namespace reasampler::vst;
|
||||
using namespace reasampler;
|
||||
using namespace reasampler::instrument::ui;
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond) do { if(!(cond)) { \
|
||||
@@ -33,7 +34,7 @@ static bool approx(double a, double b) { return (a - b) < 1e-9 && (b - a) < 1e-9
|
||||
// --- layoutControls -----------------------------------------------------------
|
||||
|
||||
static void testLayoutStacksRows() {
|
||||
const Rect panel{0, 100, 300, 400};
|
||||
const Rect panel = Rect::ltrb(0, 100, 300, 400);
|
||||
std::vector<ControlDesc> ctl{
|
||||
{1, ControlKind::Toggle},
|
||||
{2, ControlKind::Slider},
|
||||
@@ -42,58 +43,58 @@ static void testLayoutStacksRows() {
|
||||
const std::vector<ControlRow> rows = layoutControls(panel, ctl);
|
||||
CHECK(rows.size() == 3);
|
||||
// Row 0 sits at the panel top; each subsequent row is one row-height + gap below.
|
||||
CHECK(rows[0].row.top == 100);
|
||||
CHECK(rows[0].row.bottom == 100 + kControlRowHeight);
|
||||
CHECK(rows[1].row.top == rows[0].row.bottom + kControlRowGap);
|
||||
CHECK(rows[2].row.top == rows[1].row.bottom + kControlRowGap);
|
||||
CHECK(rows[0].row.y == 100);
|
||||
CHECK(rows[0].row.bottom() == 100 + kControlRowHeight);
|
||||
CHECK(rows[1].row.y == rows[0].row.bottom() + kControlRowGap);
|
||||
CHECK(rows[2].row.y == rows[1].row.bottom() + kControlRowGap);
|
||||
// Ids + kinds carried through in order.
|
||||
CHECK(rows[0].id == 1 && rows[0].kind == ControlKind::Toggle);
|
||||
CHECK(rows[1].id == 2 && rows[1].kind == ControlKind::Slider);
|
||||
// Label column then control column, contiguous, spanning the panel width.
|
||||
CHECK(rows[0].label.left == panel.left);
|
||||
CHECK(rows[0].control.left == rows[0].label.right);
|
||||
CHECK(rows[0].control.right == panel.right);
|
||||
CHECK(rows[0].label.width() == kControlLabelWidth);
|
||||
CHECK(rows[0].label.x == panel.x);
|
||||
CHECK(rows[0].control.x == rows[0].label.right());
|
||||
CHECK(rows[0].control.right() == panel.right());
|
||||
CHECK(rows[0].label.width == kControlLabelWidth);
|
||||
}
|
||||
|
||||
static void testLayoutEmptyAndDegenerate() {
|
||||
CHECK(layoutControls(Rect{0, 0, 300, 300}, {}).empty());
|
||||
CHECK(layoutControls(Rect::ltrb(0, 0, 300, 300), {}).empty());
|
||||
std::vector<ControlDesc> ctl{{1, ControlKind::Slider}};
|
||||
CHECK(layoutControls(Rect{0, 0, 0, 0}, ctl).empty());
|
||||
CHECK(layoutControls(Rect{0, 0, 300, 0}, ctl).empty());
|
||||
CHECK(layoutControls(Rect::ltrb(0, 0, 0, 0), ctl).empty());
|
||||
CHECK(layoutControls(Rect::ltrb(0, 0, 300, 0), ctl).empty());
|
||||
}
|
||||
|
||||
static void testLayoutNarrowPanelClampsLabel() {
|
||||
// A panel narrower than 2*labelWidth clamps the label column to half so a control column
|
||||
// survives.
|
||||
const Rect panel{0, 0, 100, 200};
|
||||
const Rect panel = Rect::ltrb(0, 0, 100, 200);
|
||||
const std::vector<ControlRow> rows = layoutControls(panel, {{1, ControlKind::Slider}});
|
||||
CHECK(rows.size() == 1);
|
||||
CHECK(rows[0].label.width() <= panel.width() / 2 + 1);
|
||||
CHECK(rows[0].control.width() > 0);
|
||||
CHECK(rows[0].label.width <= panel.width / 2 + 1);
|
||||
CHECK(rows[0].control.width > 0);
|
||||
}
|
||||
|
||||
// --- toggle -------------------------------------------------------------------
|
||||
|
||||
static void testToggleSegmentsTile() {
|
||||
const Rect control{100, 0, 300, 22}; // width 200
|
||||
const Rect control = Rect::ltrb(100, 0, 300, 22); // width 200
|
||||
const Rect s0 = toggleSegmentRect(control, 0);
|
||||
const Rect s1 = toggleSegmentRect(control, 1);
|
||||
CHECK(s0.left == 100 && s0.right == 200);
|
||||
CHECK(s1.left == 200 && s1.right == 300); // last absorbs remainder -> reaches control.right
|
||||
CHECK(s0.x == 100 && s0.right() == 200);
|
||||
CHECK(s1.x == 200 && s1.right() == 300); // last absorbs remainder -> reaches control.right()
|
||||
// Out of range.
|
||||
CHECK(toggleSegmentRect(control, 2).width() == 0);
|
||||
CHECK(toggleSegmentRect(control, -1).width() == 0);
|
||||
CHECK(toggleSegmentRect(control, 2).width == 0);
|
||||
CHECK(toggleSegmentRect(control, -1).width == 0);
|
||||
}
|
||||
|
||||
static void testToggleSegmentRemainderInLast() {
|
||||
const Rect control{0, 0, 201, 22}; // odd width -> seg0 = 100, seg1 = 101 (absorbs remainder)
|
||||
CHECK(toggleSegmentRect(control, 0).width() == 100);
|
||||
CHECK(toggleSegmentRect(control, 1).right == 201);
|
||||
const Rect control = Rect::ltrb(0, 0, 201, 22); // odd width -> seg0 = 100, seg1 = 101 (absorbs remainder)
|
||||
CHECK(toggleSegmentRect(control, 0).width == 100);
|
||||
CHECK(toggleSegmentRect(control, 1).right() == 201);
|
||||
}
|
||||
|
||||
static void testToggleHitTest() {
|
||||
const Rect control{100, 0, 300, 22};
|
||||
const Rect control = Rect::ltrb(100, 0, 300, 22);
|
||||
CHECK(toggleSegmentHitTest(control, 150, 10) == 0);
|
||||
CHECK(toggleSegmentHitTest(control, 250, 10) == 1);
|
||||
CHECK(toggleSegmentHitTest(control, 50, 10) == -1); // left of control
|
||||
@@ -103,59 +104,59 @@ static void testToggleHitTest() {
|
||||
// --- slider -------------------------------------------------------------------
|
||||
|
||||
static void testSliderTrackInsetsHalfHandle() {
|
||||
const Rect control{100, 0, 300, 22};
|
||||
const Rect control = Rect::ltrb(100, 0, 300, 22);
|
||||
const Rect track = sliderTrackRect(control);
|
||||
CHECK(track.left == control.left + kSliderHandleWidth / 2);
|
||||
CHECK(track.right == control.right - kSliderHandleWidth / 2);
|
||||
CHECK(track.x == control.x + kSliderHandleWidth / 2);
|
||||
CHECK(track.right() == control.right() - kSliderHandleWidth / 2);
|
||||
// A control too narrow for a handle yields an empty track.
|
||||
CHECK(sliderTrackRect(Rect{0, 0, kSliderHandleWidth - 1, 22}).width() == 0);
|
||||
CHECK(sliderTrackRect(Rect::ltrb(0, 0, kSliderHandleWidth - 1, 22)).width == 0);
|
||||
}
|
||||
|
||||
static void testSliderHandleAtEndpointsAndMid() {
|
||||
const Rect control{100, 0, 300, 22};
|
||||
const Rect control = Rect::ltrb(100, 0, 300, 22);
|
||||
const Rect track = sliderTrackRect(control);
|
||||
const int half = kSliderHandleWidth / 2;
|
||||
// Value 0 -> handle centered at track.left.
|
||||
// Value 0 -> handle centered at track.x.
|
||||
const Rect h0 = sliderHandleRect(control, 0.0);
|
||||
CHECK(h0.left + half == track.left);
|
||||
// Value 1 -> handle centered at track.right.
|
||||
CHECK(h0.x + half == track.x);
|
||||
// Value 1 -> handle centered at track.right().
|
||||
const Rect h1 = sliderHandleRect(control, 1.0);
|
||||
CHECK(h1.left + half == track.right);
|
||||
CHECK(h1.x + half == track.right());
|
||||
// Value 0.5 -> centered at the track middle.
|
||||
const Rect hm = sliderHandleRect(control, 0.5);
|
||||
CHECK(hm.left + half == track.left + track.width() / 2);
|
||||
CHECK(hm.x + half == track.x + track.width / 2);
|
||||
}
|
||||
|
||||
static void testSliderHandleClampsOutOfRange() {
|
||||
const Rect control{0, 0, 200, 22};
|
||||
CHECK(sliderHandleRect(control, -0.5).left == sliderHandleRect(control, 0.0).left);
|
||||
CHECK(sliderHandleRect(control, 5.0).left == sliderHandleRect(control, 1.0).left);
|
||||
const Rect control = Rect::ltrb(0, 0, 200, 22);
|
||||
CHECK(sliderHandleRect(control, -0.5).x == sliderHandleRect(control, 0.0).x);
|
||||
CHECK(sliderHandleRect(control, 5.0).x == sliderHandleRect(control, 1.0).x);
|
||||
}
|
||||
|
||||
static void testValueAtPointEndpointsSaturate() {
|
||||
const Rect control{100, 0, 300, 22};
|
||||
const Rect control = Rect::ltrb(100, 0, 300, 22);
|
||||
const Rect track = sliderTrackRect(control);
|
||||
CHECK(approx(valueAtPoint(control, track.left - 20), 0.0));
|
||||
CHECK(approx(valueAtPoint(control, track.left), 0.0));
|
||||
CHECK(approx(valueAtPoint(control, track.right + 20), 1.0));
|
||||
CHECK(approx(valueAtPoint(control, track.right), 1.0));
|
||||
CHECK(approx(valueAtPoint(control, track.x - 20), 0.0));
|
||||
CHECK(approx(valueAtPoint(control, track.x), 0.0));
|
||||
CHECK(approx(valueAtPoint(control, track.right() + 20), 1.0));
|
||||
CHECK(approx(valueAtPoint(control, track.right()), 1.0));
|
||||
}
|
||||
|
||||
static void testValueAtPointIsHandleInverse() {
|
||||
// Round-trip: a value -> handle center -> valueAtPoint recovers (within one pixel quantum).
|
||||
const Rect control{50, 0, 450, 22}; // wide track for pixel resolution
|
||||
const Rect control = Rect::ltrb(50, 0, 450, 22); // wide track for pixel resolution
|
||||
const Rect track = sliderTrackRect(control);
|
||||
for (double v : {0.1, 0.25, 0.5, 0.75, 0.9}) {
|
||||
const Rect h = sliderHandleRect(control, v);
|
||||
const int centerX = h.left + kSliderHandleWidth / 2;
|
||||
const int centerX = h.x + kSliderHandleWidth / 2;
|
||||
const double back = valueAtPoint(control, centerX);
|
||||
CHECK(back >= v - 0.01 && back <= v + 0.01);
|
||||
CHECK(centerX >= track.left && centerX <= track.right);
|
||||
CHECK(centerX >= track.x && centerX <= track.right());
|
||||
}
|
||||
}
|
||||
|
||||
static void testValueAtPointDegenerateTrack() {
|
||||
CHECK(approx(valueAtPoint(Rect{0, 0, kSliderHandleWidth - 1, 22}, 5), 0.0));
|
||||
CHECK(approx(valueAtPoint(Rect::ltrb(0, 0, kSliderHandleWidth - 1, 22), 5), 0.0));
|
||||
}
|
||||
|
||||
// --- knob (FA4) -----------------------------------------------------------------
|
||||
@@ -164,21 +165,21 @@ static bool nearWithin(double a, double b, double tol) { return (a - b) < tol &&
|
||||
|
||||
static void testKnobGeometryInscribesCell() {
|
||||
// A 44x44 cell at (100,0): center (122,22), radius 22.
|
||||
const KnobGeometry g = computeKnob(Rect{100, 0, 144, 44});
|
||||
const KnobGeometry g = computeKnob(Rect::ltrb(100, 0, 144, 44));
|
||||
CHECK(approx(g.centerX, 122.0));
|
||||
CHECK(approx(g.centerY, 22.0));
|
||||
CHECK(approx(g.radius, 22.0));
|
||||
// A wide cell inscribes on the smaller (vertical) dimension.
|
||||
const KnobGeometry w = computeKnob(Rect{0, 0, 200, 22});
|
||||
const KnobGeometry w = computeKnob(Rect::ltrb(0, 0, 200, 22));
|
||||
CHECK(approx(w.radius, 11.0));
|
||||
CHECK(approx(w.centerX, 100.0));
|
||||
// Degenerate cells yield radius 0.
|
||||
CHECK(computeKnob(Rect{0, 0, 0, 22}).radius == 0.0);
|
||||
CHECK(computeKnob(Rect{0, 0, 22, 0}).radius == 0.0);
|
||||
CHECK(computeKnob(Rect::ltrb(0, 0, 0, 22)).radius == 0.0);
|
||||
CHECK(computeKnob(Rect::ltrb(0, 0, 22, 0)).radius == 0.0);
|
||||
}
|
||||
|
||||
static void testKnobHitTestCircle() {
|
||||
const KnobGeometry g = computeKnob(Rect{100, 0, 144, 44}); // center (122,22), r 22
|
||||
const KnobGeometry g = computeKnob(Rect::ltrb(100, 0, 144, 44)); // center (122,22), r 22
|
||||
CHECK(knobHitTest(g, 122, 22)); // center — always hits
|
||||
CHECK(!knobHitTest(g, 122 + 22, 22)); // exactly on the boundary — boundary exclusive
|
||||
CHECK(!knobHitTest(g, 122 + 22, 44)); // cell corner: inside the rect, outside the circle
|
||||
@@ -223,7 +224,7 @@ static void testKnobArcWrapBoundary() {
|
||||
}
|
||||
|
||||
static void testKnobNeedlePointOnCircle() {
|
||||
const KnobGeometry g = computeKnob(Rect{100, 0, 144, 44}); // center (122,22), r 22
|
||||
const KnobGeometry g = computeKnob(Rect::ltrb(100, 0, 144, 44)); // center (122,22), r 22
|
||||
// Default arc, value 0 -> 7 o'clock -> needle points down-left from center.
|
||||
const KnobPoint p7 = knobNeedlePoint(g, KnobArc{}, 0.0);
|
||||
// 210° clockwise from 12: sin(210°)=-0.5, cos(210°)=-√3/2 -> x = cx - r/2, y = cy + r*√3/2
|
||||
@@ -265,7 +266,7 @@ static void testKnobDragClamps() {
|
||||
// --- controlAtPoint routing ---------------------------------------------------
|
||||
|
||||
static void testControlAtPointRoutes() {
|
||||
const Rect panel{0, 0, 300, 400};
|
||||
const Rect panel = Rect::ltrb(0, 0, 300, 400);
|
||||
std::vector<ControlDesc> ctl{
|
||||
{10, ControlKind::Toggle},
|
||||
{20, ControlKind::Slider},
|
||||
@@ -274,26 +275,26 @@ static void testControlAtPointRoutes() {
|
||||
const std::vector<ControlRow> rows = layoutControls(panel, ctl);
|
||||
// A point in the toggle's control area routes to the toggle id.
|
||||
const Rect tctl = rows[0].control;
|
||||
CHECK(controlAtPoint(rows, (tctl.left + tctl.right) / 2, (tctl.top + tctl.bottom) / 2) == 10);
|
||||
CHECK(controlAtPoint(rows, (tctl.x + tctl.right()) / 2, (tctl.y + tctl.bottom()) / 2) == 10);
|
||||
// A point on the slider's track routes to the slider id.
|
||||
const Rect strack = sliderTrackRect(rows[1].control);
|
||||
CHECK(controlAtPoint(rows, (strack.left + strack.right) / 2,
|
||||
(strack.top + strack.bottom) / 2) == 20);
|
||||
CHECK(controlAtPoint(rows, (strack.x + strack.right()) / 2,
|
||||
(strack.y + strack.bottom()) / 2) == 20);
|
||||
// A point at the knob's center routes to the knob id; the control-rect corner (outside
|
||||
// the circle) is a miss.
|
||||
const KnobGeometry kg = computeKnob(rows[2].control);
|
||||
CHECK(controlAtPoint(rows, static_cast<int>(kg.centerX), static_cast<int>(kg.centerY)) == 30);
|
||||
CHECK(controlAtPoint(rows, rows[2].control.left + 1, rows[2].control.top + 1) == -1);
|
||||
CHECK(controlAtPoint(rows, rows[2].control.x + 1, rows[2].control.y + 1) == -1);
|
||||
}
|
||||
|
||||
static void testControlAtPointMisses() {
|
||||
const Rect panel{0, 0, 300, 400};
|
||||
const Rect panel = Rect::ltrb(0, 0, 300, 400);
|
||||
const std::vector<ControlRow> rows =
|
||||
layoutControls(panel, {{10, ControlKind::Toggle}, {20, ControlKind::Slider}});
|
||||
// The label column is not interactive.
|
||||
CHECK(controlAtPoint(rows, rows[0].label.left + 2, rows[0].label.top + 4) == -1);
|
||||
CHECK(controlAtPoint(rows, rows[0].label.x + 2, rows[0].label.y + 4) == -1);
|
||||
// The gap between rows is a miss.
|
||||
const int gapY = rows[0].row.bottom + kControlRowGap / 2;
|
||||
const int gapY = rows[0].row.bottom() + kControlRowGap / 2;
|
||||
CHECK(controlAtPoint(rows, 200, gapY) == -1);
|
||||
// Off-panel below.
|
||||
CHECK(controlAtPoint(rows, 200, 5000) == -1);
|
||||
|
||||
Reference in New Issue
Block a user