213 lines
7.0 KiB
C++
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
|