190 lines
8.6 KiB
C++
190 lines
8.6 KiB
C++
// Standalone tests for reasampler::instrument::ui::sample_chrome — no VST3, no REAPER, no
|
|
// test framework.
|
|
//
|
|
// Covers: the chrome band's two rows (toolbar over strip row, tiling the band exactly); the
|
|
// toolbar's fixed right-anchored run in order (preview, velocity cell, Mono|Stereo,
|
|
// Browse) with the title taking the remainder; the velocity knob centred in its
|
|
// cell above its label; the piano strip owning its whole row at every width; no rect on the
|
|
// toolbar overlapping any other; degenerate bands yielding no inverted rects; and the preview
|
|
// button's play-triangle glyph, which sits inside the button without changing its rect.
|
|
|
|
#include "../src/core/instrument/ui/sample_bands.h"
|
|
#include "../src/core/instrument/ui/sample_chrome.h"
|
|
|
|
#include <cstdio>
|
|
#include <initializer_list>
|
|
|
|
using namespace reasampler;
|
|
using namespace reasampler::instrument::ui;
|
|
|
|
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 constexpr int kKnob = 40; // stands in for knob_deck's kDeckKnobSize
|
|
|
|
static Rect chromeBand(int w = kEditorMinWidth, int h = kEditorMinHeight) {
|
|
return computeSampleBands(w, h, 120).chrome;
|
|
}
|
|
|
|
// True when the two rects share at least one pixel.
|
|
static bool overlaps(const Rect& a, const Rect& b) {
|
|
if (a.empty() || b.empty()) return false;
|
|
return a.x < b.right() && b.x < a.right() && a.y < b.bottom() && b.y < a.bottom();
|
|
}
|
|
|
|
static void testRowsTileTheBandExactly() {
|
|
const Rect band = chromeBand();
|
|
const ChromeRects r = chromeRects(band, kKnob);
|
|
CHECK(r.toolbar.y == band.y);
|
|
CHECK(r.controls.y == r.toolbar.bottom());
|
|
CHECK(r.controls.bottom() == band.bottom());
|
|
CHECK(r.toolbar.x == band.x && r.toolbar.right() == band.right());
|
|
CHECK(r.controls.x == band.x && r.controls.right() == band.right());
|
|
// The toolbar has to be tall enough for the velocity knob cell it now carries.
|
|
CHECK(r.toolbar.height >= kKnob);
|
|
CHECK(r.controls.height > 0);
|
|
}
|
|
|
|
static void testToolbarRunIsOrderedRightToLeftWithoutOverlap() {
|
|
const Rect band = chromeBand();
|
|
const ChromeRects r = chromeRects(band, kKnob);
|
|
// Rightmost first: Browse, stereo, mono, velocity cell, preview, title.
|
|
CHECK(r.navBrowse.right() == band.right() - kPad);
|
|
CHECK(r.navBrowse.width == kNavButtonWidth);
|
|
CHECK(r.chanStereo.right() <= r.navBrowse.x);
|
|
CHECK(r.chanMono.right() == r.chanStereo.x);
|
|
CHECK(r.velCell.right() <= r.chanMono.x);
|
|
CHECK(r.preview.right() <= r.velCell.x);
|
|
CHECK(r.title.right() <= r.preview.x);
|
|
CHECK(r.title.x == band.x + kPad);
|
|
CHECK(r.title.width > 0);
|
|
|
|
// Every toolbar rect sits inside the toolbar row.
|
|
const Rect items[] = {r.title, r.preview, r.velCell, r.chanMono,
|
|
r.chanStereo, r.navBrowse};
|
|
for (const Rect& it : items) {
|
|
CHECK(it.y >= r.toolbar.y && it.bottom() <= r.toolbar.bottom());
|
|
}
|
|
}
|
|
|
|
static void testChromePartsNeverOverlapAtAnyWidth() {
|
|
for (int w = 560; w <= 2400; w += 37) {
|
|
const ChromeRects r = chromeRects(chromeBand(w, 620), kKnob);
|
|
// The strip row and the toolbar row are disjoint by construction; the strip must
|
|
// stay inside its own row, clear of every control.
|
|
CHECK(!overlaps(r.toolbar, r.rootStrip));
|
|
CHECK(r.rootStrip.y >= r.controls.y && r.rootStrip.bottom() <= r.controls.bottom());
|
|
const Rect items[] = {r.preview, r.velCell, r.chanMono, r.chanStereo,
|
|
r.navBrowse};
|
|
for (const Rect& it : items) {
|
|
CHECK(!overlaps(it, r.rootStrip));
|
|
CHECK(!overlaps(it, r.title));
|
|
}
|
|
// The run's own members are pairwise disjoint (velKnob/velLabel are inside velCell,
|
|
// so they are checked against the cell's neighbours, not the cell).
|
|
for (int i = 0; i < 5; ++i) {
|
|
for (int j = i + 1; j < 5; ++j) CHECK(!overlaps(items[i], items[j]));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void testStripOwnsItsWholeRowAndGrowsWithTheWindow() {
|
|
const ChromeRects narrow = chromeRects(chromeBand(600, 620), kKnob);
|
|
const ChromeRects wide = chromeRects(chromeBand(1000, 620), kKnob);
|
|
for (const ChromeRects* r : {&narrow, &wide}) {
|
|
// Inset only by the shared band pad — the same inset the waveform band beneath uses,
|
|
// so the two line up. No control shortens it.
|
|
CHECK(r->rootStrip.x == r->controls.x + kPad);
|
|
CHECK(r->rootStrip.right() == r->controls.right() - kPad);
|
|
}
|
|
CHECK(wide.rootStrip.width == narrow.rootStrip.width + 400);
|
|
// The fixed run keeps its size; every extra pixel goes to the title, not the run.
|
|
CHECK(wide.preview.width == narrow.preview.width);
|
|
CHECK(wide.velCell.width == narrow.velCell.width);
|
|
CHECK(wide.title.width == narrow.title.width + 400);
|
|
}
|
|
|
|
static void testVelocityKnobIsCentredInItsCellAboveTheLabel() {
|
|
const ChromeRects r = chromeRects(chromeBand(), kKnob);
|
|
CHECK(r.velKnob.width == kKnob && r.velKnob.height == kKnob);
|
|
CHECK(r.velKnob.y == r.velCell.y);
|
|
const int leftGap = r.velKnob.x - r.velCell.x;
|
|
const int rightGap = r.velCell.right() - r.velKnob.right();
|
|
CHECK(leftGap == rightGap); // horizontally centred in the cell
|
|
CHECK(r.velLabel.y == r.velKnob.bottom());
|
|
CHECK(r.velLabel.bottom() == r.velCell.bottom());
|
|
CHECK(r.velLabel.height > 0);
|
|
CHECK(r.velLabel.x == r.velCell.x && r.velLabel.right() == r.velCell.right());
|
|
}
|
|
|
|
static void testDegenerateBandYieldsNoInvertedRects() {
|
|
const ChromeRects empty = chromeRects(Rect{}, kKnob);
|
|
CHECK(empty.toolbar.empty() && empty.controls.empty());
|
|
CHECK(empty.rootStrip.empty() && empty.preview.empty());
|
|
|
|
// A band far too narrow for the fixed run: everything collapses left, nothing inverts.
|
|
const ChromeRects tiny = chromeRects(Rect::ltrb(0, 0, 40, kTitleHeight + kChromeRowHeight),
|
|
kKnob);
|
|
const Rect items[] = {tiny.title, tiny.preview, tiny.velCell, tiny.velKnob, tiny.velLabel,
|
|
tiny.chanMono, tiny.chanStereo, tiny.navBrowse,
|
|
tiny.rootStrip};
|
|
for (const Rect& it : items) CHECK(it.right() >= it.x && it.bottom() >= it.y);
|
|
}
|
|
|
|
static void testPreviewGlyphSitsInsideTheButtonAndPointsRight() {
|
|
const ChromeRects r = chromeRects(chromeBand(), kKnob);
|
|
const PreviewGlyph g = previewGlyph(r.preview);
|
|
CHECK(!g.empty());
|
|
// Wholly inside the button — the glyph replaces the label, it does not resize the target.
|
|
CHECK(g.leftX >= r.preview.x && g.apexX <= r.preview.right());
|
|
CHECK(g.topY >= r.preview.y && g.bottomY <= r.preview.bottom());
|
|
// Right-pointing, and the apex on the button's own centre line so it reads as balanced.
|
|
CHECK(g.apexX > g.leftX);
|
|
CHECK(g.apexY == r.preview.y + r.preview.height / 2);
|
|
CHECK(g.apexY - g.topY == g.bottomY - g.apexY); // isosceles about the centre line
|
|
// The button's rect is what the hit-test uses, and the glyph must not have moved it: the
|
|
// preview button still sits at the run's fixed size, exactly where the text button did.
|
|
CHECK(r.preview.width == 64 && r.preview.height == 24);
|
|
}
|
|
|
|
static void testPreviewGlyphDegradesRatherThanOverflowing() {
|
|
// An unusably small button yields an empty glyph (draw nothing) rather than a triangle
|
|
// spilling past the button edge.
|
|
CHECK(previewGlyph(Rect{}).empty());
|
|
CHECK(previewGlyph(Rect::ltrb(0, 0, 6, 6)).empty());
|
|
// A tall, wide button caps the glyph instead of scaling without bound.
|
|
const PreviewGlyph big = previewGlyph(Rect::ltrb(0, 0, 400, 200));
|
|
CHECK(!big.empty());
|
|
CHECK(big.bottomY - big.topY <= 14);
|
|
}
|
|
|
|
static void testToolbarOnlyBandStillPlacesTheNav() {
|
|
// A band clipped to just the toolbar row: the strip row is empty but Browse still
|
|
// resolves, so the empty state's call-to-action is never unreachable.
|
|
const ChromeRects r = chromeRects(Rect::ltrb(0, 0, 400, kTitleHeight), kKnob);
|
|
CHECK(r.toolbar.height == kTitleHeight);
|
|
CHECK(r.controls.empty());
|
|
CHECK(r.rootStrip.empty());
|
|
CHECK(r.navBrowse.width == kNavButtonWidth);
|
|
}
|
|
|
|
int main() {
|
|
testRowsTileTheBandExactly();
|
|
testToolbarRunIsOrderedRightToLeftWithoutOverlap();
|
|
testChromePartsNeverOverlapAtAnyWidth();
|
|
testStripOwnsItsWholeRowAndGrowsWithTheWindow();
|
|
testVelocityKnobIsCentredInItsCellAboveTheLabel();
|
|
testDegenerateBandYieldsNoInvertedRects();
|
|
testPreviewGlyphSitsInsideTheButtonAndPointsRight();
|
|
testPreviewGlyphDegradesRatherThanOverflowing();
|
|
testToolbarOnlyBandStillPlacesTheNav();
|
|
|
|
if (g_fail == 0) {
|
|
std::printf("sample_chrome: all tests passed\n");
|
|
return 0;
|
|
}
|
|
std::printf("sample_chrome: %d failure(s)\n", g_fail);
|
|
return 1;
|
|
}
|