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:
2026-07-27 03:52:26 -04:00
parent 06494c654e
commit 26e2bf2fc6
13 changed files with 779 additions and 54 deletions
+58 -1
View File
@@ -28,6 +28,7 @@
#ifdef _WIN32
#include <windowsx.h> // GET_X_LPARAM / GET_Y_LPARAM
#include <shellapi.h> // DragAcceptFiles / DragQueryFile / DragFinish — S13 editor drop-accept
#include "wdltypes.h"
#include "lice/lice.h"
@@ -218,6 +219,12 @@ void ReaSamplerEditor::onSyncTimer() {
refreshFromBank();
invalidate();
}
// S13: decay the drop-affordance banner so it auto-dismisses a few ticks after a drop.
if (dropHintTicks_ > 0) {
--dropHintTicks_;
invalidate();
}
}
#endif // _WIN32
@@ -522,6 +529,10 @@ void ReaSamplerEditor::attachedToParent() {
r.getWidth(), r.getHeight(), parent, nullptr, hInst, nullptr);
if (childHwnd_) {
SetWindowLongPtr(childHwnd_, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
// S13: accept OS file drops on the editor window (WM_DROPFILES). The drop is NOT
// ingested here (the relay is degraded — see onFilesDropped); accepting it lets us show
// the "drop on the panel" affordance instead of the OS bouncing the drop silently.
DragAcceptFiles(childHwnd_, TRUE);
// Start the S9/S8 change-detection poll (UI thread). Tied to the child window's
// lifetime — created here, killed in removedFromParent — so an instance whose editor
// is closed does NOT poll (the editor-open-only cadence; see the handoff limitation).
@@ -697,6 +708,19 @@ void ReaSamplerEditor::paint(HDC hdc) {
paintBrowser(&bmp, w, h);
}
// S13 (relay degraded): a transient banner flashed after a file was dropped ON THIS window.
// It reiterates the shipped ingest gesture rather than swallowing the drop silently. Drawn
// LAST so it overlays the mode content; decays via onSyncTimer (dropHintTicks_).
if (dropHintTicks_ > 0) {
const int bannerH = (std::min)(kTitleHeight + 8, h);
Rect banner{0, bands.toggleZones.bottom, w, bands.toggleZones.bottom + bannerH};
LICE_FillRect(&bmp, banner.left, banner.top, banner.width(), banner.height(),
kColTabActiveBg, 1.0f, 0);
drawTextCentered(&bmp, banner,
"Dropped here isn't loaded yet - drop files onto the ReaSampler bank panel to add them.",
kRgbText);
}
BitBlt(hdc, 0, 0, w, h, bmp.getDC(), 0, 0, SRCCOPY);
}
@@ -706,7 +730,16 @@ void ReaSamplerEditor::paintEmptyState(LICE_IBitmap* bmp, const Rect& area) {
const char* msg = samples_.empty()
? "No captures in this project yet - capture audio into the bank to play it here."
: "No captures in this bank filter. Choose another bank tab above.";
drawTextCentered(bmp, area, msg, kRgbDim);
// Split the area so the primary line sits centered and the S13 ingest affordance sits just
// below it. The affordance is the SHIPPED ingest gesture (drop onto the docked panel) — kept
// discoverable here regardless of whether a drop ever lands on THIS window.
Rect primary{area.left, area.top, area.right, area.top + area.height() / 2};
Rect hint{area.left, primary.bottom, area.right, area.bottom};
drawTextCentered(bmp, primary, msg, kRgbDim);
drawTextCentered(bmp,
hint,
"To add a sample: drop a file onto the ReaSampler bank panel (the docked window).",
kRgbDim);
}
void ReaSamplerEditor::paintBrowser(LICE_IBitmap* bmp, int w, int h) {
@@ -1559,6 +1592,20 @@ void ReaSamplerEditor::onSearchChar(unsigned int ch) {
invalidate();
}
void ReaSamplerEditor::onFilesDropped(int droppedCount) {
// S13 relay DEGRADED. The instrument is a read-only bank consumer and the cross-artifact
// ingest relay (editor drop -> extension) is not shipped (see the header note + the handoff
// decision point), so we do NOT ingest the dropped files and — load-bearing — NEVER insert a
// timeline item. Instead of silently swallowing the drop, flash a clear affordance pointing
// at the shipped ingest gesture. dropHintTicks_ counts sync ticks (kSyncTimerIntervalMs
// each); ~6 ticks keeps the banner up a few seconds, then onSyncTimer decays it to 0.
(void)droppedCount; // count is informational; the banner text is drop-count-agnostic
dropHintTicks_ = 6;
#ifdef _WIN32
invalidate();
#endif
}
LRESULT CALLBACK ReaSamplerEditor::wndProc(HWND hwnd, UINT msg, WPARAM wParam,
LPARAM lParam) {
auto* self =
@@ -1612,6 +1659,16 @@ LRESULT CALLBACK ReaSamplerEditor::wndProc(HWND hwnd, UINT msg, WPARAM wParam,
self->invalidate();
}
return 0;
case WM_DROPFILES: {
// S13 (relay degraded): count the dropped files and flash the affordance. We do NOT
// read/ingest the paths (the instrument never ingests — the relay to the extension is
// unshipped); DragQueryFile with 0xFFFFFFFF just returns the count for the banner.
HDROP drop = reinterpret_cast<HDROP>(wParam);
const UINT count = DragQueryFileW(drop, 0xFFFFFFFF, nullptr, 0);
DragFinish(drop);
if (self) self->onFilesDropped(static_cast<int>(count));
return 0;
}
case WM_TIMER:
if (self && wParam == kSyncTimerId) self->onSyncTimer();
return 0;
+18
View File
@@ -114,6 +114,13 @@ private:
void onMouseWheel(int delta); // S12 browser scroll (wheel)
void onSearchChar(unsigned int ch); // S12 type-to-filter search keystroke
// S13 (relay degraded): an OS file drop landed on the editor window. We do NOT ingest (the
// instrument is a read-only bank consumer and the relay is unshipped) — we flash the "drop
// on the ReaSampler panel to add" affordance so the drop is never silently swallowed and the
// shipped ingest gesture stays discoverable. `droppedCount` is how many files were dropped
// (drawn into the banner). NEVER inserts a timeline item / never touches the bank.
void onFilesDropped(int droppedCount);
// The S9/S8 change-detection tick (WM_TIMER on the child window — the UI thread, NEVER the
// audio thread). Polls the processor's bank-sync (generation change -> hands-free reload;
// a new assignment request -> apply as this instance's selection) and, when anything
@@ -212,6 +219,17 @@ private:
std::string activeFilterBankId_; // "" = All; else a bank id from banks_
int selectedZone_ = -1; // highlighted zone in the Zones panel; -1 = none
// --- S13 drop-to-load affordance (relay DEGRADED — transient, never persisted) ----
// S13's cross-artifact ingest relay (editor drop -> extension ingest) is NOT shipped: the
// instrument's REAPER bridge is deliberately READ-ONLY (it never writes the bank / ext
// state), so an editor drop cannot relay a bank-ingest request without a new write seam +
// an extension-side poller (surfaced as a decision, not crossed here). The DEGRADE path per
// the spec: the editor ACCEPTS the drop (WM_DROPFILES) and, rather than silently swallowing
// it, flashes a clear affordance pointing at the shipped ingest gesture (drop onto the
// docked ReaSampler panel). When > 0, the affordance banner is shown; each sync tick decays
// it so it auto-dismisses. No file is ingested, no timeline item is ever inserted.
int dropHintTicks_ = 0; // remaining sync ticks to show the drop affordance
// --- S12 browser scroll + search (transient UI state, never persisted) --------
int scrollOffset_ = 0; // vertical px offset into the card grid (clamped)
std::string searchQuery_; // type-to-filter narrow; "" = no search