Rebuild the chrome band: full-width piano strip with uniform key widths, note tooltips, one toolbar font

This commit is contained in:
2026-07-30 09:14:07 -04:00
parent ea52b14f2a
commit ae23ee0882
14 changed files with 656 additions and 386 deletions
+70 -30
View File
@@ -1,16 +1,17 @@
// Standalone tests for reasampler::instrument::ui::sample_chrome — no VST3, no REAPER, no
// test framework.
//
// Covers: the chrome band's two rows (toolbar over control row, tiling the band exactly);
// the Browse button right-anchored inside the toolbar; the control row's fixed
// right-anchored run in order (preview, velocity cell, curve button, Mono|Stereo) with the
// root strip taking the remainder; the velocity knob centred in its cell above its label;
// and degenerate bands yielding no inverted rects.
// 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, curve button,
// 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; and degenerate bands yielding no inverted rects.
#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;
@@ -25,47 +26,83 @@ static Rect chromeBand(int w = 840, int h = 620) {
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.toolbar.height == kTitleHeight);
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 testBrowseIsRightAnchoredInsideTheToolbar() {
static void testToolbarRunIsOrderedRightToLeftWithoutOverlap() {
const Rect band = chromeBand();
const ChromeRects r = chromeRects(band, kKnob);
// Rightmost first: Browse, stereo, mono, curve button, velocity cell, preview, title.
CHECK(r.navBrowse.right() == band.right() - kPad);
CHECK(r.navBrowse.width == kNavButtonWidth);
CHECK(r.navBrowse.y >= r.toolbar.y);
CHECK(r.navBrowse.bottom() <= r.toolbar.bottom());
}
static void testControlRunIsOrderedRightToLeftWithoutOverlap() {
const Rect band = chromeBand();
const ChromeRects r = chromeRects(band, kKnob);
// Rightmost first: stereo, mono, curve button, velocity cell, preview, then the strip.
CHECK(r.chanStereo.right() == band.right() - kPad);
CHECK(r.chanStereo.right() <= r.navBrowse.x);
CHECK(r.chanMono.right() == r.chanStereo.x);
CHECK(r.curveBtn.right() <= r.chanMono.x);
CHECK(r.velCell.right() <= r.curveBtn.x);
CHECK(r.preview.right() <= r.velCell.x);
CHECK(r.rootStrip.right() <= r.preview.x);
CHECK(r.rootStrip.x == band.x + kPad);
CHECK(r.rootStrip.width > 0);
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.curveBtn, r.chanMono,
r.chanStereo, r.navBrowse};
for (const Rect& it : items) {
CHECK(it.y >= r.toolbar.y && it.bottom() <= r.toolbar.bottom());
}
}
static void testRootStripTakesTheRemainderWidth() {
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.curveBtn, 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 < 6; ++i) {
for (int j = i + 1; j < 6; ++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);
// The fixed run keeps its size; every extra pixel goes to the strip.
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.rootStrip.width == narrow.rootStrip.width + 400);
CHECK(wide.title.width == narrow.title.width + 400);
}
static void testVelocityKnobIsCentredInItsCellAboveTheLabel() {
@@ -77,6 +114,7 @@ static void testVelocityKnobIsCentredInItsCellAboveTheLabel() {
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());
}
@@ -85,28 +123,30 @@ static void testDegenerateBandYieldsNoInvertedRects() {
CHECK(empty.toolbar.empty() && empty.controls.empty());
CHECK(empty.rootStrip.empty() && empty.preview.empty());
// A band far too narrow for the fixed run: the strip collapses, nothing inverts.
// 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);
CHECK(tiny.rootStrip.right() >= tiny.rootStrip.x);
CHECK(tiny.navBrowse.right() >= tiny.navBrowse.x);
CHECK(tiny.preview.right() >= tiny.preview.x || tiny.preview.width < 0);
const Rect items[] = {tiny.title, tiny.preview, tiny.velCell, tiny.velKnob, tiny.velLabel,
tiny.curveBtn, tiny.chanMono, tiny.chanStereo, tiny.navBrowse,
tiny.rootStrip};
for (const Rect& it : items) CHECK(it.right() >= it.x && it.bottom() >= it.y);
}
static void testToolbarOnlyBandStillPlacesTheNav() {
// A band clipped to just the toolbar row: the control row is empty but Browse still
// 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();
testBrowseIsRightAnchoredInsideTheToolbar();
testControlRunIsOrderedRightToLeftWithoutOverlap();
testRootStripTakesTheRemainderWidth();
testToolbarRunIsOrderedRightToLeftWithoutOverlap();
testChromePartsNeverOverlapAtAnyWidth();
testStripOwnsItsWholeRowAndGrowsWithTheWindow();
testVelocityKnobIsCentredInItsCellAboveTheLabel();
testDegenerateBandYieldsNoInvertedRects();
testToolbarOnlyBandStillPlacesTheNav();