fix: pin the waveform arbitration in a testable predicate, close round-4 review minors

Extracts resolveWaveformClaim (core/instrument/ui/spline_edit) so the shell's node/tab/marker click resolution is unit-tested directly, not just its input geometry; folds the staged-envelope node into it; fixes comment accuracy, a cost regression, and test fidelity issues.
This commit is contained in:
2026-08-01 00:17:06 -04:00
parent 757e1585d6
commit aedcc6976c
10 changed files with 316 additions and 179 deletions
+30 -4
View File
@@ -1,11 +1,13 @@
// spline_edit.h — the point-editing grammar's CLICK resolution: add/grab/delete/toggle from a
// single (x, y). Both spline consumers — the velocity-curve popup and the spline EG overlay —
// route their mouse-down through it, so the two cannot drift into two click grammars. Two more
// gesture rules complete the grammar but live in the shell (see the note near the bottom of this
// file). Mirror of envelope_edit otherwise: decision logic only, no host types, no drawing.
// single (x, y), plus resolveWaveformClaim, the waveform overlay's cross-affordance arbitration
// (node vs. crossfade tab vs. marker). Both spline consumers — the velocity-curve popup and the
// spline EG overlay — route their mouse-down through the click grammar, so the two cannot drift
// apart. Decision logic only, no host types, no drawing; mirror of envelope_edit otherwise.
#pragma once
#include <cstdint>
#include "core/instrument/engine/velocity_curve.h"
#include "core/instrument/ui/editor_geometry.h" // Rect / OverlayArea
@@ -51,4 +53,28 @@ VelocityCurve::Box splineOverlayBox(const OverlayArea& area);
// strictly in-box, since splineOverlayBox has no inset — editor_input_waveform.cpp's
// splineOverlayClick.
// One arbitration candidate: whether the affordance was hit under the cursor, and its own
// pick-target area (nominal, per its own module's constants — not the actual clipped pixel
// count; see resolveWaveformClaim).
struct WaveformClaim {
bool hit = false;
std::int64_t area = 0;
};
// Which affordance a waveform-overlay click claims.
enum class WaveformClaimant { kNone, kNode, kTab, kMarker };
// The overlay's cross-affordance arbitration: a contour node (or, mutually exclusively, a
// staged envelope's drag node — both feed the same `node` slot), the loop crossfade tab, and a
// marker's full-height column can all claim the same pixel. Hit gates a candidate out
// entirely; among the ones that hit, the SMALLEST nominal area wins — the marker column is the
// odd one out (its target is the whole overlay height), so it only wins where nothing narrower
// also claims the click. Ties go to whichever is checked first: node, then tab, then marker —
// no live geometry produces a tie except tab-vs-marker, which the tab correctly wins (see
// editor_input_waveform.cpp's mouseDownWaveform for the live constants). A control-click has no
// tab/marker meaning (they answer plain grabs only), so it resolves to the node whenever the
// node is in the running, regardless of area.
WaveformClaimant resolveWaveformClaim(const WaveformClaim& node, const WaveformClaim& tab,
const WaveformClaim& marker, SplineGesture gesture);
} // namespace reasampler::instrument::ui