610 lines
30 KiB
C++
610 lines
30 KiB
C++
#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 <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).
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <pthread.h>
|
|
#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<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 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<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 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<std::string> orderedIds; // occupied ids in slot order (selection space)
|
|
std::vector<SlotCellRect> 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<int>(orderedIds.size()) || !bank) return -1;
|
|
return bank->slots.slotOf(orderedIds[static_cast<std::size_t>(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<int>(i);
|
|
return -1;
|
|
}
|
|
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.
|
|
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<const Bank*> namedBanks();
|
|
std::string currentProjectDir();
|
|
void doCreateBank();
|
|
void transferSamples(const std::vector<std::string>& sampleIds,
|
|
const std::string& srcBankId, const std::string& destBankId,
|
|
bool copy);
|
|
void removeSamples(const std::vector<std::string>& sampleIds,
|
|
const std::string& srcBankId);
|
|
std::vector<std::string> focusedSelectionIds();
|
|
std::vector<std::string> 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<ActionBarRow> topBarRows();
|
|
std::vector<ActionBarRow> bottomBarRows();
|
|
std::vector<ActionBarRow> overflowMenuRows();
|
|
std::vector<ClusterSpec> actionBarClusters(const std::vector<ActionBarRow>& rows);
|
|
int resolveBarCommandId(const ActionBarRow& row);
|
|
int toolbarHit(int x, int y, const ActionBarRect& bar, const std::vector<ActionBarRow>& 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
|