diff --git a/src/shell/panel/CLAUDE.md b/src/shell/panel/CLAUDE.md index 15dae0c..441a355 100644 --- a/src/shell/panel/CLAUDE.md +++ b/src/shell/panel/CLAUDE.md @@ -47,7 +47,7 @@ live in `shell/bank_ops`, a sibling directory, not here. ## 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_layout` — toolbar/footer/menu rects + vertical-split geometry (the one geometry source both paint and hit-test read). - `panel_render` — the WM_PAINT draw. diff --git a/src/shell/panel/panel_drag.cpp b/src/shell/panel/panel_drag.cpp index a9e20f6..d4a5a16 100644 --- a/src/shell/panel/panel_drag.cpp +++ b/src/shell/panel/panel_drag.cpp @@ -157,7 +157,7 @@ void applyDropCue(DropCue cue) { const char* idc = nullptr; switch (cue) { case DropCue::Instrument: idc = IDC_HAND; break; - case DropCue::ArrangeInsert: idc = IDC_IBEAM; break; // an insertion point on a timeline + case DropCue::ArrangeInsert: idc = IDC_CROSS; break; // I-beam read as text-edit, not a timeline placement point case DropCue::Refuse: idc = IDC_NO; break; case DropCue::OsOwned: return; // reached once per move resolving to OsHandoff, // right before handOffToOs is attempted; the OS diff --git a/src/shell/panel/panel_layout.cpp b/src/shell/panel/panel_layout.cpp index ad9d781..1bb17e5 100644 --- a/src/shell/panel/panel_layout.cpp +++ b/src/shell/panel/panel_layout.cpp @@ -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; } -// Frequent acts only: Capture (item/track), Re-capture (between capture and -// placement), Placement (insert/insert-conform). The four rare variants live in -// the overflow menu (overflowMenuRows) — same actions, different home. +// Frequent acts only: Capture (item/track), Placement (insert/insert-as-fx). +// Re-capture and Insert Conform moved to the overflow menu (overflowMenuRows) — +// same actions, different home. std::vector topBarRows() { std::vector rows; for (const CaptureActionDef& def : captureActionTable()) { @@ -121,13 +121,8 @@ std::vector topBarRows() { rows.push_back({def.commandSuffix, label, def.descriptionPhrase, ActionCluster::Capture, true}); } - rows.push_back({"RECAPTURE_FROM_SOURCE", "Re-capture", - "re-capture from source", ActionCluster::Maintenance, true}); rows.push_back({"INSERT_SELECTED", "Insert", "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 // 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. @@ -142,8 +137,11 @@ std::vector topBarRows() { return rows; } -// The four rare batch/realtime capture variants pulled off the visible bar, plus -// Cancel RT. fullName is the popup entry text (shortLabel is unused for menu items). +// The rare batch/realtime capture variants and Cancel RT, plus Re-capture and +// 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 overflowMenuRows() { return { {"CAPTURE_BATCH_ITEMS", "Batch Items", @@ -152,8 +150,13 @@ std::vector overflowMenuRows() { "batch capture razor areas (one per area)", ActionCluster::Capture, true}, {"CAPTURE_TRACK_REALTIME", "Capture RT", "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", ActionCluster::Maintenance, true}, + {"INSERT_SELECTED_CONFORM", "Insert Conform", + "insert selected sample at edit cursor (conform to tempo)", + ActionCluster::Placement, true}, }; } diff --git a/tests/test_action_bar.cpp b/tests/test_action_bar.cpp index db53fd8..5ca6f54 100644 --- a/tests/test_action_bar.cpp +++ b/tests/test_action_bar.cpp @@ -30,15 +30,13 @@ static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ 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 -// (Insert / Insert Conform / Insert as FX) = 6 buttons. Cluster order: -// Capture -> Maintenance -> Placement (Re-capture sits between the two capture verbs and the -// placement verbs). +// The panel's real inventory shape: 2 capture, 2 placement (Insert / Insert as FX) = 4 +// buttons. Re-capture and Insert Conform moved to the overflow menu, so the top bar's +// Maintenance cluster is now empty. Cluster order: Capture -> Placement. static std::vector inventory() { return { {ActionCluster::Capture, 2}, - {ActionCluster::Maintenance, 1}, - {ActionCluster::Placement, 3}, + {ActionCluster::Placement, 2}, }; } @@ -55,28 +53,26 @@ static ActionBarSpec roundSpec() { // --- Layout: all fit, correct rects + gaps ------------------------------------ -// A wide bar fits all 6 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). +// 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 (first placement). // // Pixel walk with roundSpec and bar at (0,40): -// btn0 (Capture): x=8, right=108 -// btn1 (Capture): x=112, right=212 (intraGap = 4 after btn0's right) -// btn2 (Maintenance): x=228, right=328 (clusterGap = 16 after btn1's right) -// btn3 (Placement): x=344, right=444 (clusterGap = 16 after btn2's right) -// btn4 (Placement): x=448, right=548 (intraGap = 4 after btn3's right) -// btn5 (Placement): x=552, right=652 (intraGap = 4 after btn4's right) -// Minimum bar width: 652 + sidePad(8) = 660. Give it 700. +// btn0 (Capture): x=8, right=108 +// btn1 (Capture): x=112, right=212 (intraGap = 4 after btn0's right) +// btn2 (Placement): x=228, right=328 (clusterGap = 16 after btn1's right) +// btn3 (Placement): x=332, right=432 (intraGap = 4 after btn2's right) +// Minimum bar width: 432 + sidePad(8) = 440. Give it 500. static void testAllFitRectsAndGaps() { const auto clusters = inventory(); const ActionBarSpec spec = roundSpec(); - ActionBarRect bar{0, 40, 700, 34}; + ActionBarRect bar{0, 40, 500, 34}; BarFit fit = computeBarFit(bar, clusters, spec); - CHECK(fit.visibleCount == 6); + CHECK(fit.visibleCount == 4); CHECK(fit.hiddenCount == 0); 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. CHECK(slots[0].x == 8); @@ -90,27 +86,17 @@ static void testAllFitRectsAndGaps() { CHECK(slots[1].x == 112); 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].cluster == ActionCluster::Maintenance); + CHECK(slots[2].cluster == ActionCluster::Placement); CHECK(slots[2].index == 2); - // Button 3 (first placement): cluster gap of 16 after 328 -> 344. - CHECK(slots[3].x == 344); + // Button 3 (Insert as FX — second placement): intra-cluster gap of 4 after 328 -> 332. + // 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].index == 3); - - // 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); + CHECK(slots[3].x - (slots[2].x + slots[2].width) == spec.buttonGap); // 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 @@ -159,14 +145,14 @@ static void testLabelFullHeightWhenShort() { // 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. -// 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() { const auto clusters = inventory(); const ActionBarSpec spec = roundSpec(); ActionBarRect bar{0, 0, 220, 34}; BarFit fit = computeBarFit(bar, clusters, spec); CHECK(fit.visibleCount == 2); - CHECK(fit.hiddenCount == 4); + CHECK(fit.hiddenCount == 2); auto slots = computeBarSlots(bar, clusters, spec); 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 BarFit fit = computeBarFit(bar, clusters, spec); CHECK(fit.visibleCount == 0); - CHECK(fit.hiddenCount == 6); + CHECK(fit.hiddenCount == 4); 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 -- -// all default-constructed here since its defaults already match), 764 is the narrowest -// band that still shows all six buttons; one pixel narrower drops Insert as FX (flat index -// 5, the trailing Placement button) alone into overflow, leaving its five neighbours intact. +// all default-constructed here since its defaults already match), 520 is the narrowest +// band that still shows all four buttons; one pixel narrower drops Insert as FX (flat index +// 3, the trailing Placement button) alone into overflow, leaving its three neighbours intact. static void testReserveDropsInsertAsFxAtRealWidth() { const auto clusters = inventory(); const ActionBarSpec spec = realBarSpec(); @@ -256,18 +242,18 @@ static void testReserveDropsInsertAsFxAtRealWidth() { const int reserve = menuButtonReserve(MenuBarRect{0, 0, 900, 34}, menuSpec); 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); - CHECK(slots.size() == 6); - CHECK(slots.back().index == 5); // Insert as FX still visible + CHECK(slots.size() == 4); + 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); - CHECK(slots.size() == 5); // Insert as FX alone dropped - CHECK(slots.back().index == 4); // Insert Conform is now the trailing visible button + CHECK(slots.size() == 3); // Insert as FX alone dropped + CHECK(slots.back().index == 2); // Insert is now the trailing visible button } }