Files
reasampler/src/shell/panel/panel_window.cpp
T

213 lines
7.0 KiB
C++

// panel_window.cpp — window-lifecycle seam of the docked bank panel. Owns the SWELL
// dialog (docked via DockWindowAddEx / undocked via DockWindowRemove), the dialog
// proc routing to the input/drag/render/audition seams, the OS drop-target opt-in
// (WM_DROPFILES -> ingest), and the shared PanelState blob's definition. The panel
// never inserts into the arrange. Dock title + persisted-position identstr both
// come from app_version (channel-qualified).
//
// main.cpp owns the API pointers; here they are extern. 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"
#include "shell/actions/ingest.h"
#ifdef _WIN32
#include <windowsx.h> // GET_X_LPARAM / GET_Y_LPARAM (SWELL supplies them on mac/linux)
#include <shellapi.h> // DragAcceptFiles / DragQueryFile / DragFinish
#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 {
// Defined here — the lifecycle seam owns the state's lifetime.
PanelState g_panel;
namespace {
// DragQueryFile(hDrop, 0xFFFFFFFF, ...) returns the file count; each path is then
// queried by index (length first, excludes NUL, then a sized buffer). DragFinish
// always frees the shell-allocated drop buffer. Multi-file drop imports all into
// the active bank (bank-fill only — no assignment to any live instance).
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) {
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:
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 pre-threshold, or another window stole it
// mid-drag) — cancel the drag as a no-op, mirroring onLBtnUp's reset.
if (g_panel.dragArmed || g_panel.dragging) {
resetDragState();
SetCursor(LoadCursor(nullptr, IDC_ARROW));
invalidatePanel();
}
return 0;
case WM_MOUSEWHEEL: {
// Unlike button messages, WM_MOUSEWHEEL carries SCREEN coords in lParam
// (Win32 and SWELL agree), so convert to client space first. Wheel delta
// is the HIWORD of wParam. Consume (return 1) only when the footer acts.
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();
// Idempotent — a reopen after closePanel (fonts left alive) is a cheap no-op;
// torn down once at bankPanelShutdown.
kitFontsInit();
g_panel.hwnd = CreateDialogParam(g_hInst, MAKEINTRESOURCE(IDD_BANK_PANEL),
GetMainHwnd(), dlgProc, 0);
if (!g_panel.hwnd) return;
// identstr is a REAPER-global collision surface keying the persisted dock
// position — channel-qualified so beta doesn't fight over stable's slot.
DockWindowAddEx(g_panel.hwnd, dockTitle().c_str(), dockIdent().c_str(), true);
DockWindowActivate(g_panel.hwnd);
g_panel.open = true;
// DragAcceptFiles is native Win32 (shellapi.h); SWELL doesn't expose it, so the
// accept opt-in is Windows-only. DragQueryFile/DragFinish ARE SWELL-provided,
// so a drop delivered by other means would still ingest.
#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
} // namespace reasampler::panel
namespace reasampler {
void bankPanelInit(ReaSamplerSession* session) {
panel::g_panel.session = session;
}
// Alt+D hides the docker without destroying the window, leaving HWND/g_panel.open
// live but IsWindowVisible false — the live query is the source of truth for
// toggle decisions and the Actions-list checkmark.
static bool panelEffectivelyVisible() {
return panel::g_panel.hwnd && IsWindowVisible(panel::g_panel.hwnd);
}
void bankPanelToggle() {
if (panelEffectivelyVisible())
panel::closePanel();
else
panel::openPanel();
}
bool bankPanelIsOpen() {
return panelEffectivelyVisible();
}
void bankPanelInvalidate() {
if (panel::g_panel.hwnd) InvalidateRect(panel::g_panel.hwnd, nullptr, FALSE);
}
void bankPanelShutdown() {
panel::closePanel();
panel::deinitPreview();
kitFontsShutdown();
panel::g_panel.cache.clear();
panel::g_panel.session = nullptr;
}
} // namespace reasampler