#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). // // 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) — this // header itself does not directly include the interim core/namespaces.h shim // (Q-W2 retires that direct dependency for this module; Q-W4 retired the // actions.h carrier with the actions split). Several panel TUs still pull the // shim in TRANSITIVELY via persist.h/ingest.h/draw_kit.h/view.h; only // panel_thumbnails.cpp and panel_audition.cpp are shim-free end to end. // Nothing HERE depends on it either way. // // 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. #include #include #include #include // 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). #ifdef _WIN32 #include #else #include #endif #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). #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 "shell/panel/panel_layout.h" // BankPanelFullHeight — the split-state enum namespace reasampler { 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 (NOT the core/namespaces.h shim) keep those // references valid while documenting each symbol's Q-W1 home. Flat-`reasampler` // symbols (BankBook / ViewModeModel / the draw_kit shell / persistBankOp / ...) // resolve via the enclosing namespace and need no using. // core/ui using ui::ActionBarRect; using ui::ActionBarSlot; using ui::ActionBarSpec; using ui::ActionCluster; using ui::ButtonRect; using ui::CardGesture; using ui::CellRect; using ui::ClusterSpec; using ui::CursorCue; using ui::DragGesture; using ui::DragModifiers; using ui::DragState; using ui::DropRegion; using ui::FooterBarLayout; using ui::FooterBarSpec; using ui::FooterHit; using ui::FooterRect; using ui::GridSpec; using ui::InteractionState; using ui::KitBox; using ui::KitButtonBox; using ui::KitColor; using ui::MenuBarRect; using ui::MenuButtonRect; using ui::MenuButtonSpec; using ui::MusicalLength; using ui::NavKey; using ui::PanelClientRect; using ui::PruneButtonSpec; using ui::ResolvedSample; using ui::Role; using ui::Selection; using ui::SlotCellRect; using ui::TabHit; using ui::TabHitKind; using ui::TabRect; using ui::TabStripLayout; using ui::TabStripRect; using ui::TabStripSpec; using ui::TagTarget; using ui::ThumbnailKey; using ui::TooltipBox; using ui::TooltipSpec; using ui::applyClick; using ui::assemblePathList; using ui::clampTabScroll; using ui::columnsForWidth; using ui::computeBarSlots; using ui::computeFooterBar; using ui::computeMenuButton; using ui::computePruneButton; using ui::computeSlotRects; using ui::computeSlotRectsForDrop; using ui::computeTabRects; using ui::computeTabStripLayout; using ui::computeTooltip; using ui::cursorForGesture; using ui::decideCardGesture; using ui::decideGesture; using ui::formatBarsBeats; using ui::formatSecondsMs; using ui::hitTestActionBar; using ui::hitTestFooterBar; using ui::hitTestMenuButton; using ui::hitTestPruneButton; using ui::hitTestSlot; using ui::hitTestTabStrip; using ui::menuButtonReserve; using ui::navigate; using ui::roleColor; using ui::stripActionPrefix; using ui::tagButtonEnabled; using ui::thumbnailKeyString; using ui::waveformColumnCount; // core/view using view::GuidBaseline; using view::HeaderRect; using view::SegmentRect; using view::computeSegmentRects; using view::hitTestSegment; using view::isOnManualLane; // core/capture using capture::CaptureActionDef; using capture::TailMode; using capture::TailSetting; using capture::adjustManualMs; using capture::captureActionTable; using capture::clampManualMs; using capture::cycleTailMode; using capture::kManualStepMs; using capture::normalizeSlashes; using capture::resolveBankFile; using capture::tailToggleLabel; // core/audio using audio::Envelope; using audio::computeEnvelope; // core/model using model::BankModel; using model::Sample; // core/version using version::actionDisplayPrefix; using version::appVersion; using version::channelCommandId; using version::dockIdent; 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. 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. 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 // --- 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). 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 // --- 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 // 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). 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. inline const ActionBarSpec kBarSpec{/*buttonWidth=*/108, /*buttonGap=*/4, /*clusterGap=*/24, /*sidePad=*/8, /*verticalInset=*/3}; // --- Panel state -------------------------------------------------------------- struct CachedThumbnail { Envelope envelope; int width = 0; }; // Which of the two split regions currently owns the selection / receives keyboard // 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. 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. // `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) 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) }; struct Hover { HoverKind kind = HoverKind::None; int index = -1; bool operator==(const Hover& o) const { return kind == o.kind && index == o.index; } 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. inline InteractionState hoverState(const Hover& hovered, HoverKind kind, int index) { return (hovered.kind == kind && hovered.index == index) ? InteractionState::Hover : InteractionState::Rest; } struct PanelState { ReaSamplerSession* session = nullptr; HWND hwnd = nullptr; bool open = false; std::string bankFingerprint; std::uint64_t generation = 0; std::unordered_map 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 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). 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. 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). 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 int dragStartX = 0, dragStartY = 0; Region dragSourceRegion = Region::Pool; std::string dragSourceBankId; // the bank the dragged samples come from std::vector 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 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. 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. 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. // --- 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). 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). 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. // // 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). struct RegionDisplay { std::vector orderedIds; // occupied ids in slot order (selection space) std::vector slotRects; // one rect per slot 0..maxSlot, viewport coords const Bank* bank = nullptr; // The id occupying `slot`, or "" for an empty slot / out of range. std::string idAtSlot(int slot) const { return bank ? bank->slots.idAt(slot) : std::string{}; } // The slot a selection ordinal `sel` maps to, or -1. orderedIds[sel] -> its slot. int slotForSelection(int sel) const { if (sel < 0 || sel >= static_cast(orderedIds.size()) || !bank) return -1; return bank->slots.slotOf(orderedIds[static_cast(sel)]); } // The selection ordinal for `slot` (index of its occupant in orderedIds), or -1 when // the slot is empty. Inverse of slotForSelection. int selectionForSlot(int slot) const { const std::string id = idAtSlot(slot); if (id.empty()) return -1; for (std::size_t i = 0; i < orderedIds.size(); ++i) if (orderedIds[i] == id) return static_cast(i); return -1; } int occupiedCount() const { return static_cast(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. 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). }; // --- Shared one-liner helpers -------------------------------------------------- // Modifier state at event time. Alt = the L7 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; } inline void invalidatePanel() { if (g_panel.hwnd) InvalidateRect(g_panel.hwnd, nullptr, FALSE); } // --- 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(); const BankModel* indexForRegion(Region r); std::string bankIdForRegion(Region r); std::vector namedBanks(); std::string currentProjectDir(); void doCreateBank(); void transferSamples(const std::vector& sampleIds, const std::string& srcBankId, const std::string& destBankId, bool copy); void removeSamples(const std::vector& sampleIds, const std::string& srcBankId); std::vector focusedSelectionIds(); std::vector resolveDragPathsForOs(); void showTabMenu(int screenX, int screenY, const std::string& bankId); 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. int modeCount(); MenuButtonRect topMenuButtonRect(int w); ActionBarRect topToolbarActionRect(int w); RECT panelFooter(int w, int h); FooterBarLayout footerBarLayoutFor(int w, int h); ButtonRect pruneButtonRectFor(int w, int h); bool pointInFooter(int x, int y); ActionBarRect bottomToolbarRect(int w, int h); RECT splitBody(int w, int h); bool poolShown(); bool banksShown(); RECT poolRegionRect(int w, int h); RECT banksRegionRect(int w, int h); RECT regionHeaderRect(const RECT& region); TabStripRect banksTabStripRect(const RECT& region); RECT regionGridRect(const RECT& region, bool isBanks); RECT fullHtBtnRect(const RECT& region); RECT createBtnRect(const RECT& region); RegionDisplay regionDisplay(const RECT& region, bool isBanks, Region reg); RegionDisplay focusedDisplay(); bool regionAt(int x, int y, Region& out); int footerToggleSegmentHit(int x, int y, int w, int h); int columnsForRegion(Region reg); std::vector topBarRows(); std::vector bottomBarRows(); std::vector overflowMenuRows(); std::vector actionBarClusters(const std::vector& rows); int resolveBarCommandId(const ActionBarRow& row); int toolbarHit(int x, int y, const ActionBarRect& bar, const std::vector& rows); bool currentTooltip(int w, int h, std::string& textOut, int& ax, int& ay, int& aw, int& ah); // panel_render.cpp — the full LICE paint (drawn last-to-front into the caller's // double buffer; BitBlt'd once by paintPanel). void paintPanel(HWND hwnd, HDC hdc); // panel_thumbnails.cpp — thumbnail compute + cache, and the bank-change fingerprint // pass that owns the cache's generation key (bumps generation, clears the cache, and // reconciles selection/shown-bank on any book mutation). const Envelope& thumbnailFor(const Sample& sample, int width, const std::string& projectDir); 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). void initPreview(); void deinitPreview(); void stopAudition(); void startAudition(int idx); // panel_input.cpp — click/wheel/key routing, accelerator, new-content detection, // and the session-tail read/mutate helpers. TailSetting currentTail(); void handleClick(int x, int y); bool handleWheel(int x, int y, int delta); 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). void onMouseMove(int x, int y); void onLBtnUp(int x, int y); void handleRightClick(int x, int y); void resetDragState(); void maybeShowTooltip(); } // namespace reasampler::panel