fe1aec5925
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.
117 lines
4.6 KiB
C++
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
|