S5 Tier-1: zoned keymap editor + performance-map playback/persistence

Zone editor in the IPlugView LICE surface, zoned resolution built off-thread into the
LoadedInstrument keymap with Tier-0 fallback, performance map in VST3 component state
with v1 back-compat. Pure resolve/build/serialize + geometry with CTest coverage.
This commit is contained in:
2026-07-26 17:28:15 -04:00
parent 6ae843345c
commit 2356958930
10 changed files with 1063 additions and 63 deletions
+119
View File
@@ -198,6 +198,118 @@ static void testSampleRowHitTestMatchesDrawnRows() {
}
}
// --- keymap editor (S5 Tier-1 UI) --------------------------------------------
static void testKeymapLayoutSplitsCanvas() {
const KeymapEditorLayout L = layoutKeymapEditor(600, 300);
// The left sample list and right zone panel partition the canvas with no overlap and
// no gap: the list's right edge is the panel's left edge.
CHECK(L.sampleList.left == L.base.canvas.left);
CHECK(L.sampleList.right == L.zonePanel.left);
CHECK(L.zonePanel.right == L.base.canvas.right);
CHECK(L.sampleList.top == L.base.canvas.top);
CHECK(L.zonePanel.top == L.base.canvas.top);
CHECK(L.sampleList.bottom == L.base.canvas.bottom);
CHECK(L.zonePanel.bottom == L.base.canvas.bottom);
CHECK(L.sampleList.width() > 0 && L.zonePanel.width() > 0);
// Add-Zone button caps the panel; zone rows stack below it.
CHECK(L.addZoneButton.top == L.zonePanel.top);
CHECK(L.addZoneButton.left == L.zonePanel.left && L.addZoneButton.right == L.zonePanel.right);
CHECK(L.zoneRowArea.top == L.addZoneButton.bottom);
CHECK(L.zoneRowArea.bottom == L.zonePanel.bottom);
}
static void checkNoInversion(const KeymapEditorLayout& L) {
CHECK(L.sampleList.right >= L.sampleList.left);
CHECK(L.zonePanel.right >= L.zonePanel.left);
CHECK(L.addZoneButton.right >= L.addZoneButton.left);
CHECK(L.addZoneButton.bottom >= L.addZoneButton.top);
CHECK(L.zoneRowArea.right >= L.zoneRowArea.left);
CHECK(L.zoneRowArea.bottom >= L.zoneRowArea.top);
// Regions stay within the client area.
CHECK(L.zonePanel.right <= L.base.canvas.right);
}
static void testKeymapLayoutTinyAndZeroNoInversion() {
checkNoInversion(layoutKeymapEditor(30, 30));
checkNoInversion(layoutKeymapEditor(0, 0));
// A click anywhere on a zero layout hits no zone and no Add button.
const KeymapEditorLayout Z = layoutKeymapEditor(0, 0);
CHECK(zoneHitTest(Z, 3, 0, 0).zoneIndex == -1);
CHECK(!addZoneHitTest(Z, 0, 0));
}
static void testKeymapSampleRowInLeftColumn() {
const KeymapEditorLayout L = layoutKeymapEditor(600, 300);
const Rect r0 = keymapSampleRowRect(L, 0);
// Rows live in the LEFT column (not the full canvas width).
CHECK(r0.left == L.sampleList.left && r0.right == L.sampleList.right);
CHECK(r0.right < L.base.canvas.right); // strictly left of the zone panel
CHECK(r0.top == L.sampleList.top && r0.height() == kSampleRowHeight);
// Hit-test maps a left-column click to the row and rejects a click in the zone panel.
const int midY = (r0.top + r0.bottom) / 2;
CHECK(keymapSampleRowHitTest(L, 3, r0.left + 2, midY) == 0);
CHECK(keymapSampleRowHitTest(L, 3, L.zonePanel.left + 2, midY) == -1);
}
static void testAddZoneHitTest() {
const KeymapEditorLayout L = layoutKeymapEditor(600, 300);
const int cx = (L.addZoneButton.left + L.addZoneButton.right) / 2;
const int cy = (L.addZoneButton.top + L.addZoneButton.bottom) / 2;
CHECK(addZoneHitTest(L, cx, cy));
// A click in the zone-row area below the button is NOT the Add button.
CHECK(!addZoneHitTest(L, cx, L.zoneRowArea.top + 2));
// A click in the left list is NOT the Add button.
CHECK(!addZoneHitTest(L, L.sampleList.left + 2, L.sampleList.top + 2));
}
static void testZoneRowStacksAndSelects() {
const KeymapEditorLayout L = layoutKeymapEditor(600, 300);
const Rect z0 = zoneRowRect(L, 0);
const Rect z1 = zoneRowRect(L, 1);
CHECK(z0.top == L.zoneRowArea.top && z0.height() == kZoneRowHeight);
CHECK(z1.top == z0.bottom); // stacked, no gap
CHECK(z0.left == L.zoneRowArea.left && z0.right == L.zoneRowArea.right);
// A click on the LABEL area (left part of a zone row) selects the zone with no field.
const int labelX = z0.left + 2; // far left = label, not a control
const int midY = (z0.top + z0.bottom) / 2;
const ZoneHit h = zoneHitTest(L, 2, labelX, midY);
CHECK(h.zoneIndex == 0 && h.field == ZoneField::kZoneNone);
}
static void testZoneRowControlsMapToFields() {
const KeymapEditorLayout L = layoutKeymapEditor(600, 300);
const Rect row = zoneRowRect(L, 0);
const int midY = (row.top + row.bottom) / 2;
// The seven controls occupy the rightmost 7*kZoneCtrlWidth px, left-to-right:
// low-, low+, high-, high+, root-, root+, delete.
const int block = row.right - 7 * kZoneCtrlWidth;
const ZoneField expected[7] = {
ZoneField::kLowDown, ZoneField::kLowUp, ZoneField::kHighDown,
ZoneField::kHighUp, ZoneField::kRootDown, ZoneField::kRootUp,
ZoneField::kDelete,
};
for (int s = 0; s < 7; ++s) {
const int x = block + s * kZoneCtrlWidth + kZoneCtrlWidth / 2; // center of slot s
const ZoneHit h = zoneHitTest(L, 1, x, midY);
CHECK(h.zoneIndex == 0);
CHECK(h.zoneIndex == 0 && h.field == expected[s]);
}
}
static void testZoneHitTestMisses() {
const KeymapEditorLayout L = layoutKeymapEditor(600, 300);
const Rect row = zoneRowRect(L, 0);
const int midY = (row.top + row.bottom) / 2;
// Zero zones -> always miss.
CHECK(zoneHitTest(L, 0, row.left + 2, midY).zoneIndex == -1);
// Below the last zone row -> miss.
const Rect last = zoneRowRect(L, 2);
CHECK(zoneHitTest(L, 3, row.left + 2, last.bottom + 1).zoneIndex == -1);
// Left of the zone panel (in the sample list) -> miss.
CHECK(zoneHitTest(L, 3, L.sampleList.left + 2, midY).zoneIndex == -1);
}
int main() {
testContainsHalfOpen();
testContainsDegenerate();
@@ -212,6 +324,13 @@ int main() {
testSampleRowHitTestMapsClickToRow();
testSampleRowHitTestMisses();
testSampleRowHitTestMatchesDrawnRows();
testKeymapLayoutSplitsCanvas();
testKeymapLayoutTinyAndZeroNoInversion();
testKeymapSampleRowInLeftColumn();
testAddZoneHitTest();
testZoneRowStacksAndSelects();
testZoneRowControlsMapToFields();
testZoneHitTestMisses();
if (g_fail == 0) std::printf("editor_geometry: all tests passed\n");
return g_fail != 0;