Q-W2v: split VST god-modules — editor 8 face-axis TUs (+pure layout hoist), processor 3 TUs, component_state_io codec split (extension drops the voice engine), zone_params.h, core/wire putLE; formats frozen, 61/61 green
This commit is contained in:
@@ -0,0 +1,440 @@
|
||||
// editor_input_browse_zone.cpp — the ReaSamplerEditor's BROWSE-MODAL and ZONE-SURFACE
|
||||
// input + the hover resolver (Q-W2v split of reasampler_editor.cpp, T4-11): the L3 hover
|
||||
// resolution across all three faces, the Browse picker's click branch (tabs, cards,
|
||||
// select-then-confirm, scroll-thumb grab, search focus), the Zone surface's click branch
|
||||
// (add/delete, strip drags, numeric-entry focus, per-zone deck + curve button), the
|
||||
// browser wheel scroll, the type-to-filter / note-entry keystrokes, and the S13 degraded
|
||||
// drop affordance. Windows-only (D5).
|
||||
|
||||
#include "shell/instrument/reasampler_editor.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/instrument/ui/browser_scroll.h" // BrowseModal + scroll/search geometry (S12)
|
||||
#include "core/instrument/ui/curve_popup.h" // computeCurvePopup (popup hover)
|
||||
#include "core/instrument/ui/knob_deck.h" // hitTestDeck / kDeckKnobSize
|
||||
#include "core/instrument/map/note_entry.h" // parseNoteEntry (S12 numeric entry)
|
||||
#include "shell/instrument/editor_internal.h" // curveBoxFromRect (popup node hover)
|
||||
#include "shell/instrument/reasampler_processor.h"
|
||||
|
||||
namespace reasampler::vst {
|
||||
|
||||
using namespace reasampler::ui;
|
||||
using namespace reasampler::instrument::ui;
|
||||
using namespace reasampler::instrument::map;
|
||||
|
||||
// --- Hover resolution (Phase L, L3) ------------------------------------------
|
||||
//
|
||||
// Resolve the interactive element under (x, y) into hover_ and repaint only on change (an
|
||||
// idle move is free). Mirrors onMouseDown's hit-test order, but read-only. Windows-only.
|
||||
void ReaSamplerEditor::resolveHover(int x, int y) {
|
||||
HoverTarget h; // kNone by default
|
||||
RECT cr{};
|
||||
GetClientRect(childHwnd_, &cr);
|
||||
const int w = cr.right - cr.left;
|
||||
const int hgt = cr.bottom - cr.top;
|
||||
|
||||
if (view_ == View::kBrowse) {
|
||||
const BrowseModal bm = computeBrowseModal(w, hgt);
|
||||
if (contains(bm.back, x, y)) h = {HoverKind::kBack, -1};
|
||||
else if (contains(bm.cancel, x, y)) h = {HoverKind::kBrowseCancel, -1};
|
||||
else if (contains(bm.confirm, x, y)) h = {HoverKind::kBrowseConfirm, -1};
|
||||
else if (contains(bm.search, x, y)) h = {HoverKind::kSearchBox, -1};
|
||||
else {
|
||||
const BrowserLayout bl = layoutBrowser(bm.content.width, bm.content.height);
|
||||
const int bx = x - bm.content.x;
|
||||
const int by = y - bm.content.y;
|
||||
const int tabCount = static_cast<int>(banks_.size()) + 1;
|
||||
const int tab = filterTabHitTest(bl, tabCount, bx, by);
|
||||
const int card = (tab >= 0)
|
||||
? -1
|
||||
: cardHitTest(bl, static_cast<int>(visible_.size()), bx, by + scrollOffset_);
|
||||
if (tab >= 0) h = {HoverKind::kFilterTab, tab};
|
||||
else if (card >= 0) h = {HoverKind::kCard, card};
|
||||
}
|
||||
} else if (curvePopupOpen_) { // the r11 curve popup — modal over Sample AND Zone (FB2)
|
||||
const CurvePopupLayout pl = computeCurvePopup(w, hgt);
|
||||
if (contains(pl.close, x, y)) {
|
||||
h = {HoverKind::kPopupClose, -1};
|
||||
} else if (contains(pl.curveBox, x, y)) {
|
||||
// A curve node under the pointer lights accent-hot.
|
||||
const int idx =
|
||||
popupZone().velocityCurve.pointAtPixel(curveBoxFromRect(pl.curveBox), x, y);
|
||||
if (idx >= 0) h = {HoverKind::kCurveNode, idx};
|
||||
}
|
||||
} else if (view_ == View::kZone) {
|
||||
const Rect back = zoneBackRect(w, hgt);
|
||||
const Rect content = zoneContentArea(w, hgt);
|
||||
Rect addR = zoneAddRect(content);
|
||||
Rect delR = zoneDeleteRect(addR);
|
||||
if (contains(back, x, y)) {
|
||||
h = {HoverKind::kBack, -1};
|
||||
} else if (contains(addR, x, y)) {
|
||||
h = {HoverKind::kAddZone, -1};
|
||||
} else if (selectedZone_ >= 0 && contains(delR, x, y)) {
|
||||
h = {HoverKind::kDeleteZone, -1};
|
||||
} else if (selectedZone_ >= 0 && selectedZone_ < static_cast<int>(map_.zones.size())) {
|
||||
// FB2: the per-zone knob deck + the mini curve-preview button (the Sample deck's
|
||||
// hover grammar — knobs light + swap label->value).
|
||||
if (contains(zonesCurveButton(content), x, y)) {
|
||||
h = {HoverKind::kCurveButton, -1};
|
||||
} else {
|
||||
const ZonePlaySeconds& play =
|
||||
map_.zones[static_cast<std::size_t>(selectedZone_)].play;
|
||||
const Rect deckArea = zonesDeckArea(content);
|
||||
const DeckLayout dl = layoutDeck(zoneDeckGroupDescs(play), deckArea.x,
|
||||
deckArea.y, deckArea.width);
|
||||
const DeckHit dh = hitTestDeck(dl, x, y);
|
||||
if (dh.kind != DeckHitKind::None) h = {HoverKind::kControl, dh.id};
|
||||
}
|
||||
}
|
||||
} else { // Sample view (home, r11 recomposition)
|
||||
const PerformanceZone zone = effectiveSampleZone();
|
||||
const std::vector<DeckGroupDesc> descs = deckGroupDescs(zone.play);
|
||||
const SampleBands bands =
|
||||
computeSampleBands(w, hgt, deckHeight(descs, w - 2 * kPad));
|
||||
if (contains(bands.navBrowse, x, y)) {
|
||||
h = {HoverKind::kNavBrowse, -1};
|
||||
} else if (contains(bands.navZone, x, y)) {
|
||||
h = {HoverKind::kNavZone, -1};
|
||||
} else if (selectedId_.empty() && map_.zones.empty()) {
|
||||
// Empty state — no interactive surfaces beyond the nav.
|
||||
} else {
|
||||
const ChannelToggleRects chan = channelToggleRects(bands.cluster);
|
||||
const ClusterRects cr = clusterRects(bands.cluster, chan.mono, kDeckKnobSize);
|
||||
if (contains(cr.preview, x, y)) h = {HoverKind::kPreview, -1};
|
||||
else if (contains(cr.velCell, x, y)) h = {HoverKind::kVelKnob, -1};
|
||||
else if (contains(cr.curveBtn, x, y)) h = {HoverKind::kCurveButton, -1};
|
||||
else if (contains(chan.mono, x, y)) h = {HoverKind::kChanMono, -1};
|
||||
else if (contains(chan.stereo, x, y)) h = {HoverKind::kChanStereo, -1};
|
||||
else if (contains(bands.deck, x, y)) {
|
||||
// A deck knob/toggle under the pointer: knobs light + swap label->value.
|
||||
const DeckLayout dl =
|
||||
layoutDeck(descs, bands.deck.x, bands.deck.y, bands.deck.width);
|
||||
const DeckHit dh = hitTestDeck(dl, x, y);
|
||||
if (dh.kind != DeckHitKind::None) h = {HoverKind::kControl, dh.id};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (h != hover_) {
|
||||
hover_ = h;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
// The Browse-modal branch of the mouse-down dispatch (formerly inline in onMouseDown —
|
||||
// behavior-identical; see editor_input_sample.cpp for the dispatch).
|
||||
void ReaSamplerEditor::mouseDownBrowse(int w, int h, int x, int y) {
|
||||
const BrowseModal bm = computeBrowseModal(w, h);
|
||||
if (contains(bm.back, x, y) || contains(bm.cancel, x, y)) {
|
||||
// Cancel/Back: discard the pending pick, return to Sample unchanged.
|
||||
browsePendingId_.clear();
|
||||
searchFocused_ = false;
|
||||
view_ = View::kSample;
|
||||
invalidate();
|
||||
return;
|
||||
}
|
||||
if (contains(bm.confirm, x, y)) {
|
||||
// Load: commit the pending pick (if any) into the loaded selection + reload, then Sample.
|
||||
if (!browsePendingId_.empty()) {
|
||||
loadSelection(browsePendingId_);
|
||||
}
|
||||
browsePendingId_.clear();
|
||||
searchFocused_ = false;
|
||||
view_ = View::kSample;
|
||||
invalidate();
|
||||
return;
|
||||
}
|
||||
if (contains(bm.search, x, y)) { searchFocused_ = true; invalidate(); return; }
|
||||
searchFocused_ = false;
|
||||
|
||||
const BrowserLayout bl = layoutBrowser(bm.content.width, bm.content.height);
|
||||
const int bx = x - bm.content.x;
|
||||
const int by = y - bm.content.y;
|
||||
const int tabCount = static_cast<int>(banks_.size()) + 1;
|
||||
const int tab = filterTabHitTest(bl, tabCount, bx, by);
|
||||
if (tab >= 0) {
|
||||
activeFilterBankId_ = (tab == 0) ? std::string()
|
||||
: banks_[static_cast<std::size_t>(tab - 1)].id;
|
||||
rebuildVisible();
|
||||
invalidate();
|
||||
return;
|
||||
}
|
||||
const Rect thumb = scrollThumbRect(bl, static_cast<int>(visible_.size()), scrollOffset_);
|
||||
if (thumb.height > 0 &&
|
||||
contains(Rect::ltrb(thumb.x + bm.content.x, thumb.y + bm.content.y,
|
||||
thumb.right() + bm.content.x, thumb.bottom() + bm.content.y), x, y)) {
|
||||
drag_ = DragKind::kScrollThumb;
|
||||
dragStartY_ = y;
|
||||
dragStartScrollOffset_ = scrollOffset_;
|
||||
return;
|
||||
}
|
||||
const int card = cardHitTest(bl, static_cast<int>(visible_.size()), bx, by + scrollOffset_);
|
||||
if (card >= 0) {
|
||||
// Select-then-confirm: a click marks the pending pick; a DOUBLE-click on the same card
|
||||
// is the load accelerator (commit + dismiss). Browse never loads on a single click.
|
||||
const std::string id = visible_[static_cast<std::size_t>(card)].id;
|
||||
if (lastBrowseClickCard_ == card && browsePendingId_ == id) {
|
||||
loadSelection(id);
|
||||
browsePendingId_.clear();
|
||||
lastBrowseClickCard_ = -1;
|
||||
searchFocused_ = false;
|
||||
view_ = View::kSample;
|
||||
invalidate();
|
||||
} else {
|
||||
browsePendingId_ = id;
|
||||
lastBrowseClickCard_ = card;
|
||||
invalidate();
|
||||
}
|
||||
return;
|
||||
}
|
||||
lastBrowseClickCard_ = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// The Zone-surface branch of the mouse-down dispatch (formerly the tail of onMouseDown —
|
||||
// behavior-identical; the curve popup is modal over the Zone surface too, FB2).
|
||||
void ReaSamplerEditor::mouseDownZone(int w, int h, int x, int y) {
|
||||
if (handlePopupMouseDown(w, h, x, y)) return;
|
||||
const Rect back = zoneBackRect(w, h);
|
||||
if (contains(back, x, y)) { view_ = View::kSample; invalidate(); return; }
|
||||
const Rect content = zoneContentArea(w, h);
|
||||
const int pad = 8;
|
||||
Rect addR = zoneAddRect(content);
|
||||
if (contains(addR, x, y)) {
|
||||
// Add a narrow default zone for the picked capture (or the first visible sample as a
|
||||
// sensible seed). No pick -> nothing to add. If a full-keyboard zone for the seed id
|
||||
// already exists (pre-fix bleed survivor), select it rather than appending a duplicate
|
||||
// (mirrors the upsert the root-marker drag path already performs).
|
||||
// NARROW DEFAULT: seed [root-6, root+5] (one octave centred on the bank root, clamped
|
||||
// to [0,127]) so the new zone is immediately "authored" (narrow) and survives
|
||||
// reconcileSingleCaptureZones without being treated as a Sample-face full-range zone.
|
||||
std::string seed = !selectedId_.empty() ? selectedId_
|
||||
: (!visible_.empty() ? visible_.front().id : std::string());
|
||||
if (seed.empty()) return;
|
||||
for (int i = 0; i < static_cast<int>(map_.zones.size()); ++i) {
|
||||
const PerformanceZone& z = map_.zones[static_cast<std::size_t>(i)];
|
||||
if (z.sampleId == seed && z.lowNote == 0 && z.highNote == 127) {
|
||||
selectedZone_ = i;
|
||||
invalidate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Look up the seed's root note from the browser list (absent root defaults to 60).
|
||||
int seedRoot = 60;
|
||||
for (const SampleChoice& sc : samples_) {
|
||||
if (sc.id == seed) { if (sc.rootNote.has_value()) seedRoot = *sc.rootNote; break; }
|
||||
}
|
||||
const int lo = (std::max)(0, seedRoot - 6);
|
||||
const int hi = (std::min)(127, seedRoot + 5);
|
||||
PerformanceZone z;
|
||||
z.sampleId = seed;
|
||||
z.lowNote = lo;
|
||||
z.highNote = hi;
|
||||
map_.zones.push_back(z);
|
||||
selectedZone_ = static_cast<int>(map_.zones.size()) - 1;
|
||||
commitAndReload();
|
||||
return;
|
||||
}
|
||||
Rect delR = zoneDeleteRect(addR);
|
||||
if (selectedZone_ >= 0 && contains(delR, x, y)) {
|
||||
map_.zones.erase(map_.zones.begin() + selectedZone_);
|
||||
selectedZone_ = -1;
|
||||
commitAndReload();
|
||||
return;
|
||||
}
|
||||
|
||||
// The zones strip: hit-test a bar edge/body to start a drag, or a bare key to set the
|
||||
// selected zone's root.
|
||||
const Rect stripArea = zonesStripArea(content);
|
||||
const StripLayout sl = layoutStrip(stripArea.width, stripArea.height);
|
||||
const int lx = x - stripArea.x;
|
||||
const int ly = y - stripArea.y;
|
||||
|
||||
std::vector<int> lows, highs;
|
||||
lows.reserve(map_.zones.size());
|
||||
highs.reserve(map_.zones.size());
|
||||
for (const PerformanceZone& z : map_.zones) { lows.push_back(z.lowNote); highs.push_back(z.highNote); }
|
||||
const ZoneBarHit hit = zoneBarAtPoint(sl, lows.empty() ? nullptr : lows.data(),
|
||||
highs.empty() ? nullptr : highs.data(),
|
||||
static_cast<int>(map_.zones.size()), lx, ly);
|
||||
if (hit.zoneIndex >= 0) {
|
||||
selectedZone_ = hit.zoneIndex;
|
||||
const PerformanceZone& z = map_.zones[static_cast<std::size_t>(hit.zoneIndex)];
|
||||
dragStartX_ = x;
|
||||
dragStartLow_ = z.lowNote;
|
||||
dragStartHigh_ = z.highNote;
|
||||
dragStartMap_ = map_;
|
||||
switch (hit.grab) {
|
||||
case ZoneGrab::kLowEdge: drag_ = DragKind::kZoneLow; break;
|
||||
case ZoneGrab::kHighEdge: drag_ = DragKind::kZoneHigh; break;
|
||||
case ZoneGrab::kBody: drag_ = DragKind::kZoneBody; break;
|
||||
default: drag_ = DragKind::kNone; break;
|
||||
}
|
||||
invalidate();
|
||||
return;
|
||||
}
|
||||
// A bare key-click inside the strip sets the selected zone's root override.
|
||||
if (contains(stripArea, x, y) && selectedZone_ >= 0 &&
|
||||
selectedZone_ < static_cast<int>(map_.zones.size())) {
|
||||
const int note = keyAtPoint(sl, lx, ly);
|
||||
if (note >= 0) {
|
||||
map_.zones[static_cast<std::size_t>(selectedZone_)].rootOverride = note;
|
||||
commitAndReload();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// S12 numeric-entry fields (low/high/root): a click focuses the field for typing. Only when a
|
||||
// zone is selected. entryText_ starts empty (the user types the full value).
|
||||
if (selectedZone_ >= 0 && selectedZone_ < static_cast<int>(map_.zones.size())) {
|
||||
const Rect fields = noteEntryFieldsArea(content);
|
||||
for (int f = 0; f < 3; ++f) {
|
||||
if (contains(noteEntryFieldRect(fields, f), x, y)) {
|
||||
entryField_ = f;
|
||||
entryText_.clear();
|
||||
invalidate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
entryField_ = -1; // a click elsewhere in the Zone view cancels an in-progress entry
|
||||
|
||||
// The per-zone param surface (FB2): the knob deck + the mini curve-preview button — the
|
||||
// SAME grammar and hit-test machinery as the Sample face. Only when a zone is selected
|
||||
// (the Zone surface has no single-capture fallback — that lives on the Sample face).
|
||||
if (selectedZone_ >= 0 && selectedZone_ < static_cast<int>(map_.zones.size())) {
|
||||
if (contains(zonesCurveButton(content), x, y)) {
|
||||
curvePopupOpen_ = true;
|
||||
invalidate();
|
||||
return;
|
||||
}
|
||||
const ZonePlaySeconds& play = map_.zones[static_cast<std::size_t>(selectedZone_)].play;
|
||||
const Rect deckArea = zonesDeckArea(content);
|
||||
const DeckLayout dl = layoutDeck(zoneDeckGroupDescs(play), deckArea.x, deckArea.y,
|
||||
deckArea.width);
|
||||
const DeckHit hit = hitTestDeck(dl, x, y);
|
||||
if (hit.kind == DeckHitKind::CaptionToggle || hit.kind == DeckHitKind::RowToggle) {
|
||||
// Zone-param toggles (play mode / pitch engine / pitch-env enable): a discrete,
|
||||
// final edit committed at once (the deck precedent). No per-instance ids reach
|
||||
// here — VOICE/MASTER are not in the zone group set.
|
||||
applyZoneControl(selectedZone_, hit.id, 0.0, hit.segment);
|
||||
commitAndReload();
|
||||
return;
|
||||
}
|
||||
if (hit.kind == DeckHitKind::Knob) {
|
||||
// PITCH ENV knobs are Disabled (drawn, inert) while the envelope is off — the
|
||||
// Sample deck's guard, mirrored.
|
||||
const bool pitchEnvKnob =
|
||||
hit.id == static_cast<int>(ParamControl::kPitchEnvAttack) ||
|
||||
hit.id == static_cast<int>(ParamControl::kPitchEnvDecay) ||
|
||||
hit.id == static_cast<int>(ParamControl::kPitchEnvDepth);
|
||||
if (pitchEnvKnob && !play.pitchEnv.enabled) return;
|
||||
// GRAB-ANCHORED vertical drag (FA4): live-drag the map, commit on release.
|
||||
drag_ = DragKind::kDeckKnob;
|
||||
dragParamId_ = hit.id;
|
||||
dragParamZone_ = selectedZone_;
|
||||
dragStartMap_ = map_;
|
||||
dragKnobStartValue_ = deckControlNorm(
|
||||
hit.id, map_.zones[static_cast<std::size_t>(selectedZone_)]);
|
||||
dragStartX_ = x;
|
||||
dragStartY_ = y;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReaSamplerEditor::onMouseWheel(int delta) {
|
||||
// Browser scroll (only in the Browse modal — the sole card grid). One wheel notch
|
||||
// (WHEEL_DELTA==120) scrolls roughly one card row; the offset is clamped at paint. A positive
|
||||
// delta (wheel up) scrolls toward the top (smaller offset).
|
||||
if (view_ != View::kBrowse) return;
|
||||
const int rows = delta / 120;
|
||||
if (rows == 0) return;
|
||||
scrollOffset_ -= rows * kBrowserCardHeight;
|
||||
if (scrollOffset_ < 0) scrollOffset_ = 0; // paint clamps the upper bound to the content
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void ReaSamplerEditor::onSearchChar(unsigned int ch) {
|
||||
// r11 curve popup: Esc dismisses (checked first — the popup is modal over the Sample face
|
||||
// or the Zone surface, FB2; opening it clears any note-entry focus, and the Browse search
|
||||
// cannot hold focus under it).
|
||||
if (curvePopupOpen_ && ch == 27) {
|
||||
curvePopupOpen_ = false;
|
||||
invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
// S12 numeric note-entry (Zone surface): a focused low/high/root field accumulates keystrokes
|
||||
// and commits via parseNoteEntry on Enter. Handled before the search box (a field, when
|
||||
// focused, owns the keystrokes).
|
||||
if (view_ == View::kZone && entryField_ >= 0) {
|
||||
if (ch == 13) { // Enter: parse + commit
|
||||
if (auto note = parseNoteEntry(entryText_)) {
|
||||
if (selectedZone_ >= 0 && selectedZone_ < static_cast<int>(map_.zones.size())) {
|
||||
PerformanceZone& z = map_.zones[static_cast<std::size_t>(selectedZone_)];
|
||||
if (entryField_ == 0) z.lowNote = (std::min)(*note, z.highNote);
|
||||
else if (entryField_ == 1) z.highNote = (std::max)(*note, z.lowNote);
|
||||
else z.rootOverride = *note;
|
||||
commitAndReload();
|
||||
}
|
||||
}
|
||||
entryField_ = -1;
|
||||
entryText_.clear();
|
||||
invalidate();
|
||||
} else if (ch == 27) { // Escape cancels
|
||||
entryField_ = -1;
|
||||
entryText_.clear();
|
||||
invalidate();
|
||||
} else if (ch == 8) { // backspace
|
||||
if (!entryText_.empty()) entryText_.pop_back();
|
||||
invalidate();
|
||||
} else if (ch >= 32 && ch < 127) {
|
||||
entryText_.push_back(static_cast<char>(ch));
|
||||
invalidate();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// S12 type-to-filter search. Only when the search box has focus (a click focuses it). Backspace
|
||||
// deletes; a printable ASCII char appends; the visible list recomposes (bank filter, then search).
|
||||
if (view_ != View::kBrowse || !searchFocused_) return;
|
||||
if (ch == 8) { // backspace
|
||||
if (!searchQuery_.empty()) searchQuery_.pop_back();
|
||||
} else if (ch == 27) { // escape clears + defocuses
|
||||
searchQuery_.clear();
|
||||
searchFocused_ = false;
|
||||
} else if (ch >= 32 && ch < 127) {
|
||||
searchQuery_.push_back(static_cast<char>(ch));
|
||||
} else {
|
||||
return; // ignore other control chars
|
||||
}
|
||||
scrollOffset_ = 0; // a new filter resets the scroll to the top of the narrowed list
|
||||
rebuildVisible();
|
||||
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
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
|
||||
#endif // _WIN32
|
||||
Reference in New Issue
Block a user