Cut shell/panel comment bloat ~47% (comments only, zero code change)

This commit is contained in:
2026-07-29 20:48:56 -04:00
parent 1f24c4b095
commit cc36dd59c7
15 changed files with 627 additions and 1320 deletions
+124 -241
View File
@@ -1,36 +1,22 @@
#pragma once
// panel_state — INTERNAL shared state + cross-seam contract of the docked bank panel
// (Q-W2: bank_panel.cpp split into eight TUs under shell/panel/). Included ONLY by the
// panel's own translation units (panel_render / panel_thumbnails / panel_audition /
// panel_input / panel_layout / panel_drag / panel_bank_ops / panel_window) — consumers
// outside the panel use the per-seam public headers (panel_window.h / panel_input.h /
// panel_bank_ops.h / panel_layout.h).
// panel_state — INTERNAL shared state + cross-seam contract of the docked bank panel.
// Included ONLY by the panel's own TUs; outside consumers use the per-seam public
// headers (panel_window.h / panel_input.h / panel_bank_ops.h / panel_layout.h).
// Cross-seam calls are plain free functions — direct call-through, no virtual dispatch
// (audition and per-mouse-move paths must stay direct calls).
//
// What lives here:
// * PanelState (the one shared state blob, defined in panel_window.cpp) + the small
// enums/structs the seams speak (Region / DropKind / Hover / RegionDisplay /
// ActionBarRow) and the shared layout constants.
// * The cross-seam free-function declarations, grouped by OWNING TU. Everything is a
// plain free function — direct call-through, no interface, no virtual dispatch
// (T4-28: the audition path and the per-mouse-move path must stay direct calls).
// * Explicit using-declarations pulling the pure modules' symbols into
// reasampler::panel from their REAL namespace homes (Q-W1 sub-namespaces). The
// interim core/namespaces.h shim is GONE (deleted in Q-W6 with the last split);
// every symbol below names its true home.
//
// REFERENCE-INVALIDATION GUARDRAIL (CONTEXT.md §Multi-bank): a bank-structural
// mutation (create/delete/evacuate/activate/move) can reallocate the book's vector,
// so a BankModel& / Bank* must NEVER be cached across one. Every seam resolves fresh
// AFTER any mutation and passes bank IDS (not references) into the model ops.
// REFERENCE-INVALIDATION GUARDRAIL: a bank-structural mutation (create/delete/
// evacuate/activate/move) can reallocate the book's vector, so a BankModel& / Bank*
// must NEVER be cached across one. Every seam resolves fresh after any mutation and
// passes bank IDS (not references) into the model ops.
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
// SWELL / platform types (HWND, RECT, HMENU). On macOS/Linux SWELL is provided by the
// host (SWELL_PROVIDED_BY_APP); on Windows we use native Win32 (windows.h first, then
// swell.h no-ops on _WIN32).
// On macOS/Linux SWELL is provided by the host (SWELL_PROVIDED_BY_APP); on Windows
// we use native Win32 (windows.h first, then swell.h no-ops on _WIN32).
#ifdef _WIN32
#include <windows.h>
#else
@@ -39,37 +25,35 @@
#include "wdltypes.h"
#include "swell/swell.h"
// REAPER SDK types only (preview_register_t, MediaTrack, ReaProject). The API function
// POINTERS are declared per-TU (REAPERAPI_MINIMAL + per-TU WANT list) — main.cpp owns
// the definitions (CLAUDE.md §contract).
// REAPER API function pointers are declared per-TU; main.cpp owns the definitions.
#include "reaper_plugin.h"
#include "core/audio/peaks.h" // audio::Envelope — thumbnail cache payload
#include "core/capture/capture_paths.h" // capture::resolveBankFile / normalizeSlashes
#include "core/capture/render_settings.h" // capture::CaptureActionDef / captureActionTable
#include "core/capture/tail_control.h" // capture::TailSetting — the tail toggle state
#include "core/model/bank_book.h" // BankBook / Bank / SlotMap (flat reasampler until its split wave)
#include "core/model/bank_model.h" // model::BankModel / model::Sample
#include "core/ui/action_bar.h" // ui::ActionBarRect / slots / clusters
#include "core/ui/bank_grid.h" // ui::GridSpec / Selection / ThumbnailKey / CellRect
#include "core/ui/card_drag.h" // ui::CardGesture / SlotCellRect / gesture decisions
#include "core/ui/card_meta.h" // ui::MusicalLength / formatters
#include "core/ui/component_geometry.h" // ui::KitBox / KitButtonBox / waveformColumnCount
#include "core/ui/drag_out.h" // ui::DragState / PanelClientRect / decideGesture
#include "core/ui/footer_bar.h" // ui::FooterBarLayout / computeFooterBar
#include "core/ui/mode_enable.h" // ui::tagButtonEnabled / TagTarget
#include "core/ui/overflow_menu.h" // ui::MenuButtonSpec / computeMenuButton
#include "core/ui/prune_button.h" // ui::ButtonRect / computePruneButton
#include "core/ui/tab_strip.h" // ui::TabStripSpec / layout / hit-test
#include "core/ui/theme.h" // ui::Role / InteractionState / KitColor
#include "core/ui/tooltip.h" // ui::TooltipBox / computeTooltip / stripActionPrefix
#include "core/version/app_version.h" // version::channelCommandId / appVersion / dock identity
#include "core/view/guid_diff.h" // view::GuidBaseline — new-content detection
#include "core/view/lane_keys.h" // view::isOnManualLane
#include "core/view/mode_switch.h" // view::SegmentRect / computeSegmentRects
#include "core/wire/instrument_drop.h" // wire::buildInstrumentDropPreset (S17)
#include "core/audio/peaks.h"
#include "core/capture/capture_paths.h"
#include "core/capture/render_settings.h"
#include "core/capture/tail_control.h"
#include "core/model/bank_book.h"
#include "core/model/bank_model.h"
#include "core/ui/action_bar.h"
#include "core/ui/bank_grid.h"
#include "core/ui/card_drag.h"
#include "core/ui/card_meta.h"
#include "core/ui/component_geometry.h"
#include "core/ui/drag_out.h"
#include "core/ui/footer_bar.h"
#include "core/ui/mode_enable.h"
#include "core/ui/overflow_menu.h"
#include "core/ui/prune_button.h"
#include "core/ui/tab_strip.h"
#include "core/ui/theme.h"
#include "core/ui/tooltip.h"
#include "core/version/app_version.h"
#include "core/view/guid_diff.h"
#include "core/view/lane_keys.h"
#include "core/view/mode_switch.h"
#include "core/wire/instrument_drop.h"
#include "shell/panel/panel_layout.h" // BankPanelFullHeight — the split-state enum
#include "shell/panel/panel_layout.h"
namespace reasampler {
class ReaSamplerSession;
@@ -77,14 +61,6 @@ class ReaSamplerSession;
namespace reasampler::panel {
// --- Real-namespace-home using-declarations -----------------------------------
//
// The panel's pre-split internals reference the pure modules' symbols unqualified;
// these explicit per-symbol usings keep those references valid while documenting
// each symbol's Q-W1 home. Flat-`reasampler` symbols (BankBook / ViewModeModel /
// the draw_kit shell / the shell/bank_ops verbs / ...) resolve via the enclosing
// namespace and need no using.
// core/ui
using ui::ActionBarRect;
using ui::ActionBarSlot;
@@ -200,73 +176,46 @@ using version::dockTitle;
// core/wire
using wire::buildInstrumentDropPreset;
// --- Layout constants ---------------------------------------------------------
//
// L2: every panel COLOR comes from the pure `theme` module by ROLE (drawn through the
// L1 kit — fillSurface / drawButton / kit text). Only the pixel LAYOUT metrics (band
// heights, grid/tab specs, insets) live here, shared by the layout/render/input/drag
// seams so draw and hit-test can never drift.
// All color comes from `theme` by role, drawn through the kit. Only pixel layout
// metrics live here, shared by layout/render/input/drag so draw and hit-test can
// never drift.
inline const GridSpec kGrid{/*cellWidth=*/140, /*cellHeight=*/84, /*gap=*/10};
// --- Footer (Phase L, L4) -----------------------------------------------------
// The footer carries a task-cluster of small persistent controls: the narrowed
// [Arrange|Design] mode toggle, a compact per-mode count, the Tail BUTTON (L4 §4 —
// a real kit button, no longer a click-zone), and the set-apart Prune button at the
// right. Taller than the L2 footer to host the toggle segments + button chrome cleanly.
// Layout is the pure footer_bar (left group) + prune_button (right); this is the band height.
// Footer: [Arrange|Design] toggle, per-mode count, Tail button, Prune button
// (footer_bar left group + prune_button right).
inline constexpr int kFooterHeight = 30;
// --- Toolbars (Phase L, L4) ---------------------------------------------------
// TWO task-grouped toolbars, both drawn through the pure action_bar module:
// * kTopToolbarHeight — the TOP toolbar (capture + placement clusters) at the very top of
// the client, where the eye lands (L4 §1). Replaces the L2 mode-switch header there.
// * kBottomToolbarHeight — the BOTTOM toolbar (Design-View tag/switch verbs) directly above
// the footer (L4 §2). This is the L2 action-bar band, repurposed.
inline constexpr int kTopToolbarHeight = 28; // single-row label face (L6: keybinding sub-row removed)
inline constexpr int kBottomToolbarHeight = 28; // same shape — both bars consistent
// Two task-grouped toolbars drawn through action_bar: top = capture + placement,
// bottom = Design-View tag/switch verbs, directly above the footer.
inline constexpr int kTopToolbarHeight = 28;
inline constexpr int kBottomToolbarHeight = 28;
// --- Tooltip (Phase L, L5) ----------------------------------------------------
// The custom hover-delay tooltip's timing + approximate text metrics. The delay matches the
// platform convention (~0.5 s) so the tooltip is deliberate, not twitchy; it is driven off the
// OnTimer poll (bankPanelRefresh) + WM_MOUSEMOVE, so no dedicated timer is added. The kit font
// is AA and proportional, so the width is estimated from a per-char average (the tooltip box is
// generous — a slight over/under-estimate only pads the box, never clips the text).
// Hover-delay tooltip timing, driven off the OnTimer poll (no dedicated timer). Kit
// font is proportional, so char width is a generous estimate (pads, never clips).
inline constexpr unsigned int kTooltipDelayMs = 500;
inline constexpr int kTooltipCharPx = 7; // approx px per char at Font::Label (generous)
inline constexpr int kTooltipTextH = 14; // approx line height at Font::Label
inline constexpr int kTooltipCharPx = 7;
inline constexpr int kTooltipTextH = 14;
// --- Vertical split + region headers + tab strip (Phase B4; L4 re-home) -------
//
// The client area, top to bottom (L4): TOP toolbar (kTopToolbarHeight, capture + placement) |
// split body | BOTTOM toolbar (kBottomToolbarHeight, Design-View verbs) | footer
// (kFooterHeight — mode toggle + count + Tail button + Prune). The split body holds the pool
// region (top) and the named-banks region (bottom). Each region opens with a REGION HEADER
// band: a title, the active-bank readout, and a full-height toggle button. The named-banks
// region's header ALSO hosts the LICE tab strip and a "+" create button.
inline constexpr int kRegionHeaderHeight = 24; // per-region title/toggle band
inline constexpr int kTabStripHeight = 26; // the named-banks tab strip band
inline constexpr int kSplitDividerHeight = 3; // the horizontal divider between regions
inline constexpr int kFullHtBtnWidth = 22; // the square full-height toggle button
inline constexpr int kCreateBtnWidth = 22; // the "+" create-bank button
// Client area top to bottom: top toolbar | split body | bottom toolbar | footer.
inline constexpr int kRegionHeaderHeight = 24;
inline constexpr int kTabStripHeight = 26;
inline constexpr int kSplitDividerHeight = 3;
inline constexpr int kFullHtBtnWidth = 22;
inline constexpr int kCreateBtnWidth = 22;
// Tab strip metrics (the pure tab_strip owns the math; these are its inputs).
inline const TabStripSpec kTabSpec{/*tabWidth=*/96, /*chevronWidth=*/20};
// The spec for the far-right More ("") overflow-menu button. One source of truth for its
// geometry + the reserve the action_bar leaves for it (L5).
// Far-right More ("...") overflow-menu button: one source of truth for its
// geometry + the reserve action_bar leaves for it.
inline const MenuButtonSpec kMenuBtnSpec{/*buttonWidth=*/28, /*rightInset=*/6,
/*verticalInset=*/3, /*minLeftInset=*/40};
// The toolbar layout spec (the panel's 8px-grid density decision). One source of truth shared
// by both toolbars' draw and hit-test (identical button shape top and bottom). L5 refinement 5:
// clusterGap widened 16 -> 24 (a 6:1 inter/intra ratio) so semantic groups read AS groups. L6:
// bindingHeight / minSplitHeight removed — buttons are single-row label-only faces now.
// Shared by both toolbars' draw and hit-test (identical button shape top and
// bottom). clusterGap is wider than buttonGap so semantic groups read as groups.
inline const ActionBarSpec kBarSpec{/*buttonWidth=*/108, /*buttonGap=*/4, /*clusterGap=*/24,
/*sidePad=*/8, /*verticalInset=*/3};
// --- Panel state --------------------------------------------------------------
struct CachedThumbnail {
Envelope envelope;
int width = 0;
@@ -276,32 +225,28 @@ struct CachedThumbnail {
// input. The move/copy source is the focused region's displayed bank.
enum class Region { Pool, Banks };
// What a drag is dropping onto, resolved live under the pointer during a drag.
// BanksRegion fires when the pointer is anywhere in the named-banks grid that is NOT
// on a specific tab (tab takes precedence — more specific wins). The resolved bank is
// always shownBankId.
// What a drag is dropping onto, resolved live under the pointer. BanksRegion fires
// when the pointer is anywhere in the named-banks grid that is NOT on a specific tab
// (tab takes precedence); the resolved bank is always shownBankId.
enum class DropKind { None, PoolRegion, Tab, BanksRegion };
// --- Hover model (Phase L, L2) ------------------------------------------------
//
// The hovered interactive element, resolved live in WM_MOUSEMOVE so the kit draws its
// hover state on that element only (the "hover on every interactive element" + "sub-frame
// feedback = the perception of speed" L2 constraint). SWELL exposes no WM_MOUSELEAVE (grep
// of vendor/WDL/WDL/swell — none), so hover is cleared by a move that resolves to None
// rather than a leave message; the panel is Windows-only (D5) but this stays portable-safe.
// hover state on that element only. SWELL exposes no WM_MOUSELEAVE (confirmed: no hit in
// vendor/WDL/WDL/swell), so hover is cleared by a move that resolves to None rather than a
// leave message; the panel is Windows-only but this stays portable-safe.
// `index` disambiguates within a kind (action-bar button index, tab index); -1 when N/A.
enum class HoverKind {
None,
TopBarButton, // a button in the TOP toolbar (index = flat action index into topBarRows)
BottomBarButton, // a button in the BOTTOM toolbar (index = flat action index into bottomBarRows)
MoreButton, // the TOP toolbar's far-right "⋯" overflow-menu button (L5)
TopBarButton, // index = flat action index into topBarRows
BottomBarButton, // index = flat action index into bottomBarRows
MoreButton,
PruneButton,
FullHtPool, // pool region full-height toggle
FullHtBanks, // banks region full-height toggle
CreateBank, // the "+" create-bank button
Tab, // a named-bank tab (index = tab ordinal)
TailButton, // the footer Tail button (L4 §4 — a real button, was a click-zone)
ModeSegment, // a footer mode-toggle segment (index = segment ordinal)
FullHtPool,
FullHtBanks,
CreateBank,
Tab, // index = tab ordinal
TailButton,
ModeSegment, // index = segment ordinal
};
struct Hover {
@@ -312,9 +257,8 @@ struct Hover {
bool operator!=(const Hover& o) const { return !(*this == o); }
};
// The kit interaction state for an interactive element: Hover when this (kind,index) is the
// live hovered element, else Rest. Active/Pressed are decided per-element by the caller (e.g.
// an active tab draws Active regardless of hover); this is the base rest/hover resolver.
// Base rest/hover resolver; Active/Pressed are decided per-element by the caller
// (e.g. an active tab draws Active regardless of hover).
inline InteractionState hoverState(const Hover& hovered, HoverKind kind, int index) {
return (hovered.kind == kind && hovered.index == index) ? InteractionState::Hover
: InteractionState::Rest;
@@ -331,135 +275,83 @@ struct PanelState {
std::unordered_map<std::string, CachedThumbnail> cache;
// --- Selection (per focused region) ---------------------------------------
// One live selection, scoped to `focusedRegion`. Switching regions moves the
// selection with the focus (a click in the other region reseeds it there).
// selection with the focus.
Selection selection;
int selItemCount = 0;
Region focusedRegion = Region::Pool;
// --- Hover (Phase L, L2) --------------------------------------------------
// The live hovered interactive element (WM_MOUSEMOVE resolves it; the kit draws its
// hover state). Repaint fires only when this changes (sub-frame, no per-move jank).
// Resolved on WM_MOUSEMOVE; repaint fires only when this changes.
Hover hovered;
// --- Tooltip (Phase L, L5) ------------------------------------------------
// A custom LICE-kit hover-delay tooltip (NOT the native Win32/SWELL tooltip control): when a
// TOOLTIP-capable element (a toolbar button) stays hovered past kTooltipDelayMs, the panel
// draws a small overlay carrying the full, prefix-stripped action name. hoverSinceTick is the
// GetTickCount() at which the CURRENT hovered element was first entered (reset on every hover
// change); tooltipShown latches once the delay elapses so the OnTimer poll repaints exactly
// once when the tooltip appears. The last-seen pointer pos anchors nothing (the anchor is the
// hovered button's rect), but is kept so the OnTimer path can re-resolve without a live event.
// Custom hover-delay tooltip. hoverSinceTick resets on every hover change;
// tooltipShown latches once the delay elapses so the OnTimer poll repaints once.
unsigned int hoverSinceTick = 0;
bool tooltipShown = false;
// --- Vertical-split state -------------------------------------------------
BankPanelFullHeight fullHeight = BankPanelFullHeight::Split;
// The named bank whose grid the banks region shows (the SHOWN tab) — DISTINCT
// from the active/capture-target bank (book().activeBankId()). Empty when there
// are no named banks. Reconciled each fingerprint pass so it always names a live
// named bank (or is empty).
// The named bank the banks region shows — distinct from the active/capture-target
// bank. Reconciled each fingerprint pass so it always names a live bank (or empty).
std::string shownBankId;
// Tab-strip horizontal scroll offset (px), clamped to the strip's max each frame.
int tabScroll = 0;
// --- Drag (sample move between regions/onto a tab) ------------------------
// A drag begins only after the pointer moves past a threshold from a press that
// landed on a SELECTED grid cell — this is how it is disambiguated from the M5
// multi-select drag (which begins immediately on any grid press). See handleClick/
// onMouseMove. dragging is true once the threshold is crossed.
bool dragArmed = false; // pressed on a selected cell; watching for threshold
bool dragging = false; // threshold crossed; a move-drag is in progress
// A drag begins only after the pointer moves past a threshold from a press
// that landed on a selected grid cell — disambiguates from the multi-select
// drag (which begins immediately on any grid press).
bool dragArmed = false;
bool dragging = false;
int dragStartX = 0, dragStartY = 0;
Region dragSourceRegion = Region::Pool;
std::string dragSourceBankId; // the bank the dragged samples come from
std::vector<std::string> dragSampleIds;// snapshot of the selection at drag start
std::string dragPrimaryId; // the single card grabbed (the focus) — the L7
// reorder/replace subject (see onLBtnUp dispatch)
DropKind dropKind = DropKind::None; // live drop target under the pointer
std::string dragSourceBankId;
std::vector<std::string> dragSampleIds;
std::string dragPrimaryId; // the single card grabbed the reorder/replace subject
DropKind dropKind = DropKind::None;
std::string dropBankId; // destination bank id when dropKind==Tab
// --- L7 in-grid reorder/replace drag --------------------------------------
// The live card gesture resolved by the pure card_drag::decideCardGesture each mouse-
// move (drives the cursor cue AND the drop dispatch), plus the same-bank target slot the
// pointer sits over (>= 0 only for a Reorder/Replace over the source bank's own grid; -1
// otherwise). A Reorder highlights dragTargetSlot's cell; Replace + a live cursor cue
// signal the Alt-over-occupied case. Reset with the rest of the drag state on drop/cancel.
// Resolved each mouse-move by card_drag::decideCardGesture. dragTargetSlot >= 0
// only for a Reorder/Replace over the source bank's own grid.
CardGesture cardGesture = CardGesture::None;
int dragTargetSlot = -1;
// --- S17 drop-and-load (InstrumentDrop) -----------------------------------
// While a SINGLE-capture drag is over REAPER's own UI outside the panel, the drag is an
// InstrumentDrop heading for a track's TCP FX button. The shell hover-tracks the FX
// hotspot; on release over a valid target it adds a ReaSampler 9000 preloaded with the
// dragged capture (no OS drag, no timeline insert). instrumentDropTrack is the last
// resolved FX-hotspot track (null when the pointer is not over an FX button) — read on
// release. Only set/used on Windows (D5); the M11 OsDrag and internal drag are untouched.
// While a single-capture drag is over REAPER's own UI, heading for a track's TCP FX
// button: on release this adds a ReaSampler 9000 preloaded with the capture. Null
// when the pointer is not over an FX button.
MediaTrack* instrumentDropTrack = nullptr;
// --- Tail-mode toggle -----------------------------------------------------
// The authoritative tail setting lives in ReaSamplerSession (session->tail()),
// NOT in panel state, so it travels inside the .rpp (persist serializes it on save,
// restores it on project load). The panel reads it for drawing and mutates it via
// the footer click (cycle mode) and scroll-wheel (Manual fine-adjust), marking the
// project dirty so the choice saves. bankPanelTailSetting is the read seam for the
// capture actions. Held here only through the session pointer above.
// Authoritative tail setting lives in ReaSamplerSession, not here; panel reads it for
// drawing and mutates via footer click / scroll-wheel. bankPanelTailSetting is the
// capture actions' read seam.
// --- Audition preview -----------------------------------------------------
preview_register_t preview{};
PCM_source* previewSrc = nullptr;
bool previewActive = false;
bool previewInited = false; // guards double init / deinit
// --- New-content detection (D2 Wave 2) ------------------------------------
//
// Each timer tick diffs the live track+item GUID set against the previous tick to
// auto-tag content created SINCE the last tick into the then-active mode. The
// baseline carries the first-poll-after-open guard (GuidBaseline self-arms on its
// first observe()) so pre-existing content is never mass-tagged (it stays Arrange).
//
// Project-load re-arm is driven by persist's AUTHORITATIVE load lifecycle, NOT by a
// pointer compare here. main.cpp calls bankPanelNotifyProjectLoaded() on the exact
// tick persist restores a project's membership + active mode (the same tick it
// reapplies the active mode); that sets reloadPending so the NEXT detect tick this
// same tick re-baselines against the fully-loaded set and reports nothing new. This
// replaces the former `proj != lastProject` re-arm, which used a WEAKER signal than
// persist (pointer-only vs persist's GUID-primary identity) and so missed a load onto
// a RECYCLED ReaProject* address — the just-loaded project's pre-existing tracks then
// diffed against the previous project's stale baseline and were mass-tagged into the
// active mode (the reload-mis-tag bug). Coordinating with persist's signal makes the
// two identity checks agree by construction.
//
// Lives for the extension's lifetime alongside the session, independent of panel
// open/close — detection must run whether or not the dock is visible (content is
// created in the arrange, not the panel).
// auto-tag new content. GuidBaseline self-arms on first observe() so pre-existing
// content is never mass-tagged. Project-load re-arm is driven by persist's load
// signal, not a ReaProject* compare — a recycled address previously mis-tagged tracks.
GuidBaseline contentBaseline;
bool reloadPending = false; // set by bankPanelNotifyProjectLoaded; drained next detect tick
};
// The one shared panel state blob. Defined in panel_window.cpp (the lifecycle owner).
// Defined in panel_window.cpp (the lifecycle owner).
extern PanelState g_panel;
// --- L7 slot-order display bridge ---------------------------------------------
//
// L7 re-maps cell index <-> sample identity: the grid draws in the bank's persisted
// SlotMap order (sparse, gap-preserving), NOT BankModel insertion order. regionDisplay
// (panel_layout.cpp) is the single place that resolves a region's display, composed
// purely from bank_book's slot order (orderedSampleIds) + card_drag's sparse slot rects
// (computeSlotRects) — the shell adds no layout math of its own.
// The grid draws in the bank's persisted SlotMap order (sparse, gap-preserving),
// NOT BankModel insertion order. regionDisplay (panel_layout.cpp) is the single
// place that resolves a region's display.
//
// TWO INDEX SPACES the whole panel must keep straight:
// * SLOT — a display position 0..maxSlot; gaps are empty slots that draw as empty
// cells and are valid drop targets. This is what pixels/hit-tests speak.
// * SELECTION — the DENSE occupied-ordinal [0, occupied) space the pure Selection /
// applyClick / navigate reason in. Selection index i <-> orderedIds[i].
// Keyboard navigation therefore traverses ONLY occupied cells and SKIPS
// gaps (spec: skip-vs-land-on-gap is unspecified -> skip, documented here).
// RegionDisplay carries both plus the translation between them, resolved FRESH each call
// (never cached across a mutation, per the reference-invalidation guardrail).
// * SLOT — display position 0..maxSlot; gaps are empty, valid drop targets.
// What pixels/hit-tests speak.
// * SELECTION — the dense occupied-ordinal [0, occupied) space the pure
// Selection/applyClick/navigate reason in. Selection index i <->
// orderedIds[i]. Keyboard nav therefore skips gaps.
// RegionDisplay carries both plus the translation, resolved fresh each call (never
// cached across a mutation, per the reference-invalidation guardrail).
struct RegionDisplay {
std::vector<std::string> orderedIds; // occupied ids in slot order (selection space)
std::vector<SlotCellRect> slotRects; // one rect per slot 0..maxSlot, viewport coords
@@ -486,27 +378,18 @@ struct RegionDisplay {
int occupiedCount() const { return static_cast<int>(orderedIds.size()); }
};
// --- Toolbar row vocabulary (Phase L, L4/L5/L6) --------------------------------
//
// One action button: its channel-AGNOSTIC command-id suffix (composed with the channel prefix
// at fire time — never a hardcoded numeric id), its terse on-button FACE label, its full action
// NAME for the hover tooltip (already prefix-stripped — the "ReaSampler:" display prefix is
// dropped at build), and the task cluster it belongs to. The order of a toolbar's row list IS
// the flat action index the pure action_bar slots carry, so each list is built cluster-by-cluster
// in its toolbar's cluster order. Built by panel_layout (topBarRows / bottomBarRows /
// overflowMenuRows); consumed by the render draw, the input click routing, and the drag hover.
// One action button. Row order IS the flat action index the pure action_bar slots
// carry. Built by panel_layout; consumed by render, input, drag.
struct ActionBarRow {
std::string suffix;
std::string shortLabel;
std::string fullName;
ActionCluster cluster = ActionCluster::Capture;
bool enabled = true; // L5: opposite-mode gate for the bottom-bar tag buttons; always true
// for the top bar (its actions are unconditional triggers).
bool enabled = true; // opposite-mode gate for bottom-bar tag buttons; always true
// for the top bar (unconditional triggers)
};
// --- Shared one-liner helpers --------------------------------------------------
// Modifier state at event time. Alt = the L7 replace modifier.
// Modifier state at event time. Alt = the replace modifier.
inline bool ctrlDown() { return (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0; }
inline bool shiftDown() { return (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; }
inline bool altDown() { return (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; }
@@ -515,7 +398,7 @@ inline void invalidatePanel() {
if (g_panel.hwnd) InvalidateRect(g_panel.hwnd, nullptr, FALSE);
}
// --- Cross-seam contract (grouped by OWNING TU; all plain free functions) ------
// Cross-seam contract, grouped by owning TU; all plain free functions.
// panel_bank_ops.cpp — book/bank accessors + the bank-CRUD verbs + menus.
BankBook* book();
@@ -536,7 +419,7 @@ void showSelectionMenu(int screenX, int screenY);
void showMoreMenu();
// panel_layout.cpp — toolbar/footer/menu rects, row/cluster builders, split geometry,
// region rects, the L7 display bridge. Draw and hit-test both call these so they never drift.
// region rects, the display bridge. Draw and hit-test both call these so they never drift.
int modeCount();
MenuButtonRect topMenuButtonRect(int w);
ActionBarRect topToolbarActionRect(int w);
@@ -579,8 +462,8 @@ const Envelope& thumbnailFor(const Sample& sample, int width, const std::string&
bool refreshFingerprint();
void reconcileShownBank();
// panel_audition.cpp — the preview engine (HOT PATH: direct call-through, never
// virtual, no added header->TU indirection — T4-28 / Q-W2 guardrail).
// panel_audition.cpp — the preview engine (hot path: direct call-through, never
// virtual, no added header->TU indirection).
void initPreview();
void deinitPreview();
void stopAudition();
@@ -595,7 +478,7 @@ void registerAccel();
void unregisterAccel();
// panel_drag.cpp — the card-drag/hover state machine (pure mirror: core/ui/card_drag).
// Per-mouse-move work stays plain free-function calls (T4-28).
// Per-mouse-move work stays plain free-function calls.
void onMouseMove(int x, int y);
void onLBtnUp(int x, int y);
void handleRightClick(int x, int y);