148 lines
6.0 KiB
C++
148 lines
6.0 KiB
C++
// editor_input.cpp — the ReaSamplerEditor's input dispatch and drag-state machine: the
|
|
// mouse-down routing (curve popup first, then Browse or the three bands in order), the
|
|
// onMouseMove drag router, the release commit, and the hover resolver. The per-band
|
|
// branches live in the editor_input_<band> TUs; this TU only sequences them.
|
|
// Windows-only. All hit-test math is pure; this family routes and mutates editor state.
|
|
|
|
#include "shell/instrument/reasampler_editor.h"
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include "shell/instrument/editor_internal.h" // curveBoxFromRect + kCurveDragOffMargin
|
|
#include "shell/instrument/reasampler_processor.h"
|
|
|
|
namespace reasampler::vst {
|
|
|
|
using namespace reasampler::ui;
|
|
using namespace reasampler::instrument::ui;
|
|
|
|
void ReaSamplerEditor::onMouseDown(int x, int y) {
|
|
if (!processor_) return;
|
|
RECT cr{};
|
|
GetClientRect(childHwnd_, &cr);
|
|
const int w = cr.right - cr.left;
|
|
const int h = cr.bottom - cr.top;
|
|
|
|
if (view_ == View::kBrowse) {
|
|
mouseDownBrowse(w, h, x, y);
|
|
return;
|
|
}
|
|
|
|
// The curve popup is modal over the face — while open it owns every left-click.
|
|
if (handlePopupMouseDown(w, h, x, y)) return;
|
|
|
|
// Band order matters only where bands can overlap on a degenerate window; each branch
|
|
// reports whether it consumed the click so the next band gets a clean shot.
|
|
const FaceLayout fl = faceLayout(w, h);
|
|
if (mouseDownChrome(fl, x, y)) return;
|
|
if (selectedId_.empty()) return; // empty state — chrome nav only
|
|
if (mouseDownDeck(fl, x, y)) return;
|
|
mouseDownWaveform(fl, x, y);
|
|
}
|
|
|
|
void ReaSamplerEditor::onMouseMove(int x, int y) {
|
|
if (drag_ == DragKind::kNone) return;
|
|
dragCurX_ = x; // keep the live cursor position for drag-state draw cues (e.g. drag-off warn)
|
|
dragCurY_ = y;
|
|
|
|
// The three band-free drags resolve without a layout pass at all.
|
|
switch (drag_) {
|
|
case DragKind::kDeckKnob: dragDeck(x, y); return;
|
|
case DragKind::kScrollThumb: dragBrowse(x, y); return;
|
|
case DragKind::kCurveNode: dragCurve(x, y); return;
|
|
default: break;
|
|
}
|
|
|
|
RECT rc{};
|
|
GetClientRect(childHwnd_, &rc);
|
|
const FaceLayout fl = faceLayout(rc.right - rc.left, rc.bottom - rc.top);
|
|
if (drag_ == DragKind::kRootMarker) {
|
|
dragChrome(fl, x, y);
|
|
} else {
|
|
dragWaveform(fl, x, y);
|
|
}
|
|
}
|
|
|
|
void ReaSamplerEditor::onMouseUp(int x, int y) {
|
|
// Release a held preview note first (the preview button is a momentary key: note-off on up).
|
|
// This runs regardless of drag state — the preview press does not start a drag.
|
|
if (previewingNote_ >= 0) {
|
|
if (processor_) processor_->previewNoteOff(previewingNote_);
|
|
previewingNote_ = -1;
|
|
invalidate();
|
|
}
|
|
if (drag_ == DragKind::kNone) return;
|
|
const DragKind kind = drag_;
|
|
const int paramId = dragParamId_;
|
|
const int curveIdx = curvePointIndex_;
|
|
const Rect curveRect = dragCurveRect_;
|
|
drag_ = DragKind::kNone;
|
|
dragParamId_ = -1;
|
|
curvePointIndex_ = -1;
|
|
// hover_ is deliberately not re-resolved during a drag (see resolveHover's caller), so it
|
|
// still names wherever the drag started. Re-resolve now against the release position, for
|
|
// every drag kind — otherwise the next paint latches a stale hover (wrong note name/tooltip,
|
|
// wrong control outline) until the next WM_MOUSEMOVE.
|
|
resolveHover(x, y);
|
|
// A scrollbar drag is transient UI (no parameter change), and the processor-side knobs
|
|
// (the preview-velocity -2 sentinel, voice count, master gain) are per-instance settings
|
|
// that don't reload the instrument. Master gain is an atomic the audio thread reads
|
|
// directly. Voice count: the label/needle tracks live during the drag but the engine
|
|
// rebuild (setVoiceCount) fires ONCE here on release — not per integer step.
|
|
const bool deckTransient =
|
|
kind == DragKind::kDeckKnob &&
|
|
(paramId == -2 || paramId == static_cast<int>(ParamControl::kVoiceCount) ||
|
|
paramId == static_cast<int>(ParamControl::kMasterGain));
|
|
if (kind == DragKind::kScrollThumb || deckTransient) {
|
|
// Commit the voice count now that the drag is complete (one rebuild per full drag).
|
|
if (deckTransient && processor_ &&
|
|
paramId == static_cast<int>(ParamControl::kVoiceCount))
|
|
processor_->setVoiceCount(voiceCount_);
|
|
invalidate();
|
|
return;
|
|
}
|
|
// Drag-off delete: releasing a curve-node drag well outside the box removes the dragged
|
|
// point (deletePoint refuses the two endpoints, so an endpoint drag-off is a plain move —
|
|
// its amp keeps the last clamped drag value).
|
|
if (kind == DragKind::kCurveNode && curveIdx >= 0) {
|
|
const bool off = x < curveRect.x - kCurveDragOffMargin ||
|
|
x > curveRect.right() + kCurveDragOffMargin ||
|
|
y < curveRect.y - kCurveDragOffMargin ||
|
|
y > curveRect.bottom() + kCurveDragOffMargin;
|
|
if (off) {
|
|
params_.velocityCurve.deletePoint(static_cast<std::size_t>(curveIdx));
|
|
hover_ = HoverTarget{}; // stale kCurveNode index would light a shifted node
|
|
}
|
|
}
|
|
commitAndReload();
|
|
}
|
|
|
|
// Resolve the interactive element under (x, y) into hover_ and repaint only on change (an
|
|
// idle move is free). Mirrors onMouseDown's routing order, but read-only.
|
|
void ReaSamplerEditor::resolveHover(int x, int y) {
|
|
RECT cr{};
|
|
GetClientRect(childHwnd_, &cr);
|
|
const int w = cr.right - cr.left;
|
|
const int hgt = cr.bottom - cr.top;
|
|
|
|
HoverTarget h; // kNone by default
|
|
if (view_ == View::kBrowse) {
|
|
h = hoverBrowse(w, hgt, x, y);
|
|
} else if (curvePopupOpen_) { // modal over the face
|
|
h = hoverCurvePopup(w, hgt, x, y);
|
|
} else {
|
|
const FaceLayout fl = faceLayout(w, hgt);
|
|
h = hoverChrome(fl, x, y);
|
|
if (h.kind == HoverKind::kNone && !selectedId_.empty()) h = hoverDeck(fl, x, y);
|
|
}
|
|
|
|
if (h != hover_) {
|
|
hover_ = h;
|
|
invalidate();
|
|
}
|
|
}
|
|
|
|
} // namespace reasampler::vst
|
|
|
|
#endif // _WIN32
|