Move Re-Capture and Insert Conform from the panel top bar to the overflow menu; re-pin action_bar width test to the new 4-button top bar.
This commit is contained in:
@@ -47,7 +47,7 @@ live in `shell/bank_ops`, a sibling directory, not here.
|
|||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
- `bank_panel` (`shell/panel/`: `panel_window` / `panel_layout` / `panel_render` / `panel_input` / `panel_drag` / `panel_thumbnails` / `panel_audition` / `panel_bank_ops`, sharing state via `panel_state.h` — Q-W2 split of the former god-module into eight TUs) — docked LICE-drawn grid with three-zone layout: top toolbar (Capture → Maintenance → Placement via `action_bar`, short labels, More (⋯) overflow menu via `overflow_menu`), bottom toolbar (four opposite-mode tag buttons + Show Both), and footer (`[Arrange|Design]` toggle, Tail button, Prune via `footer_bar`). Grid renders in sparse slot order with gap cells, drop dispatch, metadata overlay, and selection via `accent/tertiary` purple border. Draws through the L1 kit by palette role; OS drag-out via `drag_out` + `drag_out_win`. `panel_window` owns the SWELL dialog lifecycle + dialog proc + drop-target opt-in; `panel_layout` the toolbar/footer/menu rects + vertical-split geometry (the one geometry source both paint and hit-test read); `panel_render` the WM_PAINT draw; `panel_input` click/wheel/keyboard routing + the new-content auto-tag timer; `panel_drag` the hover + card-drag state machine + drop dispatch; `panel_thumbnails` the PCM→envelope thumbnail cache + the bank-change fingerprint pass; `panel_audition` the preview-playback engine; `panel_bank_ops` the menu/prompt UX skin over the promptless `shell/bank_ops` verbs. `draw_kit` (shared with the VST3 editor) stays a separate TU.
|
- `bank_panel` (`shell/panel/`: `panel_window` / `panel_layout` / `panel_render` / `panel_input` / `panel_drag` / `panel_thumbnails` / `panel_audition` / `panel_bank_ops`, sharing state via `panel_state.h` — Q-W2 split of the former god-module into eight TUs) — docked LICE-drawn grid with three-zone layout: top toolbar (Capture → Placement, frequent acts only, via `action_bar`, short labels; Maintenance and the rarer Placement/Capture variants sit in the More (⋯) overflow menu via `overflow_menu`), bottom toolbar (four opposite-mode tag buttons + Show Both), and footer (`[Arrange|Design]` toggle, Tail button, Prune via `footer_bar`). Grid renders in sparse slot order with gap cells, drop dispatch, metadata overlay, and selection via `accent/tertiary` purple border. Draws through the L1 kit by palette role; OS drag-out via `drag_out` + `drag_out_win`. `panel_window` owns the SWELL dialog lifecycle + dialog proc + drop-target opt-in; `panel_layout` the toolbar/footer/menu rects + vertical-split geometry (the one geometry source both paint and hit-test read); `panel_render` the WM_PAINT draw; `panel_input` click/wheel/keyboard routing + the new-content auto-tag timer; `panel_drag` the hover + card-drag state machine + drop dispatch; `panel_thumbnails` the PCM→envelope thumbnail cache + the bank-change fingerprint pass; `panel_audition` the preview-playback engine; `panel_bank_ops` the menu/prompt UX skin over the promptless `shell/bank_ops` verbs. `draw_kit` (shared with the VST3 editor) stays a separate TU.
|
||||||
- `panel_window` — SWELL dialog lifecycle + dialog proc + drop-target opt-in. The drop splits by extension: a `.rsbank` is a whole bank and routes to the package-import action (one NEW bank each), everything else keeps the audio-ingest route.
|
- `panel_window` — SWELL dialog lifecycle + dialog proc + drop-target opt-in. The drop splits by extension: a `.rsbank` is a whole bank and routes to the package-import action (one NEW bank each), everything else keeps the audio-ingest route.
|
||||||
- `panel_layout` — toolbar/footer/menu rects + vertical-split geometry (the one geometry source both paint and hit-test read).
|
- `panel_layout` — toolbar/footer/menu rects + vertical-split geometry (the one geometry source both paint and hit-test read).
|
||||||
- `panel_render` — the WM_PAINT draw.
|
- `panel_render` — the WM_PAINT draw.
|
||||||
|
|||||||
@@ -109,9 +109,9 @@ bool pointInFooter(int x, int y) {
|
|||||||
return f.top < f.bottom && x >= f.left && x < f.right && y >= f.top && y < f.bottom;
|
return f.top < f.bottom && x >= f.left && x < f.right && y >= f.top && y < f.bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frequent acts only: Capture (item/track), Re-capture (between capture and
|
// Frequent acts only: Capture (item/track), Placement (insert/insert-as-fx).
|
||||||
// placement), Placement (insert/insert-conform). The four rare variants live in
|
// Re-capture and Insert Conform moved to the overflow menu (overflowMenuRows) —
|
||||||
// the overflow menu (overflowMenuRows) — same actions, different home.
|
// same actions, different home.
|
||||||
std::vector<ActionBarRow> topBarRows() {
|
std::vector<ActionBarRow> topBarRows() {
|
||||||
std::vector<ActionBarRow> rows;
|
std::vector<ActionBarRow> rows;
|
||||||
for (const CaptureActionDef& def : captureActionTable()) {
|
for (const CaptureActionDef& def : captureActionTable()) {
|
||||||
@@ -121,13 +121,8 @@ std::vector<ActionBarRow> topBarRows() {
|
|||||||
rows.push_back({def.commandSuffix, label, def.descriptionPhrase,
|
rows.push_back({def.commandSuffix, label, def.descriptionPhrase,
|
||||||
ActionCluster::Capture, true});
|
ActionCluster::Capture, true});
|
||||||
}
|
}
|
||||||
rows.push_back({"RECAPTURE_FROM_SOURCE", "Re-capture",
|
|
||||||
"re-capture from source", ActionCluster::Maintenance, true});
|
|
||||||
rows.push_back({"INSERT_SELECTED", "Insert",
|
rows.push_back({"INSERT_SELECTED", "Insert",
|
||||||
"insert selected sample at edit cursor", ActionCluster::Placement, true});
|
"insert selected sample at edit cursor", ActionCluster::Placement, true});
|
||||||
rows.push_back({"INSERT_SELECTED_CONFORM", "Insert Conform",
|
|
||||||
"insert selected sample at edit cursor (conform to tempo)",
|
|
||||||
ActionCluster::Placement, true});
|
|
||||||
// The one top-bar row with a live/dead gate: it places the PLAYER, and one instance
|
// The one top-bar row with a live/dead gate: it places the PLAYER, and one instance
|
||||||
// holds one capture. Only the payload half of the fold is knowable at draw time —
|
// holds one capture. Only the payload half of the fold is knowable at draw time —
|
||||||
// core/ui/insert_fx_enable.h states why the track half stays a press-time message.
|
// core/ui/insert_fx_enable.h states why the track half stays a press-time message.
|
||||||
@@ -142,8 +137,11 @@ std::vector<ActionBarRow> topBarRows() {
|
|||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The four rare batch/realtime capture variants pulled off the visible bar, plus
|
// The rare batch/realtime capture variants and Cancel RT, plus Re-capture and
|
||||||
// Cancel RT. fullName is the popup entry text (shortLabel is unused for menu items).
|
// Insert Conform (moved off the top bar to make room for the frequent-acts-only
|
||||||
|
// bar). fullName is the popup entry text (shortLabel is unused for menu items).
|
||||||
|
// Ordered by cluster (Capture -> Maintenance -> Placement), same convention as
|
||||||
|
// topBarRows.
|
||||||
std::vector<ActionBarRow> overflowMenuRows() {
|
std::vector<ActionBarRow> overflowMenuRows() {
|
||||||
return {
|
return {
|
||||||
{"CAPTURE_BATCH_ITEMS", "Batch Items",
|
{"CAPTURE_BATCH_ITEMS", "Batch Items",
|
||||||
@@ -152,8 +150,13 @@ std::vector<ActionBarRow> overflowMenuRows() {
|
|||||||
"batch capture razor areas (one per area)", ActionCluster::Capture, true},
|
"batch capture razor areas (one per area)", ActionCluster::Capture, true},
|
||||||
{"CAPTURE_TRACK_REALTIME", "Capture RT",
|
{"CAPTURE_TRACK_REALTIME", "Capture RT",
|
||||||
"capture selected track (realtime)", ActionCluster::Capture, true},
|
"capture selected track (realtime)", ActionCluster::Capture, true},
|
||||||
|
{"RECAPTURE_FROM_SOURCE", "Re-capture",
|
||||||
|
"re-capture from source", ActionCluster::Maintenance, true},
|
||||||
{"CANCEL_REALTIME_CAPTURE", "Cancel RT",
|
{"CANCEL_REALTIME_CAPTURE", "Cancel RT",
|
||||||
"cancel realtime capture", ActionCluster::Maintenance, true},
|
"cancel realtime capture", ActionCluster::Maintenance, true},
|
||||||
|
{"INSERT_SELECTED_CONFORM", "Insert Conform",
|
||||||
|
"insert selected sample at edit cursor (conform to tempo)",
|
||||||
|
ActionCluster::Placement, true},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+33
-47
@@ -30,15 +30,13 @@ static int g_fail = 0;
|
|||||||
#define CHECK(cond) do { if(!(cond)) { \
|
#define CHECK(cond) do { if(!(cond)) { \
|
||||||
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
|
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
|
||||||
|
|
||||||
// The panel's real inventory shape: 2 capture, 1 maintenance (Re-capture), 3 placement
|
// The panel's real inventory shape: 2 capture, 2 placement (Insert / Insert as FX) = 4
|
||||||
// (Insert / Insert Conform / Insert as FX) = 6 buttons. Cluster order:
|
// buttons. Re-capture and Insert Conform moved to the overflow menu, so the top bar's
|
||||||
// Capture -> Maintenance -> Placement (Re-capture sits between the two capture verbs and the
|
// Maintenance cluster is now empty. Cluster order: Capture -> Placement.
|
||||||
// placement verbs).
|
|
||||||
static std::vector<ClusterSpec> inventory() {
|
static std::vector<ClusterSpec> inventory() {
|
||||||
return {
|
return {
|
||||||
{ActionCluster::Capture, 2},
|
{ActionCluster::Capture, 2},
|
||||||
{ActionCluster::Maintenance, 1},
|
{ActionCluster::Placement, 2},
|
||||||
{ActionCluster::Placement, 3},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,28 +53,26 @@ static ActionBarSpec roundSpec() {
|
|||||||
|
|
||||||
// --- Layout: all fit, correct rects + gaps ------------------------------------
|
// --- Layout: all fit, correct rects + gaps ------------------------------------
|
||||||
|
|
||||||
// A wide bar fits all 6 buttons. Verify the first few rects, the intra-cluster gap, and the
|
// A wide bar fits all 4 buttons. Verify the first few rects, the intra-cluster gap, and the
|
||||||
// (wider) inter-cluster gap between button 1 (last capture) and button 2 (maintenance).
|
// (wider) inter-cluster gap between button 1 (last capture) and button 2 (first placement).
|
||||||
//
|
//
|
||||||
// Pixel walk with roundSpec and bar at (0,40):
|
// Pixel walk with roundSpec and bar at (0,40):
|
||||||
// btn0 (Capture): x=8, right=108
|
// btn0 (Capture): x=8, right=108
|
||||||
// btn1 (Capture): x=112, right=212 (intraGap = 4 after btn0's right)
|
// btn1 (Capture): x=112, right=212 (intraGap = 4 after btn0's right)
|
||||||
// btn2 (Maintenance): x=228, right=328 (clusterGap = 16 after btn1's right)
|
// btn2 (Placement): x=228, right=328 (clusterGap = 16 after btn1's right)
|
||||||
// btn3 (Placement): x=344, right=444 (clusterGap = 16 after btn2's right)
|
// btn3 (Placement): x=332, right=432 (intraGap = 4 after btn2's right)
|
||||||
// btn4 (Placement): x=448, right=548 (intraGap = 4 after btn3's right)
|
// Minimum bar width: 432 + sidePad(8) = 440. Give it 500.
|
||||||
// btn5 (Placement): x=552, right=652 (intraGap = 4 after btn4's right)
|
|
||||||
// Minimum bar width: 652 + sidePad(8) = 660. Give it 700.
|
|
||||||
static void testAllFitRectsAndGaps() {
|
static void testAllFitRectsAndGaps() {
|
||||||
const auto clusters = inventory();
|
const auto clusters = inventory();
|
||||||
const ActionBarSpec spec = roundSpec();
|
const ActionBarSpec spec = roundSpec();
|
||||||
ActionBarRect bar{0, 40, 700, 34};
|
ActionBarRect bar{0, 40, 500, 34};
|
||||||
|
|
||||||
BarFit fit = computeBarFit(bar, clusters, spec);
|
BarFit fit = computeBarFit(bar, clusters, spec);
|
||||||
CHECK(fit.visibleCount == 6);
|
CHECK(fit.visibleCount == 4);
|
||||||
CHECK(fit.hiddenCount == 0);
|
CHECK(fit.hiddenCount == 0);
|
||||||
|
|
||||||
auto slots = computeBarSlots(bar, clusters, spec);
|
auto slots = computeBarSlots(bar, clusters, spec);
|
||||||
CHECK(slots.size() == 6);
|
CHECK(slots.size() == 4);
|
||||||
|
|
||||||
// Button 0: at sidePad, top = y + verticalInset, height = barH - 2*inset.
|
// Button 0: at sidePad, top = y + verticalInset, height = barH - 2*inset.
|
||||||
CHECK(slots[0].x == 8);
|
CHECK(slots[0].x == 8);
|
||||||
@@ -90,27 +86,17 @@ static void testAllFitRectsAndGaps() {
|
|||||||
CHECK(slots[1].x == 112);
|
CHECK(slots[1].x == 112);
|
||||||
CHECK(slots[1].cluster == ActionCluster::Capture);
|
CHECK(slots[1].cluster == ActionCluster::Capture);
|
||||||
|
|
||||||
// Button 2 (maintenance / Re-capture): cluster gap of 16 after 212 -> 228.
|
// Button 2 (first placement): cluster gap of 16 after 212 -> 228.
|
||||||
CHECK(slots[2].x == 228);
|
CHECK(slots[2].x == 228);
|
||||||
CHECK(slots[2].cluster == ActionCluster::Maintenance);
|
CHECK(slots[2].cluster == ActionCluster::Placement);
|
||||||
CHECK(slots[2].index == 2);
|
CHECK(slots[2].index == 2);
|
||||||
|
|
||||||
// Button 3 (first placement): cluster gap of 16 after 328 -> 344.
|
// Button 3 (Insert as FX — second placement): intra-cluster gap of 4 after 328 -> 332.
|
||||||
CHECK(slots[3].x == 344);
|
// It joins the Placement cluster, so no cluster gap opens before it.
|
||||||
|
CHECK(slots[3].x == 332);
|
||||||
CHECK(slots[3].cluster == ActionCluster::Placement);
|
CHECK(slots[3].cluster == ActionCluster::Placement);
|
||||||
CHECK(slots[3].index == 3);
|
CHECK(slots[3].index == 3);
|
||||||
|
CHECK(slots[3].x - (slots[2].x + slots[2].width) == spec.buttonGap);
|
||||||
// Button 4 (second placement): intra-cluster gap of 4 after 444 -> 448.
|
|
||||||
CHECK(slots[4].x == 448);
|
|
||||||
CHECK(slots[4].cluster == ActionCluster::Placement);
|
|
||||||
CHECK(slots[4].index == 4);
|
|
||||||
|
|
||||||
// Button 5 (Insert as FX — third placement): intra-cluster gap of 4 after 548 -> 552.
|
|
||||||
// It joins the Placement cluster, so no cluster gap opens before it.
|
|
||||||
CHECK(slots[5].x == 552);
|
|
||||||
CHECK(slots[5].cluster == ActionCluster::Placement);
|
|
||||||
CHECK(slots[5].index == 5);
|
|
||||||
CHECK(slots[5].x - (slots[4].x + slots[4].width) == spec.buttonGap);
|
|
||||||
|
|
||||||
// Inter-cluster gap (slot[2].x - slot[1].right = 228 - 212 = 16) is wider than the
|
// Inter-cluster gap (slot[2].x - slot[1].right = 228 - 212 = 16) is wider than the
|
||||||
// intra-cluster gap (slot[1].x - slot[0].right = 112 - 108 = 4) — task grouping is
|
// intra-cluster gap (slot[1].x - slot[0].right = 112 - 108 = 4) — task grouping is
|
||||||
@@ -159,14 +145,14 @@ static void testLabelFullHeightWhenShort() {
|
|||||||
// keep their full width (never clipped), and the frequent capture cluster survives.
|
// keep their full width (never clipped), and the frequent capture cluster survives.
|
||||||
//
|
//
|
||||||
// Exact fit for 2 capture buttons: sidePad(8) + 2*100 + 1*4 + sidePad(8) = 220.
|
// Exact fit for 2 capture buttons: sidePad(8) + 2*100 + 1*4 + sidePad(8) = 220.
|
||||||
// A 3rd button (Maintenance) needs clusterGap(16)+100 = 116 more -> 336. So 220 fits exactly 2.
|
// A 3rd button (Placement) needs clusterGap(16)+100 = 116 more -> 336. So 220 fits exactly 2.
|
||||||
static void testOverflowDropsTrailingWhole() {
|
static void testOverflowDropsTrailingWhole() {
|
||||||
const auto clusters = inventory();
|
const auto clusters = inventory();
|
||||||
const ActionBarSpec spec = roundSpec();
|
const ActionBarSpec spec = roundSpec();
|
||||||
ActionBarRect bar{0, 0, 220, 34};
|
ActionBarRect bar{0, 0, 220, 34};
|
||||||
BarFit fit = computeBarFit(bar, clusters, spec);
|
BarFit fit = computeBarFit(bar, clusters, spec);
|
||||||
CHECK(fit.visibleCount == 2);
|
CHECK(fit.visibleCount == 2);
|
||||||
CHECK(fit.hiddenCount == 4);
|
CHECK(fit.hiddenCount == 2);
|
||||||
|
|
||||||
auto slots = computeBarSlots(bar, clusters, spec);
|
auto slots = computeBarSlots(bar, clusters, spec);
|
||||||
CHECK(slots.size() == 2);
|
CHECK(slots.size() == 2);
|
||||||
@@ -186,7 +172,7 @@ static void testTooNarrowForAny() {
|
|||||||
ActionBarRect bar{0, 0, 60, 34}; // sidePad*2 + one 100-wide button won't fit
|
ActionBarRect bar{0, 0, 60, 34}; // sidePad*2 + one 100-wide button won't fit
|
||||||
BarFit fit = computeBarFit(bar, clusters, spec);
|
BarFit fit = computeBarFit(bar, clusters, spec);
|
||||||
CHECK(fit.visibleCount == 0);
|
CHECK(fit.visibleCount == 0);
|
||||||
CHECK(fit.hiddenCount == 6);
|
CHECK(fit.hiddenCount == 4);
|
||||||
CHECK(computeBarSlots(bar, clusters, spec).empty());
|
CHECK(computeBarSlots(bar, clusters, spec).empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,9 +232,9 @@ static ActionBarSpec realBarSpec() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// At the real spec plus the real 40 px reserve (28 + 2*6, panel_state.h's kMenuBtnSpec --
|
// At the real spec plus the real 40 px reserve (28 + 2*6, panel_state.h's kMenuBtnSpec --
|
||||||
// all default-constructed here since its defaults already match), 764 is the narrowest
|
// all default-constructed here since its defaults already match), 520 is the narrowest
|
||||||
// band that still shows all six buttons; one pixel narrower drops Insert as FX (flat index
|
// band that still shows all four buttons; one pixel narrower drops Insert as FX (flat index
|
||||||
// 5, the trailing Placement button) alone into overflow, leaving its five neighbours intact.
|
// 3, the trailing Placement button) alone into overflow, leaving its three neighbours intact.
|
||||||
static void testReserveDropsInsertAsFxAtRealWidth() {
|
static void testReserveDropsInsertAsFxAtRealWidth() {
|
||||||
const auto clusters = inventory();
|
const auto clusters = inventory();
|
||||||
const ActionBarSpec spec = realBarSpec();
|
const ActionBarSpec spec = realBarSpec();
|
||||||
@@ -256,18 +242,18 @@ static void testReserveDropsInsertAsFxAtRealWidth() {
|
|||||||
const int reserve = menuButtonReserve(MenuBarRect{0, 0, 900, 34}, menuSpec);
|
const int reserve = menuButtonReserve(MenuBarRect{0, 0, 900, 34}, menuSpec);
|
||||||
CHECK(reserve == 40);
|
CHECK(reserve == 40);
|
||||||
|
|
||||||
const int allSixWidth = 764;
|
const int allFourWidth = 520;
|
||||||
{
|
{
|
||||||
ActionBarRect action{0, 0, allSixWidth - reserve, 34};
|
ActionBarRect action{0, 0, allFourWidth - reserve, 34};
|
||||||
const auto slots = computeBarSlots(action, clusters, spec);
|
const auto slots = computeBarSlots(action, clusters, spec);
|
||||||
CHECK(slots.size() == 6);
|
CHECK(slots.size() == 4);
|
||||||
CHECK(slots.back().index == 5); // Insert as FX still visible
|
CHECK(slots.back().index == 3); // Insert as FX still visible
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
ActionBarRect action{0, 0, allSixWidth - 1 - reserve, 34};
|
ActionBarRect action{0, 0, allFourWidth - 1 - reserve, 34};
|
||||||
const auto slots = computeBarSlots(action, clusters, spec);
|
const auto slots = computeBarSlots(action, clusters, spec);
|
||||||
CHECK(slots.size() == 5); // Insert as FX alone dropped
|
CHECK(slots.size() == 3); // Insert as FX alone dropped
|
||||||
CHECK(slots.back().index == 4); // Insert Conform is now the trailing visible button
|
CHECK(slots.back().index == 2); // Insert is now the trailing visible button
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user