S12: editor scale + S15/S16 control surfaces
Pure browser_scroll/note_entry/param_slider modules (+ CTest) for scroll, type-to-filter search, numeric note entry, and the AHDSR/Trigger/pitch-engine/ pitch-env control panel. Editor shell draws + routes through them; params edit the selected zone's ZonePlayParams via commitAndReload.
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
// Standalone tests for reasampler::vst::browser_scroll — no VST3, no REAPER, no framework.
|
||||
// Same fast assert loop as the sibling pure editor tests: assert the S12 scroll-window +
|
||||
// scrollbar-thumb + type-to-filter-search geometry LAYERED over the S10 capture_browser.
|
||||
//
|
||||
// Covers: scrollContentHeight (ceil rows * card height, 0 for no cards); scrollMaxOffset (0
|
||||
// when content fits, else content-visible); clampScrollOffset pinning to [0,max]; visibleCardRange
|
||||
// windowing (top rows only, scrolled window, empty when scrolled past the end); scrolledCardCellRect
|
||||
// shifting a cell up by the offset; scrollThumbRect (empty when it fits, proportional height +
|
||||
// position, minimum height, at-max pins to the track bottom); thumbDragToOffset as the position
|
||||
// inverse (a full-track drag reaches max, round-trips); searchBoxRect; nameMatchesQuery
|
||||
// (case-insensitive substring, empty-query identity, no-match); filterNameIndices preserving order
|
||||
// and returning every index for an empty query.
|
||||
|
||||
#include "../src/vst/browser_scroll.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace reasampler::vst;
|
||||
|
||||
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 layout wide enough for a few columns and tall enough to show a few rows.
|
||||
static BrowserLayout wideLayout() { return layoutBrowser(560, 300); }
|
||||
|
||||
// --- content / max / clamp ----------------------------------------------------
|
||||
|
||||
static void testContentHeight() {
|
||||
const BrowserLayout L = wideLayout();
|
||||
CHECK(scrollContentHeight(L, 0) == 0);
|
||||
// One card -> one row -> one card height.
|
||||
CHECK(scrollContentHeight(L, 1) == kBrowserCardHeight);
|
||||
// columns+1 cards -> two rows.
|
||||
CHECK(scrollContentHeight(L, L.columns + 1) == 2 * kBrowserCardHeight);
|
||||
// Exactly `columns` cards -> one row.
|
||||
CHECK(scrollContentHeight(L, L.columns) == kBrowserCardHeight);
|
||||
}
|
||||
|
||||
static void testMaxOffsetFitsAndOverflows() {
|
||||
const BrowserLayout L = wideLayout();
|
||||
// A single row fits within the 300px area -> no scroll.
|
||||
CHECK(scrollMaxOffset(L, L.columns) == 0);
|
||||
// Many rows overflow -> max = content - gridHeight.
|
||||
const int many = L.columns * 20;
|
||||
const int expect = scrollContentHeight(L, many) - L.grid.height();
|
||||
CHECK(scrollMaxOffset(L, many) == expect);
|
||||
CHECK(expect > 0);
|
||||
}
|
||||
|
||||
static void testClamp() {
|
||||
const BrowserLayout L = wideLayout();
|
||||
const int many = L.columns * 20;
|
||||
const int maxOff = scrollMaxOffset(L, many);
|
||||
CHECK(clampScrollOffset(L, many, -50) == 0);
|
||||
CHECK(clampScrollOffset(L, many, maxOff + 500) == maxOff);
|
||||
CHECK(clampScrollOffset(L, many, maxOff / 2) == maxOff / 2);
|
||||
}
|
||||
|
||||
// --- visible window -----------------------------------------------------------
|
||||
|
||||
static void testVisibleRangeTop() {
|
||||
const BrowserLayout L = wideLayout();
|
||||
const int many = L.columns * 20;
|
||||
const VisibleRange vr = visibleCardRange(L, many, 0);
|
||||
CHECK(vr.first == 0);
|
||||
// At offset 0, the last visible row is the one containing (gridH-1).
|
||||
const int expectedLastRow = (L.grid.height() - 1) / kBrowserCardHeight + 1;
|
||||
CHECK(vr.last == expectedLastRow * L.columns);
|
||||
}
|
||||
|
||||
static void testVisibleRangeScrolled() {
|
||||
const BrowserLayout L = wideLayout();
|
||||
const int many = L.columns * 20;
|
||||
// Scroll one full card row down.
|
||||
const VisibleRange vr = visibleCardRange(L, many, kBrowserCardHeight);
|
||||
CHECK(vr.first == L.columns); // the first row scrolled off the top
|
||||
}
|
||||
|
||||
static void testVisibleRangeEmptyWhenNoCards() {
|
||||
const BrowserLayout L = wideLayout();
|
||||
const VisibleRange vr = visibleCardRange(L, 0, 0);
|
||||
CHECK(vr.first == 0 && vr.last == 0);
|
||||
}
|
||||
|
||||
static void testScrolledCellShiftsUp() {
|
||||
const BrowserLayout L = wideLayout();
|
||||
const Rect base = cardCellRect(L, 3);
|
||||
const Rect shifted = scrolledCardCellRect(L, 3, 40);
|
||||
CHECK(shifted.top == base.top - 40);
|
||||
CHECK(shifted.bottom == base.bottom - 40);
|
||||
CHECK(shifted.left == base.left);
|
||||
}
|
||||
|
||||
// --- scrollbar thumb ----------------------------------------------------------
|
||||
|
||||
static void testThumbEmptyWhenFits() {
|
||||
const BrowserLayout L = wideLayout();
|
||||
CHECK(scrollThumbRect(L, L.columns, 0).height() == 0); // one row fits -> no thumb
|
||||
}
|
||||
|
||||
static void testThumbProportionalAndClamped() {
|
||||
const BrowserLayout L = wideLayout();
|
||||
const int many = L.columns * 20;
|
||||
const Rect atTop = scrollThumbRect(L, many, 0);
|
||||
CHECK(atTop.height() > 0);
|
||||
CHECK(atTop.top == L.grid.top); // at offset 0 the thumb starts at the track top
|
||||
CHECK(atTop.width() == kScrollbarWidth);
|
||||
CHECK(atTop.right == L.grid.right);
|
||||
// At max offset, the thumb bottom reaches the grid bottom (pinned to the end).
|
||||
const int maxOff = scrollMaxOffset(L, many);
|
||||
const Rect atMax = scrollThumbRect(L, many, maxOff);
|
||||
CHECK(atMax.bottom == L.grid.top + L.grid.height());
|
||||
}
|
||||
|
||||
static void testThumbDragIsInverse() {
|
||||
const BrowserLayout L = wideLayout();
|
||||
const int many = L.columns * 20;
|
||||
const int maxOff = scrollMaxOffset(L, many);
|
||||
// A zero drag holds the start offset.
|
||||
CHECK(thumbDragToOffset(L, many, 0, 0) == 0);
|
||||
// A large positive drag pins to max; a large negative drag pins to 0.
|
||||
CHECK(thumbDragToOffset(L, many, 0, 100000) == maxOff);
|
||||
CHECK(thumbDragToOffset(L, many, maxOff, -100000) == 0);
|
||||
// Dragging the thumb by the whole track span from top reaches (near) max.
|
||||
const Rect thumb = scrollThumbRect(L, many, 0);
|
||||
const int trackSpan = L.grid.height() - thumb.height();
|
||||
const int off = thumbDragToOffset(L, many, 0, trackSpan);
|
||||
CHECK(off >= maxOff - 2 && off <= maxOff);
|
||||
}
|
||||
|
||||
// --- search -------------------------------------------------------------------
|
||||
|
||||
static void testSearchBoxRect() {
|
||||
const Rect r = searchBoxRect(200);
|
||||
CHECK(r.left == 0 && r.top == 0 && r.right == 200 && r.height() == kSearchBoxHeight);
|
||||
CHECK(searchBoxRect(0).width() == 0);
|
||||
}
|
||||
|
||||
static void testNameMatch() {
|
||||
CHECK(nameMatchesQuery("Kick Drum 01", "")); // empty query matches all
|
||||
CHECK(nameMatchesQuery("Kick Drum 01", "drum")); // case-insensitive substring
|
||||
CHECK(nameMatchesQuery("Kick Drum 01", "KICK"));
|
||||
CHECK(!nameMatchesQuery("Kick Drum 01", "snare"));
|
||||
CHECK(!nameMatchesQuery("ab", "abc")); // query longer than name
|
||||
}
|
||||
|
||||
static void testFilterIndices() {
|
||||
std::vector<std::string> names{"Kick", "Snare", "Kick Sub", "Hat"};
|
||||
// Empty query -> every index, in order.
|
||||
const std::vector<int> all = filterNameIndices(names, "");
|
||||
CHECK(all.size() == 4 && all[0] == 0 && all[3] == 3);
|
||||
// "kick" -> indices 0 and 2, order preserved.
|
||||
const std::vector<int> kicks = filterNameIndices(names, "kick");
|
||||
CHECK(kicks.size() == 2 && kicks[0] == 0 && kicks[1] == 2);
|
||||
// No match -> empty.
|
||||
CHECK(filterNameIndices(names, "zzz").empty());
|
||||
}
|
||||
|
||||
int main() {
|
||||
testContentHeight();
|
||||
testMaxOffsetFitsAndOverflows();
|
||||
testClamp();
|
||||
testVisibleRangeTop();
|
||||
testVisibleRangeScrolled();
|
||||
testVisibleRangeEmptyWhenNoCards();
|
||||
testScrolledCellShiftsUp();
|
||||
testThumbEmptyWhenFits();
|
||||
testThumbProportionalAndClamped();
|
||||
testThumbDragIsInverse();
|
||||
testSearchBoxRect();
|
||||
testNameMatch();
|
||||
testFilterIndices();
|
||||
|
||||
if (g_fail == 0) std::printf("browser_scroll: all tests passed\n");
|
||||
return g_fail != 0;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Standalone tests for reasampler::vst::note_entry — no VST3, no REAPER, no framework.
|
||||
// Assert the S12 direct-numeric-entry parse for a zone's low/high/root MIDI note.
|
||||
//
|
||||
// Covers: plain decimal integers (with +/- sign + surrounding whitespace); note names under the
|
||||
// C4==60 convention (C-1==0, sharps + flats, negative octaves); out-of-range values CLAMPING to
|
||||
// [0,127] rather than rejecting; empty / whitespace-only / unparseable input returning nullopt;
|
||||
// the integer path taking precedence over the note-name path for a leading digit.
|
||||
|
||||
#include "../src/vst/note_entry.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
using namespace reasampler::vst;
|
||||
|
||||
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 testPlainIntegers() {
|
||||
CHECK(parseNoteEntry("60") == 60);
|
||||
CHECK(parseNoteEntry("0") == 0);
|
||||
CHECK(parseNoteEntry("127") == 127);
|
||||
CHECK(parseNoteEntry(" 64 ") == 64); // surrounding whitespace ignored
|
||||
CHECK(parseNoteEntry("+5") == 5);
|
||||
}
|
||||
|
||||
static void testIntegerClamps() {
|
||||
CHECK(parseNoteEntry("200") == 127); // over-range clamps to the ceiling
|
||||
CHECK(parseNoteEntry("-10") == 0); // under-range clamps to the floor
|
||||
CHECK(parseNoteEntry("99999") == 127);
|
||||
}
|
||||
|
||||
static void testNoteNames() {
|
||||
// C4 == 60 (MIDI 0 == C-1).
|
||||
CHECK(parseNoteEntry("C4") == 60);
|
||||
CHECK(parseNoteEntry("c4") == 60); // case-insensitive
|
||||
CHECK(parseNoteEntry("A4") == 69); // A4 = 69 (concert A)
|
||||
CHECK(parseNoteEntry("C-1") == 0); // lowest MIDI note
|
||||
CHECK(parseNoteEntry("G9") == 127); // G9 = 127
|
||||
}
|
||||
|
||||
static void testAccidentals() {
|
||||
CHECK(parseNoteEntry("C#4") == 61);
|
||||
CHECK(parseNoteEntry("Db4") == 61); // enharmonic of C#4
|
||||
CHECK(parseNoteEntry("F#3") == 54);
|
||||
CHECK(parseNoteEntry("Bb3") == 58); // Bb3 = 58
|
||||
}
|
||||
|
||||
static void testNoteNameClamps() {
|
||||
CHECK(parseNoteEntry("C10") == 127); // above the range clamps
|
||||
CHECK(parseNoteEntry("C-5") == 0); // below the range clamps
|
||||
}
|
||||
|
||||
static void testRejects() {
|
||||
CHECK(parseNoteEntry("") == std::nullopt);
|
||||
CHECK(parseNoteEntry(" ") == std::nullopt);
|
||||
CHECK(parseNoteEntry("hello") == std::nullopt);
|
||||
CHECK(parseNoteEntry("C") == std::nullopt); // a bare letter with no octave is ambiguous
|
||||
CHECK(parseNoteEntry("H4") == std::nullopt); // H is not a note letter
|
||||
CHECK(parseNoteEntry("+") == std::nullopt);
|
||||
}
|
||||
|
||||
int main() {
|
||||
testPlainIntegers();
|
||||
testIntegerClamps();
|
||||
testNoteNames();
|
||||
testAccidentals();
|
||||
testNoteNameClamps();
|
||||
testRejects();
|
||||
|
||||
if (g_fail == 0) std::printf("note_entry: all tests passed\n");
|
||||
return g_fail != 0;
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// Standalone tests for reasampler::vst::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.
|
||||
//
|
||||
// Covers: layoutControls stacking rows top-down with the label column + control column and the
|
||||
// inter-row gap; an empty list / degenerate panel yielding nothing; toggleSegmentRect splitting
|
||||
// a toggle into two tiling segments (last absorbs the remainder) + toggleSegmentHitTest;
|
||||
// sliderTrackRect insetting a half-handle at each end; sliderHandleRect at value 0/0.5/1 and
|
||||
// out-of-range clamping; valueAtPoint mapping x back to 0..1 (endpoints saturate) as the inverse
|
||||
// of the handle position; controlAtPoint routing a point to the right control id (toggle whole
|
||||
// area vs slider track) and MISSING in the label column, a row gap, and off-panel.
|
||||
|
||||
#include "../src/vst/param_slider.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
|
||||
using namespace reasampler::vst;
|
||||
|
||||
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 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};
|
||||
std::vector<ControlDesc> ctl{
|
||||
{1, ControlKind::Toggle},
|
||||
{2, ControlKind::Slider},
|
||||
{3, ControlKind::Slider},
|
||||
};
|
||||
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);
|
||||
// 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);
|
||||
}
|
||||
|
||||
static void testLayoutEmptyAndDegenerate() {
|
||||
CHECK(layoutControls(Rect{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());
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
// --- toggle -------------------------------------------------------------------
|
||||
|
||||
static void testToggleSegmentsTile() {
|
||||
const Rect control{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
|
||||
// Out of range.
|
||||
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);
|
||||
}
|
||||
|
||||
static void testToggleHitTest() {
|
||||
const Rect control{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
|
||||
CHECK(toggleSegmentHitTest(control, 150, 40) == -1); // below control
|
||||
}
|
||||
|
||||
// --- slider -------------------------------------------------------------------
|
||||
|
||||
static void testSliderTrackInsetsHalfHandle() {
|
||||
const Rect control{100, 0, 300, 22};
|
||||
const Rect track = sliderTrackRect(control);
|
||||
CHECK(track.left == control.left + 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);
|
||||
}
|
||||
|
||||
static void testSliderHandleAtEndpointsAndMid() {
|
||||
const Rect control{100, 0, 300, 22};
|
||||
const Rect track = sliderTrackRect(control);
|
||||
const int half = kSliderHandleWidth / 2;
|
||||
// Value 0 -> handle centered at track.left.
|
||||
const Rect h0 = sliderHandleRect(control, 0.0);
|
||||
CHECK(h0.left + half == track.left);
|
||||
// Value 1 -> handle centered at track.right.
|
||||
const Rect h1 = sliderHandleRect(control, 1.0);
|
||||
CHECK(h1.left + 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void testValueAtPointEndpointsSaturate() {
|
||||
const Rect control{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));
|
||||
}
|
||||
|
||||
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 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 double back = valueAtPoint(control, centerX);
|
||||
CHECK(back >= v - 0.01 && back <= v + 0.01);
|
||||
CHECK(centerX >= track.left && centerX <= track.right);
|
||||
}
|
||||
}
|
||||
|
||||
static void testValueAtPointDegenerateTrack() {
|
||||
CHECK(approx(valueAtPoint(Rect{0, 0, kSliderHandleWidth - 1, 22}, 5), 0.0));
|
||||
}
|
||||
|
||||
// --- controlAtPoint routing ---------------------------------------------------
|
||||
|
||||
static void testControlAtPointRoutes() {
|
||||
const Rect panel{0, 0, 300, 400};
|
||||
std::vector<ControlDesc> ctl{
|
||||
{10, ControlKind::Toggle},
|
||||
{20, ControlKind::Slider},
|
||||
};
|
||||
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);
|
||||
// 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);
|
||||
}
|
||||
|
||||
static void testControlAtPointMisses() {
|
||||
const Rect panel{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);
|
||||
// The gap between rows is a miss.
|
||||
const int gapY = rows[0].row.bottom + kControlRowGap / 2;
|
||||
CHECK(controlAtPoint(rows, 200, gapY) == -1);
|
||||
// Off-panel below.
|
||||
CHECK(controlAtPoint(rows, 200, 5000) == -1);
|
||||
}
|
||||
|
||||
int main() {
|
||||
testLayoutStacksRows();
|
||||
testLayoutEmptyAndDegenerate();
|
||||
testLayoutNarrowPanelClampsLabel();
|
||||
testToggleSegmentsTile();
|
||||
testToggleSegmentRemainderInLast();
|
||||
testToggleHitTest();
|
||||
testSliderTrackInsetsHalfHandle();
|
||||
testSliderHandleAtEndpointsAndMid();
|
||||
testSliderHandleClampsOutOfRange();
|
||||
testValueAtPointEndpointsSaturate();
|
||||
testValueAtPointIsHandleInverse();
|
||||
testValueAtPointDegenerateTrack();
|
||||
testControlAtPointRoutes();
|
||||
testControlAtPointMisses();
|
||||
|
||||
if (g_fail == 0) std::printf("param_slider: all tests passed\n");
|
||||
return g_fail != 0;
|
||||
}
|
||||
Reference in New Issue
Block a user