Fix drag-handoff bugs: gate FX re-resolve on outside-panel, cache unresolvable OS-drag verdict, block double FX-add retry
This commit is contained in:
@@ -297,14 +297,32 @@ void onMouseMove(int x, int y) {
|
||||
g_panel.instrumentDropTrack = nullptr;
|
||||
|
||||
if (gesture == DragGesture::OsDrag) {
|
||||
if (g_panel.dragOsHandoffBlocked) {
|
||||
// Already known un-hand-off-able for this gesture (empty/unresolvable payload,
|
||||
// or the OS wasn't ready) — skip the fs::exists work and the readiness probe on
|
||||
// every move; keep the drag alive with no drop-target highlight.
|
||||
g_panel.dropKind = DropKind::None;
|
||||
g_panel.dropBankId.clear();
|
||||
invalidatePanel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolve the payload to existing on-disk paths BEFORE tearing down internal
|
||||
// drag state (the resolver reads dragSourceBankId / dragSampleIds), then let the
|
||||
// pure rule couple the two side effects: an unresolvable payload must leave the
|
||||
// internal drag intact rather than wind it down for a hand-off that never runs —
|
||||
// a half-torn-down drag reads to the user as "the drag did nothing, try again".
|
||||
// canInitiateDragOut extends the same coupling to the OS-readiness failure modes
|
||||
// (OLE unavailable, HDROP build failure) that the pure decision cannot see.
|
||||
const std::vector<std::string> paths = resolveDragPathsForOs();
|
||||
const ui::OsHandoff handoff = ui::decideOsHandoff(paths);
|
||||
if (!handoff.releaseInternalDrag) return;
|
||||
if (!handoff.handOffToOs || !canInitiateDragOut(paths)) {
|
||||
g_panel.dragOsHandoffBlocked = true;
|
||||
g_panel.dropKind = DropKind::None;
|
||||
g_panel.dropBankId.clear();
|
||||
invalidatePanel();
|
||||
return;
|
||||
}
|
||||
|
||||
// DoDragDrop runs its own modal loop and takes over mouse capture, so the internal
|
||||
// drag must be fully wound down first.
|
||||
@@ -318,8 +336,7 @@ void onMouseMove(int x, int y) {
|
||||
g_panel.dragPrimaryId.clear();
|
||||
invalidatePanel();
|
||||
|
||||
if (handoff.startOsDrag)
|
||||
initiateDragOut(g_panel.hwnd, paths); // COPY-ONLY; blocking on Windows
|
||||
initiateDragOut(g_panel.hwnd, paths); // COPY-ONLY; blocking on Windows
|
||||
return;
|
||||
}
|
||||
// Inside the client: classify the in-grid gesture (reorder/replace vs move/copy) and
|
||||
@@ -372,6 +389,7 @@ void resetDragState() {
|
||||
g_panel.dragTargetSlot = -1;
|
||||
g_panel.dragPrimaryId.clear();
|
||||
g_panel.instrumentDropTrack = nullptr;
|
||||
g_panel.dragOsHandoffBlocked = false;
|
||||
}
|
||||
|
||||
// Commits (or abandons) a drag on button-up. The resolved pure CardGesture decides:
|
||||
@@ -388,12 +406,19 @@ void onLBtnUp(int x, int y) {
|
||||
//
|
||||
// Re-resolve at the RELEASE point rather than trusting only the hover-tracked target:
|
||||
// WM_MOUSEMOVE is coalesced, so a fast drag onto a dense surface (an FX chain row, a
|
||||
// container) can release over a hotspot no processed move ever reported. Strictly
|
||||
// additive — a release-point miss falls back to the tracked target, so the
|
||||
// hover-then-release-on-the-FX-button path is untouched.
|
||||
// container) can release over a hotspot no processed move ever reported. Gated on
|
||||
// !inside exactly like onMouseMove's live resolve — GetThingFromPoint can return a
|
||||
// track+FX hit at a release point that is still inside the panel's own client rect, and
|
||||
// an in-grid release must always go through the reorder/replace/move/copy path below,
|
||||
// never be reinterpreted as an FX add. Strictly additive otherwise — a release-point
|
||||
// miss falls back to the tracked target, so the hover-then-release-on-the-FX-button
|
||||
// path is untouched.
|
||||
const bool singleCapture = g_panel.dragSampleIds.size() == 1;
|
||||
MediaTrack* dropTrack = g_panel.instrumentDropTrack;
|
||||
if (singleCapture) {
|
||||
RECT cr{};
|
||||
GetClientRect(g_panel.hwnd, &cr);
|
||||
const bool inside = (x >= cr.left && x < cr.right && y >= cr.top && y < cr.bottom);
|
||||
if (!inside && singleCapture) {
|
||||
POINT sp{x, y};
|
||||
ClientToScreen(g_panel.hwnd, &sp);
|
||||
const FxDropTarget fx = resolveFxDropTarget(sp.x, sp.y);
|
||||
|
||||
@@ -320,6 +320,12 @@ struct PanelState {
|
||||
// when the pointer is not over an FX button.
|
||||
MediaTrack* instrumentDropTrack = nullptr;
|
||||
|
||||
// Latched once an OsDrag hand-off attempt for THIS gesture resolves to "cannot hand off"
|
||||
// (empty/unresolvable payload, or the OS isn't ready) — skips re-running
|
||||
// resolveDragPathsForOs (an fs::exists per sample) and the readiness probe on every
|
||||
// subsequent move while the drag stays alive. Cleared by resetDragState.
|
||||
bool dragOsHandoffBlocked = false;
|
||||
|
||||
// Authoritative tail setting lives in ReaSamplerSession, not here; panel reads it for
|
||||
// drawing and mutates via footer click / scroll-wheel. bankPanelTailSetting is the
|
||||
// capture actions' read seam.
|
||||
|
||||
Reference in New Issue
Block a user