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.
|
// border so an endpoint at amp 0/1 stays grabbable.
|
||||||
constexpr int kVelCurveBoxW = 168; // the curve box width (incl. border)
|
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 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
|
constexpr int kCurveDragOffMargin = 24; // release beyond box+margin -> drag-off delete
|
||||||
|
|
||||||
struct SampleBands {
|
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
|
// 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 handle = toLice(roleColor(Role::AccentPrimary));
|
||||||
const LICE_pixel handleHot = toLice(roleColor(Role::AccentHot));
|
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) {
|
for (std::size_t i = 0; i < curve.points().size(); ++i) {
|
||||||
const auto np = VelocityCurve::pixelFromPoint(box, curve.points()[i]);
|
const auto np = VelocityCurve::pixelFromPoint(box, curve.points()[i]);
|
||||||
const bool grabbed = (drag_ == DragKind::kCurveNode &&
|
const bool grabbed = (drag_ == DragKind::kCurveNode &&
|
||||||
curvePointIndex_ == static_cast<int>(i));
|
curvePointIndex_ == static_cast<int>(i));
|
||||||
const bool hot = grabbed || isHovered(HoverKind::kCurveNode, 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;
|
const int nr = 3;
|
||||||
LICE_FillRect(bmp, np.x - nr, np.y - nr, 2 * nr, 2 * nr, hot ? handleHot : handle,
|
LICE_FillRect(bmp, np.x - nr, np.y - nr, 2 * nr, 2 * nr, col, 1.0f, 0);
|
||||||
1.0f, 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1193,12 +1204,23 @@ void ReaSamplerEditor::handleCurveMouseDown(const Rect& r, int zoneIndex, int x,
|
|||||||
// ADD (mirror of the other map-editing drags' dragStartMap_ contract).
|
// ADD (mirror of the other map-editing drags' dragStartMap_ contract).
|
||||||
dragStartMap_ = map_;
|
dragStartMap_ = map_;
|
||||||
|
|
||||||
// Empty-space click: add a control point at the cursor via the pure inverse map, then grab
|
// Empty-space click inside the MAPPING BOX: add a control point at the cursor via the pure
|
||||||
// it — the click flows straight into a placing drag.
|
// 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) {
|
if (idx < 0) {
|
||||||
|
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);
|
const VelocityPoint p = VelocityCurve::pointFromPixel(box, x, y);
|
||||||
idx = static_cast<int>(z.velocityCurve.addPoint(p.velocity, p.amp));
|
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;
|
drag_ = DragKind::kCurveNode;
|
||||||
curvePointIndex_ = idx;
|
curvePointIndex_ = idx;
|
||||||
@@ -2056,6 +2078,8 @@ bool ReaSamplerEditor::handleControlClick(int zoneIndex, const Rect& panel, int
|
|||||||
|
|
||||||
void ReaSamplerEditor::onMouseMove(int x, int y) {
|
void ReaSamplerEditor::onMouseMove(int x, int y) {
|
||||||
if (drag_ == DragKind::kNone) return;
|
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{};
|
RECT cr{};
|
||||||
GetClientRect(childHwnd_, &cr);
|
GetClientRect(childHwnd_, &cr);
|
||||||
const int w = cr.right - cr.left;
|
const int w = cr.right - cr.left;
|
||||||
@@ -2276,6 +2300,7 @@ void ReaSamplerEditor::onMouseUp(int x, int y) {
|
|||||||
if (off) {
|
if (off) {
|
||||||
map_.zones[static_cast<std::size_t>(curveZone)].velocityCurve.deletePoint(
|
map_.zones[static_cast<std::size_t>(curveZone)].velocityCurve.deletePoint(
|
||||||
static_cast<std::size_t>(curveIdx));
|
static_cast<std::size_t>(curveIdx));
|
||||||
|
hover_ = HoverTarget{}; // stale kCurveNode index would light a shifted node on next paint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
commitAndReload();
|
commitAndReload();
|
||||||
|
|||||||
@@ -392,6 +392,8 @@ private:
|
|||||||
DragKind drag_ = DragKind::kNone;
|
DragKind drag_ = DragKind::kNone;
|
||||||
int dragStartX_ = 0; // grab x (px), for the pixel-delta resolver
|
int dragStartX_ = 0; // grab x (px), for the pixel-delta resolver
|
||||||
int dragStartY_ = 0; // grab y (px), for the vertical scrollbar-thumb drag
|
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 dragStartLow_ = 0; // the grabbed field's note at grab time
|
||||||
int dragStartHigh_ = 0;
|
int dragStartHigh_ = 0;
|
||||||
int dragStartRoot_ = 60;
|
int dragStartRoot_ = 60;
|
||||||
|
|||||||
Reference in New Issue
Block a user