S17 drop-and-load onto track FX button; S13 editor drop-accept (relay degraded)
S17: drag_out InstrumentDrop gesture + instrument_drop blob reusing the instrument's own serializer; bank_panel FX hover-track + add-VST/vst_chunk inject. S13 relay deferred (read-only bridge) — editor shows drop affordance.
This commit is contained in:
+95
-31
@@ -62,6 +62,8 @@
|
||||
#include "footer_bar.h" // pure footer LEFT-group layout: toggle + count + Tail button (L4)
|
||||
#include "guid_diff.h" // GuidBaseline — new-content detection (D2 Wave 2)
|
||||
#include "ingest.h" // ingestDroppedFiles — S8 drop-onto-panel ingest
|
||||
#include "instrument_drop.h" // pure buildInstrumentDropChunk — the vst_chunk blob (S17)
|
||||
#include "instrument_drop_win.h" // resolveFxDropTarget / performInstrumentDrop shell (S17)
|
||||
#include "item_read.h" // itemGuid / itemLaneName — shared item-read seam (D2 W3-B)
|
||||
#include "lane_keys.h" // managed/manual lane heuristic (D2 Wave 2)
|
||||
#include "mode_enable.h" // opposite-mode tag-button enablement predicate (pure, L5)
|
||||
@@ -321,6 +323,15 @@ struct PanelState {
|
||||
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 now lives in ReaSamplerSession (session->tail()),
|
||||
// NOT in panel state, so it travels inside the .rpp (persist serializes it on save,
|
||||
@@ -2917,16 +2928,52 @@ 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).
|
||||
// M11 gesture boundary, REFINED by S17. While a drag with samples is under way and the
|
||||
// pointer is INSIDE the client rect it stays the internal bank-to-bank drag (invariant
|
||||
// #4, byte-identical). Once it LEAVES the client rect the pure drag_out::decideGesture
|
||||
// splits the outside case three ways: a single-capture drag over REAPER's OWN UI is an
|
||||
// InstrumentDrop (hover-track the FX button, drop on release); a multi-capture drag OR a
|
||||
// pointer that has left REAPER entirely is the unchanged M11 OsDrag; inside stays
|
||||
// Internal. The shell supplies the "over REAPER's UI" predicate via GetThingFromPoint.
|
||||
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) {
|
||||
const bool inside = (x >= cr.left && x < cr.right && y >= cr.top && y < cr.bottom);
|
||||
|
||||
DragState st{/*dragging=*/true, /*hasArmedSamples=*/!g_panel.dragSampleIds.empty()};
|
||||
st.singleCapture = (g_panel.dragSampleIds.size() == 1);
|
||||
|
||||
// Resolve the FX drop target only when OUTSIDE the client rect (the S17 middle case can
|
||||
// only arise there) and only for a single-capture payload — the SDK hit-test is skipped
|
||||
// on the common internal-drag path so it costs nothing there. The screen conversion is
|
||||
// Windows-only (D5); resolveFxDropTarget owns the REAPER hit query.
|
||||
FxDropTarget fx;
|
||||
if (!inside && st.singleCapture) {
|
||||
POINT sp{x, y};
|
||||
ClientToScreen(g_panel.hwnd, &sp);
|
||||
fx = resolveFxDropTarget(sp.x, sp.y);
|
||||
st.overReaperUi = fx.overReaperUi;
|
||||
}
|
||||
|
||||
const DragGesture gesture = decideGesture(x, y, client, st);
|
||||
|
||||
if (gesture == DragGesture::InstrumentDrop) {
|
||||
// Track the FX hotspot for the release; the highlight is REAPER's own FX-button
|
||||
// hover feedback under the pointer (the drop is driven on button-up). We keep the
|
||||
// internal-drag capture alive so we keep receiving moves (unlike OsDrag, this does
|
||||
// NOT hand off to a modal OS loop). Clear any internal drop-target highlight so the
|
||||
// panel does not also paint a bank-drop cue while the drag is out over a track.
|
||||
g_panel.instrumentDropTrack = fx.valid() ? fx.track : nullptr;
|
||||
g_panel.dropKind = DropKind::None;
|
||||
g_panel.dropBankId.clear();
|
||||
invalidatePanel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Left InstrumentDrop territory (back inside, or over a non-FX area): drop the FX target.
|
||||
g_panel.instrumentDropTrack = nullptr;
|
||||
|
||||
if (gesture == 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();
|
||||
@@ -2995,31 +3042,45 @@ void doReplaceDrop(const std::string& newId, const std::string& oldId,
|
||||
// OsDragOut is never seen here: the pointer-left-client handoff happens live in onMouseMove.
|
||||
void onLBtnUp(int x, int y) {
|
||||
if (g_panel.dragging) {
|
||||
updateDropTarget(x, y);
|
||||
classifyCardDrag(x, y); // re-resolve at the drop point (modifiers may have changed)
|
||||
const CardGesture g = g_panel.cardGesture;
|
||||
// S17 drop-and-load: a release while hover-tracking a valid FX hotspot instantiates a
|
||||
// ReaSampler 9000 on that track preloaded with the dragged capture — NOT a bank move,
|
||||
// NOT an OS drag, NEVER a timeline insert. Takes priority over the L7 in-grid / cross-bank
|
||||
// drop (the pointer is out over a track, not over a bank region). Single-capture only (the
|
||||
// gesture never armed for a multi payload), so dragSampleIds.front() is the capture.
|
||||
if (g_panel.instrumentDropTrack && g_panel.dragSampleIds.size() == 1) {
|
||||
const std::string sampleId = g_panel.dragSampleIds.front();
|
||||
const std::string chunk = buildInstrumentDropChunk(sampleId);
|
||||
performInstrumentDrop(g_panel.instrumentDropTrack, chunk);
|
||||
// Read-only over the bank + arrange: the ONLY mutations are the new FX instance +
|
||||
// its state (both undoable in performInstrumentDrop). No book change, no ext-state,
|
||||
// no dirty-mark here.
|
||||
} else {
|
||||
updateDropTarget(x, y);
|
||||
classifyCardDrag(x, y); // re-resolve at the drop point (modifiers may have changed)
|
||||
const CardGesture g = g_panel.cardGesture;
|
||||
|
||||
if (g == CardGesture::Reorder) {
|
||||
doReorderDrop(g_panel.dragPrimaryId, g_panel.dragSourceBankId,
|
||||
g_panel.dragTargetSlot);
|
||||
} else if (g == CardGesture::Replace) {
|
||||
// Replace targets the OCCUPANT of the target slot with the single grabbed card.
|
||||
const bool isBanks = g_panel.dragSourceRegion == Region::Banks;
|
||||
RECT cr{};
|
||||
GetClientRect(g_panel.hwnd, &cr);
|
||||
const int w = cr.right - cr.left, h = cr.bottom - cr.top;
|
||||
const RECT region = isBanks ? banksRegionRect(w, h) : poolRegionRect(w, h);
|
||||
const RegionDisplay disp = regionDisplay(region, isBanks, g_panel.dragSourceRegion);
|
||||
const std::string occupant = disp.idAtSlot(g_panel.dragTargetSlot);
|
||||
// Replace only makes sense for a single grabbed card over a DIFFERENT occupant.
|
||||
if (!occupant.empty() && occupant != g_panel.dragPrimaryId)
|
||||
doReplaceDrop(g_panel.dragPrimaryId, occupant, g_panel.dragSourceBankId);
|
||||
} else if (g == CardGesture::Move || g == CardGesture::Copy) {
|
||||
const std::string destId = dropTargetBankId();
|
||||
if (!destId.empty() && destId != g_panel.dragSourceBankId &&
|
||||
!g_panel.dragSampleIds.empty()) {
|
||||
transferSamples(g_panel.dragSampleIds, g_panel.dragSourceBankId, destId,
|
||||
/*copy=*/g == CardGesture::Copy);
|
||||
if (g == CardGesture::Reorder) {
|
||||
doReorderDrop(g_panel.dragPrimaryId, g_panel.dragSourceBankId,
|
||||
g_panel.dragTargetSlot);
|
||||
} else if (g == CardGesture::Replace) {
|
||||
// Replace targets the OCCUPANT of the target slot with the single grabbed card.
|
||||
const bool isBanks = g_panel.dragSourceRegion == Region::Banks;
|
||||
RECT cr{};
|
||||
GetClientRect(g_panel.hwnd, &cr);
|
||||
const int w = cr.right - cr.left, h = cr.bottom - cr.top;
|
||||
const RECT region = isBanks ? banksRegionRect(w, h) : poolRegionRect(w, h);
|
||||
const RegionDisplay disp = regionDisplay(region, isBanks, g_panel.dragSourceRegion);
|
||||
const std::string occupant = disp.idAtSlot(g_panel.dragTargetSlot);
|
||||
// Replace only makes sense for a single grabbed card over a DIFFERENT occupant.
|
||||
if (!occupant.empty() && occupant != g_panel.dragPrimaryId)
|
||||
doReplaceDrop(g_panel.dragPrimaryId, occupant, g_panel.dragSourceBankId);
|
||||
} else if (g == CardGesture::Move || g == CardGesture::Copy) {
|
||||
const std::string destId = dropTargetBankId();
|
||||
if (!destId.empty() && destId != g_panel.dragSourceBankId &&
|
||||
!g_panel.dragSampleIds.empty()) {
|
||||
transferSamples(g_panel.dragSampleIds, g_panel.dragSourceBankId, destId,
|
||||
/*copy=*/g == CardGesture::Copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
// CardGesture::None -> no-op drop (dead space, or same-bank gap resolved to None).
|
||||
@@ -3041,6 +3102,7 @@ void onLBtnUp(int x, int y) {
|
||||
g_panel.cardGesture = CardGesture::None;
|
||||
g_panel.dragTargetSlot = -1;
|
||||
g_panel.dragPrimaryId.clear();
|
||||
g_panel.instrumentDropTrack = nullptr; // S17: clear the FX hotspot after the release
|
||||
invalidatePanel();
|
||||
}
|
||||
|
||||
@@ -3169,6 +3231,7 @@ WDL_DLGRET dlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||
g_panel.cardGesture = CardGesture::None;
|
||||
g_panel.dragTargetSlot = -1;
|
||||
g_panel.dragPrimaryId.clear();
|
||||
g_panel.instrumentDropTrack = nullptr; // S17: drop the FX hotspot on teardown
|
||||
g_panel.hovered = Hover{};
|
||||
g_panel.tooltipShown = false;
|
||||
g_panel.hwnd = nullptr;
|
||||
@@ -3226,6 +3289,7 @@ void closePanel() {
|
||||
stopAudition();
|
||||
g_panel.selection = Selection{};
|
||||
g_panel.dragArmed = g_panel.dragging = false;
|
||||
g_panel.instrumentDropTrack = nullptr; // S17: drop the FX hotspot on close
|
||||
unregisterAccel();
|
||||
if (g_panel.hwnd) {
|
||||
DockWindowRemove(g_panel.hwnd);
|
||||
|
||||
Reference in New Issue
Block a user