Ψ-W1-T4: resolve drop targets per move, not once — every surface gets a defined outcome, a cue, and no silent no-op
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
// arrange_drop_win.cpp — see arrange_drop_win.h. main.cpp owns the API pointers; this TU gets
|
||||
// them extern via the WANT list.
|
||||
//
|
||||
// Runtime assumptions, all DAW-verifiable and none confirmed by the SDK header:
|
||||
// A. InsertMedia base mode 0 targets the sole selected track and inserts at the edit cursor;
|
||||
// SetOnlyTrackSelected isolates that track first (the same pair insert.cpp relies on).
|
||||
// B. InsertMedia advances the edit cursor past the media it added. That advance IS the
|
||||
// multi-file layout: the cursor is deliberately not reset between files, so N captures
|
||||
// land end to end. If REAPER does not advance it, they stack at one position instead —
|
||||
// visible and one Ctrl-Z away, never silent.
|
||||
// C. SnapToGrid honors the project's snap-enabled toggle. The header documents no
|
||||
// snap-enabled query for the arrange, so a drop taken with snapping OFF is the test that
|
||||
// settles it.
|
||||
|
||||
#include "shell/actions/arrange_drop_win.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/capture/insert_plan.h" // computeInsertMode — the ONE InsertMedia bitfield owner
|
||||
|
||||
#include "reaper_plugin.h"
|
||||
|
||||
#define REAPERAPI_MINIMAL
|
||||
#define REAPERAPI_WANT_CountSelectedTracks
|
||||
#define REAPERAPI_WANT_GetCursorPosition
|
||||
#define REAPERAPI_WANT_GetSelectedTrack
|
||||
#define REAPERAPI_WANT_GetSet_ArrangeView2
|
||||
#define REAPERAPI_WANT_InsertMedia
|
||||
#define REAPERAPI_WANT_SetEditCurPos
|
||||
#define REAPERAPI_WANT_SetOnlyTrackSelected
|
||||
#define REAPERAPI_WANT_SetTrackSelected
|
||||
#define REAPERAPI_WANT_SnapToGrid
|
||||
#define REAPERAPI_WANT_Undo_BeginBlock2
|
||||
#define REAPERAPI_WANT_Undo_EndBlock2
|
||||
#include "reaper_plugin_functions.h"
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
using capture::computeInsertMode;
|
||||
using capture::InsertOptions;
|
||||
|
||||
namespace {
|
||||
|
||||
// Selection snapshot/restore, so a drop leaves the user's track selection exactly as it found
|
||||
// it. Mirrors insert.cpp's pair; that TU belongs to the capture pillar and this one to the drag
|
||||
// chain, so they are kept separate rather than sharing a helper across the seam.
|
||||
std::vector<MediaTrack*> snapshotSelectedTracks() {
|
||||
const int n = CountSelectedTracks(nullptr); // nullptr = active project
|
||||
std::vector<MediaTrack*> tracks;
|
||||
tracks.reserve(static_cast<std::size_t>(n));
|
||||
for (int i = 0; i < n; ++i) tracks.push_back(GetSelectedTrack(nullptr, i));
|
||||
return tracks;
|
||||
}
|
||||
|
||||
void restoreSelectedTracks(const std::vector<MediaTrack*>& tracks) {
|
||||
if (tracks.empty()) return; // nothing was selected; leave whatever the drop selected
|
||||
SetOnlyTrackSelected(tracks[0]);
|
||||
for (std::size_t i = 1; i < tracks.size(); ++i) SetTrackSelected(tracks[i], true);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
double arrangeTimeAtScreenX(int screenX) {
|
||||
double start = 0.0, end = 0.0;
|
||||
// isSet=false reads the arrange times spanned by [screen_x_start, screen_x_end); a
|
||||
// one-pixel span therefore reads the time at that column. (The all-zero span is the SDK's
|
||||
// documented "whole view" form, which is why the span here must not be empty.)
|
||||
GetSet_ArrangeView2(nullptr, false, screenX, screenX + 1, &start, &end);
|
||||
return start < 0.0 ? 0.0 : start;
|
||||
}
|
||||
|
||||
int performArrangeDrop(MediaTrack* track, double time,
|
||||
const std::vector<std::string>& absolutePaths) {
|
||||
if (!track || absolutePaths.empty()) return 0;
|
||||
|
||||
const std::vector<MediaTrack*> priorSelection = snapshotSelectedTracks();
|
||||
const double priorCursor = GetCursorPosition();
|
||||
const int mode = computeInsertMode(InsertOptions{}); // current track, native length, no stretch
|
||||
|
||||
// One undo block around the whole drop (every item plus the selection/cursor restore) so a
|
||||
// single Ctrl-Z returns the project to exactly its pre-drop state.
|
||||
Undo_BeginBlock2(nullptr);
|
||||
|
||||
SetOnlyTrackSelected(track);
|
||||
SetEditCurPos(SnapToGrid(nullptr, time), /*moveview=*/false, /*seekplay=*/false);
|
||||
|
||||
int inserted = 0;
|
||||
for (const std::string& path : absolutePaths) {
|
||||
// No cursor reset between files — see assumption B in the file header.
|
||||
InsertMedia(path.c_str(), mode);
|
||||
++inserted;
|
||||
}
|
||||
|
||||
restoreSelectedTracks(priorSelection);
|
||||
SetEditCurPos(priorCursor, /*moveview=*/false, /*seekplay=*/false);
|
||||
|
||||
// extraflags -1 = UNDO_STATE_ALL, matching the insert action's own block.
|
||||
const std::string label = "ReaSampler: drop " + std::to_string(inserted) +
|
||||
(inserted == 1 ? " capture" : " captures") + " onto arrange";
|
||||
Undo_EndBlock2(nullptr, label.c_str(), -1);
|
||||
return inserted;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
Reference in New Issue
Block a user