Files
reasampler/src/shell/instrument/editor_input.cpp
T

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