Q-W2: split bank_panel.cpp (3459 LOC) into eight shell/panel TUs — reasampler::panel internals, per-seam public headers, shim retired; zero behavior change, 60/60 green
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
// panel_window.cpp — the window-lifecycle seam of the docked bank panel (Q-W2 split
|
||||
// of bank_panel.cpp; M5 Wave A). Owns the SWELL dialog (IDD_BANK_PANEL) docked via
|
||||
// DockWindowAddEx / undocked via DockWindowRemove, the dialog proc that routes
|
||||
// messages to the input/drag/render/audition seams, the S8 OS drop-target opt-in
|
||||
// (WM_DROPFILES -> ingest), and the shared PanelState blob's definition.
|
||||
//
|
||||
// READ-ONLY of the TIMELINE (load-bearing principle): the panel never inserts into
|
||||
// the arrange. Channel-qualified dock identity (Phase V, V4): title + persisted-
|
||||
// position identstr both come from app_version.
|
||||
//
|
||||
// Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h
|
||||
// WITHOUT REAPERAPI_IMPLEMENT — main.cpp owns the API pointers; here they are
|
||||
// extern (CLAUDE.md §contract). DAW-verified, not unit tested.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "shell/panel/panel_state.h"
|
||||
#include "shell/panel/panel_window.h"
|
||||
|
||||
#include "shell/panel/draw_kit.h" // kitFontsInit/Shutdown — the kit's cached AA fonts (L1)
|
||||
#include "ingest.h" // ingestDroppedFiles — S8 drop-onto-panel ingest
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windowsx.h> // GET_X_LPARAM / GET_Y_LPARAM (SWELL supplies them on mac/linux)
|
||||
#include <shellapi.h> // DragAcceptFiles / DragQueryFile / DragFinish — S8 drop ingest
|
||||
#endif
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
#define REAPERAPI_MINIMAL
|
||||
#define REAPERAPI_WANT_DockWindowAddEx
|
||||
#define REAPERAPI_WANT_DockWindowActivate
|
||||
#define REAPERAPI_WANT_DockWindowRemove
|
||||
#define REAPERAPI_WANT_GetMainHwnd
|
||||
#include "reaper_plugin_functions.h"
|
||||
|
||||
// main.cpp owns the module instance handle.
|
||||
extern REAPER_PLUGIN_HINSTANCE g_hInst;
|
||||
|
||||
namespace reasampler::panel {
|
||||
|
||||
// The one shared panel state blob (declared extern in panel_state.h). Defined here —
|
||||
// the lifecycle seam owns the state's lifetime, mirroring the old single-TU global.
|
||||
PanelState g_panel;
|
||||
|
||||
// --- Dialog proc + docking ----------------------------------------------------
|
||||
|
||||
// Decodes a WM_DROPFILES HDROP into the dropped file paths (absolute, OS-native) and hands
|
||||
// them to the S8 ingest path. Multi-file drop: ingestDroppedFiles imports all into the active
|
||||
// bank (bank-fill only — no assignment to any live instance). Always DragFinish's the HDROP
|
||||
// (frees the shell-allocated drop buffer) on every path. DragQueryFile(hDrop, 0xFFFFFFFF, ...)
|
||||
// returns the file count; then each path is queried by index. Both Win32 and SWELL expose
|
||||
// DragQueryFile/DragFinish with this contract.
|
||||
void handleDropFiles(HDROP hDrop) {
|
||||
std::vector<std::string> paths;
|
||||
const UINT count = DragQueryFile(hDrop, 0xFFFFFFFF, nullptr, 0);
|
||||
paths.reserve(count);
|
||||
for (UINT i = 0; i < count; ++i) {
|
||||
// Query the required length first (excludes the NUL), then read into a sized buffer.
|
||||
const UINT len = DragQueryFile(hDrop, i, nullptr, 0);
|
||||
if (len == 0) continue;
|
||||
std::vector<char> buf(static_cast<std::size_t>(len) + 1, '\0');
|
||||
DragQueryFile(hDrop, i, buf.data(), static_cast<UINT>(buf.size()));
|
||||
std::string p(buf.data());
|
||||
if (!p.empty()) paths.push_back(std::move(p));
|
||||
}
|
||||
DragFinish(hDrop);
|
||||
if (!paths.empty()) ingestDroppedFiles(paths);
|
||||
}
|
||||
|
||||
WDL_DLGRET dlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||
switch (msg) {
|
||||
case WM_DROPFILES:
|
||||
// S8 drop-onto-panel ingest: OS file drop on the docked panel HWND -> import
|
||||
// into the active bank (bank-fill only). wParam is the HDROP.
|
||||
handleDropFiles(reinterpret_cast<HDROP>(wParam));
|
||||
return 0;
|
||||
case WM_PAINT: {
|
||||
PAINTSTRUCT ps;
|
||||
HDC hdc = BeginPaint(hwnd, &ps);
|
||||
paintPanel(hwnd, hdc);
|
||||
EndPaint(hwnd, &ps);
|
||||
return 0;
|
||||
}
|
||||
case WM_LBUTTONDOWN: {
|
||||
SetFocus(hwnd);
|
||||
handleClick(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
||||
return 0;
|
||||
}
|
||||
case WM_MOUSEMOVE:
|
||||
onMouseMove(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
||||
return 0;
|
||||
case WM_LBUTTONUP:
|
||||
onLBtnUp(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
||||
return 0;
|
||||
case WM_RBUTTONDOWN:
|
||||
SetFocus(hwnd);
|
||||
handleRightClick(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
||||
return 0;
|
||||
case WM_CAPTURECHANGED:
|
||||
// Capture lost (pointer left window pre-threshold and released outside, or another
|
||||
// window stole capture mid-drag) — cancel the whole drag as a NO-OP so no stale
|
||||
// state lingers, mirroring onLBtnUp's reset (peer-path symmetry). Nothing is
|
||||
// mutated on a cancel; the cursor is restored to the arrow.
|
||||
if (g_panel.dragArmed || g_panel.dragging) {
|
||||
resetDragState();
|
||||
SetCursor(LoadCursor(nullptr, IDC_ARROW));
|
||||
invalidatePanel();
|
||||
}
|
||||
return 0;
|
||||
case WM_MOUSEWHEEL: {
|
||||
// Fine-adjust the Manual tail length when the wheel is over the footer.
|
||||
// UNLIKE the button messages, WM_MOUSEWHEEL carries SCREEN coordinates in
|
||||
// lParam (Win32 and SWELL agree — swell-generic-gdk.cpp §WM_MOUSEWHEEL), so
|
||||
// convert to client space before hit-testing the footer. The signed wheel
|
||||
// delta is the HIWORD of wParam (SWELL packs it as (delta<<16), delta=+/-120,
|
||||
// matching GET_WHEEL_DELTA_WPARAM). Consume (return 1) only when the footer
|
||||
// handler acts, so scrolling elsewhere in the dock still behaves normally.
|
||||
POINT pt{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
|
||||
ScreenToClient(hwnd, &pt);
|
||||
const int delta = static_cast<short>(HIWORD(wParam));
|
||||
return handleWheel(pt.x, pt.y, delta) ? 1 : 0;
|
||||
}
|
||||
case WM_DESTROY:
|
||||
if (GetCapture() == hwnd) ReleaseCapture();
|
||||
stopAudition();
|
||||
g_panel.selection = Selection{};
|
||||
resetDragState();
|
||||
g_panel.hovered = Hover{};
|
||||
g_panel.tooltipShown = false;
|
||||
g_panel.hwnd = nullptr;
|
||||
g_panel.open = false;
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void openPanel() {
|
||||
if (g_panel.open && g_panel.hwnd) {
|
||||
DockWindowActivate(g_panel.hwnd);
|
||||
return;
|
||||
}
|
||||
initPreview();
|
||||
|
||||
// Create the kit's cached AA fonts before the first paint (Phase L, L1). Idempotent, so
|
||||
// a reopen after closePanel (which leaves the fonts alive) is a cheap no-op; the fonts
|
||||
// are torn down once at bankPanelShutdown. All panel text draws through these.
|
||||
kitFontsInit();
|
||||
|
||||
g_panel.hwnd = CreateDialogParam(g_hInst, MAKEINTRESOURCE(IDD_BANK_PANEL),
|
||||
GetMainHwnd(), dlgProc, 0);
|
||||
if (!g_panel.hwnd) return;
|
||||
|
||||
// Channel-qualified dock identity (Phase V, V4). The title and the persisted-position
|
||||
// identstr both come from app_version, so a beta panel is distinguishable ("ReaSampler
|
||||
// Bank beta") and does not fight over stable's saved dock slot (the identstr is a
|
||||
// REAPER-global collision surface — it keys the persisted dock position).
|
||||
DockWindowAddEx(g_panel.hwnd, dockTitle().c_str(), dockIdent().c_str(), true);
|
||||
DockWindowActivate(g_panel.hwnd);
|
||||
g_panel.open = true;
|
||||
|
||||
// S8: accept OS file drops on the panel HWND (WM_DROPFILES routes to handleDropFiles).
|
||||
// DragAcceptFiles is a native Win32 shell call (shellapi.h); SWELL does NOT expose it,
|
||||
// so the opt-in is Windows-only here. The primary/shipped platform is Windows (the VST3
|
||||
// instrument the drop assigns to is Windows-only, D5); a mac/linux drop-registration
|
||||
// surface is out of scope for this dispatch. WM_DROPFILES handling itself uses
|
||||
// DragQueryFile/DragFinish, which SWELL DOES provide, so a drop delivered by other means
|
||||
// would still ingest — only the accept opt-in is gated.
|
||||
#ifdef _WIN32
|
||||
DragAcceptFiles(g_panel.hwnd, TRUE);
|
||||
#endif
|
||||
|
||||
registerAccel();
|
||||
|
||||
reconcileShownBank();
|
||||
refreshFingerprint();
|
||||
}
|
||||
|
||||
void closePanel() {
|
||||
if (GetCapture() == g_panel.hwnd) ReleaseCapture();
|
||||
stopAudition();
|
||||
g_panel.selection = Selection{};
|
||||
resetDragState();
|
||||
unregisterAccel();
|
||||
if (g_panel.hwnd) {
|
||||
DockWindowRemove(g_panel.hwnd);
|
||||
DestroyWindow(g_panel.hwnd);
|
||||
g_panel.hwnd = nullptr;
|
||||
}
|
||||
g_panel.open = false;
|
||||
}
|
||||
|
||||
} // namespace reasampler::panel
|
||||
|
||||
// --- Public API (the lifecycle seam — panel_window.h) --------------------------
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
void bankPanelInit(ReaSamplerSession* session) {
|
||||
panel::g_panel.session = session;
|
||||
}
|
||||
|
||||
// Returns true only when the panel window is actually visible to the user right now.
|
||||
// IsWindowVisible() returns false when the docker is hidden via Alt+D even though the
|
||||
// HWND and g_panel.open are still live — the live query is the source of truth for
|
||||
// toggle decisions and the Actions-list checkmark (OnToggleAction in main.cpp).
|
||||
static bool panelEffectivelyVisible() {
|
||||
return panel::g_panel.hwnd && IsWindowVisible(panel::g_panel.hwnd);
|
||||
}
|
||||
|
||||
void bankPanelToggle() {
|
||||
// Decide from live visibility, not the cached g_panel.open flag.
|
||||
// Alt+D hides the docker without destroying the window, leaving g_panel.open
|
||||
// stale (true) while the panel is gone. Using IsWindowVisible avoids the
|
||||
// double-fire needed to re-show the panel after a docker hide.
|
||||
if (panelEffectivelyVisible())
|
||||
panel::closePanel();
|
||||
else
|
||||
panel::openPanel();
|
||||
}
|
||||
|
||||
bool bankPanelIsOpen() {
|
||||
// Derive from live window state so the Actions-list checkmark stays honest
|
||||
// even after Alt+D hides the docker without notifying the extension.
|
||||
return panelEffectivelyVisible();
|
||||
}
|
||||
|
||||
void bankPanelInvalidate() {
|
||||
if (panel::g_panel.hwnd) InvalidateRect(panel::g_panel.hwnd, nullptr, FALSE);
|
||||
}
|
||||
|
||||
void bankPanelShutdown() {
|
||||
panel::closePanel();
|
||||
panel::deinitPreview();
|
||||
kitFontsShutdown(); // free the kit's cached AA fonts + their owned HFONTs (L1)
|
||||
panel::g_panel.cache.clear();
|
||||
panel::g_panel.session = nullptr;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
Reference in New Issue
Block a user