fix(velcurve-ui): caption overlap, add-in-ring data corruption, drag-off warn affordance
This commit is contained in:
@@ -700,7 +700,7 @@ constexpr int kPad = 8;
|
||||
// border so an endpoint at amp 0/1 stays grabbable.
|
||||
constexpr int kVelCurveBoxW = 168; // the curve box width (incl. border)
|
||||
constexpr int kVelCurveMinHeroW = 240; // hero must keep at least this much width beside the box
|
||||
constexpr int kVelCurveInset = 6; // border -> mapping-box inset (>= node half-size + 3)
|
||||
constexpr int kVelCurveInset = 14; // border -> mapping-box inset: caption band (~12px) + 2px gap
|
||||
constexpr int kCurveDragOffMargin = 24; // release beyond box+margin -> drag-off delete
|
||||
|
||||
struct SampleBands {
|
||||
@@ -1157,17 +1157,28 @@ void ReaSamplerEditor::paintVelocityCurve(LICE_IBitmap* bmp, const Rect& r,
|
||||
}
|
||||
|
||||
// Draggable node handles (mirror of the envelope overlay's): accent-primary squares lifted
|
||||
// to accent-hot when grabbed or hovered.
|
||||
// to accent-hot when grabbed or hovered, or warn when a drag-off delete is armed (cursor
|
||||
// has passed kCurveDragOffMargin outside the box — release will delete the node).
|
||||
const LICE_pixel handle = toLice(roleColor(Role::AccentPrimary));
|
||||
const LICE_pixel handleHot = toLice(roleColor(Role::AccentHot));
|
||||
const LICE_pixel handleWarn = toLice(roleColor(Role::Warn));
|
||||
// Drag-off check: during a kCurveNode drag on THIS box, is the live cursor beyond the margin?
|
||||
const bool dragOffArmed = (drag_ == DragKind::kCurveNode && dragCurveRect_.left == r.left &&
|
||||
dragCurveRect_.top == r.top) &&
|
||||
(dragCurX_ < r.left - kCurveDragOffMargin ||
|
||||
dragCurX_ > r.right + kCurveDragOffMargin ||
|
||||
dragCurY_ < r.top - kCurveDragOffMargin ||
|
||||
dragCurY_ > r.bottom + kCurveDragOffMargin);
|
||||
for (std::size_t i = 0; i < curve.points().size(); ++i) {
|
||||
const auto np = VelocityCurve::pixelFromPoint(box, curve.points()[i]);
|
||||
const bool grabbed = (drag_ == DragKind::kCurveNode &&
|
||||
curvePointIndex_ == static_cast<int>(i));
|
||||
const bool hot = grabbed || isHovered(HoverKind::kCurveNode, static_cast<int>(i));
|
||||
// A grabbed node in drag-off territory draws warn to signal "release will delete."
|
||||
const LICE_pixel col = (grabbed && dragOffArmed) ? handleWarn
|
||||
: (hot ? handleHot : handle);
|
||||
const int nr = 3;
|
||||
LICE_FillRect(bmp, np.x - nr, np.y - nr, 2 * nr, 2 * nr, hot ? handleHot : handle,
|
||||
1.0f, 0);
|
||||
LICE_FillRect(bmp, np.x - nr, np.y - nr, 2 * nr, 2 * nr, col, 1.0f, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1193,13 +1204,24 @@ void ReaSamplerEditor::handleCurveMouseDown(const Rect& r, int zoneIndex, int x,
|
||||
// ADD (mirror of the other map-editing drags' dragStartMap_ contract).
|
||||
dragStartMap_ = map_;
|
||||
|
||||
// Empty-space click: add a control point at the cursor via the pure inverse map, then grab
|
||||
// it — the click flows straight into a placing drag.
|
||||
// Empty-space click inside the MAPPING BOX: add a control point at the cursor via the pure
|
||||
// inverse map, then grab it — the click flows straight into a placing drag. Guard: the caller
|
||||
// gates on contains(r, x, y) (the full border rect), but the 6+px inset ring — including the
|
||||
// caption band — must not add a point; a click there would clamp to velocity 0/127 and
|
||||
// produce an undeletable duplicate stacked on an endpoint. Clicks in the ring may still grab
|
||||
// an existing node (pointAtPixel's pick radius legitimately extends into the ring), which is
|
||||
// handled above; only the add path is box-gated here.
|
||||
if (idx < 0) {
|
||||
const VelocityPoint p = VelocityCurve::pointFromPixel(box, x, y);
|
||||
idx = static_cast<int>(z.velocityCurve.addPoint(p.velocity, p.amp));
|
||||
const bool inBox = (x >= box.left && x < box.left + box.width &&
|
||||
y >= box.top && y < box.top + box.height);
|
||||
if (inBox) {
|
||||
const VelocityPoint p = VelocityCurve::pointFromPixel(box, x, y);
|
||||
idx = static_cast<int>(z.velocityCurve.addPoint(p.velocity, p.amp));
|
||||
}
|
||||
}
|
||||
|
||||
if (idx < 0) return; // ring click with no node hit — nothing to grab
|
||||
|
||||
drag_ = DragKind::kCurveNode;
|
||||
curvePointIndex_ = idx;
|
||||
dragStartCurve_ = z.velocityCurve; // AFTER the add — resolvePointDrag's absolute-delta base
|
||||
@@ -2056,6 +2078,8 @@ bool ReaSamplerEditor::handleControlClick(int zoneIndex, const Rect& panel, int
|
||||
|
||||
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;
|
||||
RECT cr{};
|
||||
GetClientRect(childHwnd_, &cr);
|
||||
const int w = cr.right - cr.left;
|
||||
@@ -2276,6 +2300,7 @@ void ReaSamplerEditor::onMouseUp(int x, int y) {
|
||||
if (off) {
|
||||
map_.zones[static_cast<std::size_t>(curveZone)].velocityCurve.deletePoint(
|
||||
static_cast<std::size_t>(curveIdx));
|
||||
hover_ = HoverTarget{}; // stale kCurveNode index would light a shifted node on next paint
|
||||
}
|
||||
}
|
||||
commitAndReload();
|
||||
|
||||
@@ -392,6 +392,8 @@ private:
|
||||
DragKind drag_ = DragKind::kNone;
|
||||
int dragStartX_ = 0; // grab x (px), for the pixel-delta resolver
|
||||
int dragStartY_ = 0; // grab y (px), for the vertical scrollbar-thumb drag
|
||||
int dragCurX_ = 0; // live cursor x (px) during a drag — updated in onMouseMove
|
||||
int dragCurY_ = 0; // live cursor y (px) during a drag — updated in onMouseMove
|
||||
int dragStartLow_ = 0; // the grabbed field's note at grab time
|
||||
int dragStartHigh_ = 0;
|
||||
int dragStartRoot_ = 60;
|
||||
|
||||
Reference in New Issue
Block a user