feat(m11): native OS drag-out of bank samples (copy-only)
Pure drag_out (gesture boundary + path-list assembly) + Windows OLE CF_HDROP / SWELL file-list shell; bank_panel hands off when a dragged selection leaves the client area. COPY-ONLY mask; internal drag intact.
This commit is contained in:
@@ -48,6 +48,8 @@
|
||||
|
||||
#include "action_buttons.h" // pure button-strip layout + label format (M11)
|
||||
#include "actions.h" // persistBankOp — shared undo-block wrapper (R-B panel path)
|
||||
#include "drag_out.h" // pure gesture-boundary decision + path-list assembly (M11)
|
||||
#include "drag_out_win.h" // OLE / SWELL drag-out initiation seam (M11)
|
||||
#include "app_version.h" // channelCommandId — compose the named-command lookup string (M11)
|
||||
#include "bank_book.h"
|
||||
#include "bank_grid.h"
|
||||
@@ -1616,6 +1618,34 @@ std::vector<std::string> focusedSelectionIds() {
|
||||
return ids;
|
||||
}
|
||||
|
||||
// Resolves the ARMED drag payload (g_panel.dragSampleIds, from g_panel.dragSourceBankId) to
|
||||
// the absolute, existing-file path list for a native OS drag-out (M11). Reuses the SAME M4
|
||||
// path machinery the panel uses for audition/insert (resolveBankFile over the current
|
||||
// project dir) — no temp copies; the drag points straight at the on-disk bank files. Each
|
||||
// id is looked up in its SOURCE bank's index (the payload's origin, not the focused region,
|
||||
// which can differ once the pointer roams), resolved, stat'd, then handed to the pure
|
||||
// drag_out::assemblePathList for dedupe + skip-missing/unresolved policy. Read-only: no
|
||||
// mutation of sample / index / selection (invariant #2).
|
||||
std::vector<std::string> resolveDragPathsForOs() {
|
||||
std::vector<ResolvedSample> resolved;
|
||||
BankBook* b = book();
|
||||
if (!b) return {};
|
||||
const BankIndex* idx = b->index(g_panel.dragSourceBankId);
|
||||
if (!idx) return {};
|
||||
|
||||
const std::string projectDir = currentProjectDir();
|
||||
resolved.reserve(g_panel.dragSampleIds.size());
|
||||
for (const std::string& sid : g_panel.dragSampleIds) {
|
||||
const Sample* s = idx->query(sid);
|
||||
if (!s) continue; // stale id — the pure layer would skip it anyway; nothing to resolve
|
||||
ResolvedSample rs;
|
||||
rs.absolutePath = resolveBankFile(projectDir, s->relativePath);
|
||||
rs.fileExists = !rs.absolutePath.empty() && fs::exists(fs::path(rs.absolutePath));
|
||||
resolved.push_back(std::move(rs));
|
||||
}
|
||||
return assemblePathList(resolved).paths;
|
||||
}
|
||||
|
||||
// --- Popup menus --------------------------------------------------------------
|
||||
//
|
||||
// SWELL/Win32 both expose CreatePopupMenu / InsertMenu (SWELL aliases SWELL_InsertMenu
|
||||
@@ -2076,6 +2106,37 @@ void onMouseMove(int x, int y) {
|
||||
}
|
||||
}
|
||||
if (g_panel.dragging) {
|
||||
// M11 gesture boundary (invariant #4): while a drag with samples is under way, the
|
||||
// moment the pointer LEAVES the panel client area the gesture becomes OS-bound —
|
||||
// hand the payload to the native OS drag. Inside the client area it stays the
|
||||
// existing internal bank-to-bank drag, byte-identical. The boundary decision is the
|
||||
// pure drag_out::decideGesture (drag state + pointer + client rect).
|
||||
RECT cr{};
|
||||
GetClientRect(g_panel.hwnd, &cr);
|
||||
const PanelClientRect client{cr.left, cr.top, cr.right - cr.left, cr.bottom - cr.top};
|
||||
const DragState st{/*dragging=*/true, /*hasArmedSamples=*/!g_panel.dragSampleIds.empty()};
|
||||
if (decideGesture(x, y, client, st) == DragGesture::OsDrag) {
|
||||
// Resolve the payload to existing on-disk paths BEFORE tearing down internal
|
||||
// drag state (the resolver reads dragSourceBankId / dragSampleIds).
|
||||
const std::vector<std::string> paths = resolveDragPathsForOs();
|
||||
|
||||
// Reset internal drag state and release capture NOW: DoDragDrop runs its own
|
||||
// modal loop and takes over mouse capture, so the internal drag must be fully
|
||||
// wound down first (no stale dragging/dropKind, no lingering SetCapture). A
|
||||
// cancelled/empty OS drag therefore leaves the panel in a clean, no-op state
|
||||
// (invariant #2 — nothing mutated).
|
||||
if (GetCapture() == g_panel.hwnd) ReleaseCapture();
|
||||
g_panel.dragArmed = false;
|
||||
g_panel.dragging = false;
|
||||
g_panel.dropKind = DropKind::None;
|
||||
g_panel.dropBankId.clear();
|
||||
invalidatePanel();
|
||||
|
||||
// Empty path list -> nothing draggable (all stale/missing); do not start a drag.
|
||||
if (!paths.empty())
|
||||
initiateDragOut(g_panel.hwnd, paths); // COPY-ONLY; blocking on Windows
|
||||
return;
|
||||
}
|
||||
updateDropTarget(x, y);
|
||||
invalidatePanel();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user