Files
reasampler/src/core/ui/drag_out.cpp
T
daniel fe1aec5925 Hand a card dragged onto a plugin's own editor to the OS as a file drop; withdraw the OLE drag when the pointer returns to REAPER
A code-ownership proof (window class procedure's module, via the pure window_origin) plus REAPER's FX-surface token routes a plugin editor to OLE; FX chrome keeps the instrument drop. The OLE loop re-asks the law each turn, so passing a plugin editor stays reversible.
2026-09-25 13:39:39 -04:00

117 lines
4.6 KiB
C++

// drag_out — pure implementation. See drag_out.h.
#include "core/ui/drag_out.h"
#include <unordered_set>
namespace reasampler::ui {
namespace {
bool insideClient(int px, int py, const PanelClientRect& c) {
return px >= c.x && px < c.x + c.width &&
py >= c.y && py < c.y + c.height;
}
} // namespace
bool needsSurfaceProbe(int px, int py, const PanelClientRect& client) {
return !insideClient(px, py, client);
}
DropClass decideDropClass(int px, int py, const PanelClientRect& client,
const DropContext& ctx) {
if (!ctx.drag.dragging || !ctx.drag.hasArmedSamples) return DropClass::None;
if (insideClient(px, py, client)) return DropClass::Internal;
// The two routes to OsHandoff, both ahead of the surface switch, each needing BOTH halves.
//
// Leaving REAPER. The process proof alone is not sufficient: REAPER runs a bridged plugin's UI
// in a separate process, so a window failing that test can still be inside REAPER. A
// recognised token therefore VETOES this route instead of producing it — a veto can only move
// the answer toward staying inside REAPER. An off-host point REAPER DID name falls through to
// the second route, then to that surface's own outcome below.
if (ctx.pointerOffHost && ctx.surface == ReaperSurface::Other && !ctx.haveTrack)
return DropClass::OsHandoff;
// A plugin's own editor, with the reverse pairing: REAPER's naming is REQUIRED here, not a
// veto. The code proof alone would hand off over any extension window docked in REAPER; the
// FX-surface token confines it to FX windows, and the proof picks the plugin's editor out of
// that window's REAPER chrome, which keeps its instrument outcome below. A bridged editor
// lands here too — the token that vetoed the first route admits this one.
if (ctx.pointerOverPluginEditor && ctx.surface == ReaperSurface::FxSurface)
return DropClass::OsHandoff;
switch (ctx.surface) {
case ReaperSurface::TrackPanel:
case ReaperSurface::FxSurface:
// One instance holds one capture, so a multi payload names no instrument to build —
// it refuses with a cue rather than falling through to some other surface's outcome.
return (ctx.singlePayload && ctx.haveTrack) ? DropClass::InstrumentDrop
: DropClass::Refuse;
case ReaperSurface::Arrange:
// GetThingFromPoint may return a null track with a valid info string (the SDK says
// so): over the arrange that is the region below the last track, which names no lane
// to place on. Refuse rather than guess a track.
return ctx.haveTrack ? DropClass::ArrangeInsert : DropClass::Refuse;
case ReaperSurface::FxEmbed:
case ReaperSurface::Other:
return DropClass::Refuse;
case ReaperSurface::Count:
break; // the sentinel is not a surface; it falls to the refusal below
}
return DropClass::Refuse; // an unclassifiable surface still refuses visibly, never silently
}
DropCue cueForDropClass(DropClass cls) {
switch (cls) {
case DropClass::Internal: return DropCue::Internal;
case DropClass::InstrumentDrop: return DropCue::Instrument;
case DropClass::ArrangeInsert: return DropCue::ArrangeInsert;
case DropClass::Refuse: return DropCue::Refuse;
case DropClass::OsHandoff: return DropCue::OsOwned;
case DropClass::None: return DropCue::None;
}
return DropCue::None;
}
PathList assemblePathList(const std::vector<ResolvedSample>& resolved) {
PathList out;
std::unordered_set<std::string> seen;
seen.reserve(resolved.size());
for (const ResolvedSample& s : resolved) {
if (s.absolutePath.empty()) {
++out.skippedUnresolved;
continue;
}
if (!s.fileExists) {
++out.skippedMissing;
continue;
}
if (!seen.insert(s.absolutePath).second) {
++out.skippedDuplicate;
continue;
}
out.paths.push_back(s.absolutePath);
}
return out;
}
OsHandoff decideOsHandoff(const std::vector<std::string>& paths) {
OsHandoff out;
out.handOffToOs = !paths.empty();
return out;
}
OsDragTurn decideOsDragTurn(bool escapePressed, bool buttonDown, bool lawHandsOff) {
if (escapePressed) return OsDragTurn::Cancel;
if (buttonDown) return lawHandsOff ? OsDragTurn::Continue : OsDragTurn::Withdraw;
return lawHandsOff ? OsDragTurn::Drop : OsDragTurn::WithdrawAtRelease;
}
} // namespace reasampler::ui