126 lines
6.7 KiB
C++
126 lines
6.7 KiB
C++
// editor_paint_curve.cpp — the velocity->amp curve surfaces: the chrome band's mini
|
|
// preview button and the modal popup sheet that hosts the full editor. Band-independent
|
|
// (the popup floats over the whole face). Windows-only.
|
|
|
|
#include "shell/instrument/reasampler_editor.h"
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include "core/instrument/ui/curve_popup.h" // centered curve-popup sheet geometry
|
|
#include "shell/instrument/editor_internal.h" // kit adapters + curveBoxFromRect
|
|
#include "shell/instrument/reasampler_processor.h"
|
|
|
|
namespace reasampler::vst {
|
|
|
|
using namespace reasampler::ui; // kit vocabulary
|
|
using namespace reasampler::instrument::ui; // popup geometry
|
|
|
|
void ReaSamplerEditor::paintCurveButton(LICE_IBitmap* bmp, const Rect& r) {
|
|
if (r.width <= 0 || r.height <= 0) return;
|
|
// A hairline-bordered bg/cell square with the live velocity curve traced in miniature
|
|
// (no node markers at this scale). Hover lifts it; it draws Active (accent-primary
|
|
// border) while its popup is open, and re-renders live as the popup edits the curve.
|
|
const bool hov = isHovered(HoverKind::kCurveButton, -1);
|
|
fillSurface(bmp, toKitBox(r), Role::BgCell,
|
|
hov ? InteractionState::Hover : InteractionState::Rest);
|
|
const KitColor border = curvePopupOpen_ ? roleColor(Role::AccentPrimary)
|
|
: roleColor(Role::LineHairline);
|
|
LICE_DrawRect(bmp, r.x, r.y, r.width - 1, r.height - 1, toLice(border), 1.0f, 0);
|
|
const VelocityCurve& curve = params_.velocityCurve;
|
|
const int inset = 3;
|
|
const VelocityCurve::Box mini{r.x + inset, r.y + inset, r.width - 2 * inset,
|
|
r.height - 2 * inset};
|
|
if (mini.width > 1 && mini.height > 1) {
|
|
const LICE_pixel trace = toLice(roleColor(Role::AccentSecondary));
|
|
int prevX = 0, prevY = 0;
|
|
for (int px = 0; px <= mini.width; ++px) {
|
|
const int mx = mini.left + px;
|
|
const double vel = VelocityCurve::pointFromPixel(mini, mx, mini.top).velocity;
|
|
const int my = VelocityCurve::pixelFromPoint(mini, {vel, curve.eval(vel)}).y;
|
|
if (px > 0) LICE_Line(bmp, prevX, prevY, mx, my, trace, 1.0f, 0, true);
|
|
prevX = mx;
|
|
prevY = my;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ReaSamplerEditor::paintCurvePopup(LICE_IBitmap* bmp, int w, int h) {
|
|
// The 0.50-alpha bg/base wash (lighter than Browse's 0.82 — a focused sub-editor; the
|
|
// face stays legible behind it), then the centered sheet.
|
|
LICE_FillRect(bmp, 0, 0, w, h, toLice(roleColor(Role::BgBase)), 0.50f, 0);
|
|
const CurvePopupLayout pl = computeCurvePopup(w, h);
|
|
fillSurface(bmp, toKitBox(pl.sheet), Role::BgPanel, InteractionState::Rest);
|
|
LICE_DrawRect(bmp, pl.sheet.x, pl.sheet.y, pl.sheet.width - 1,
|
|
pl.sheet.height - 1, toLice(roleColor(Role::LineHairline)), 1.0f, 0);
|
|
kitText(bmp, pl.title, "VELOCITY -> AMP", Font::Micro, Role::TextDim);
|
|
{
|
|
const KitButtonBox box{toKitBox(pl.close)};
|
|
const InteractionState st = isHovered(HoverKind::kPopupClose, -1)
|
|
? InteractionState::Hover
|
|
: InteractionState::Rest;
|
|
drawButton(bmp, box, "x", st, /*warn=*/false);
|
|
}
|
|
// The full-size editor: one draw path + the one curveBoxFromRect mapping formula, so
|
|
// trace/handles/drag-off cues cannot drift from the hit-test.
|
|
paintVelocityCurve(bmp, pl.curveBox);
|
|
}
|
|
|
|
void ReaSamplerEditor::paintVelocityCurve(LICE_IBitmap* bmp, const Rect& r) {
|
|
if (r.width <= 0 || r.height <= 0) return; // defensive (degenerate rect)
|
|
|
|
// The bordered box: a panel surface + hairline border, drawn by palette role. No corner
|
|
// caption — the popup sheet's own "VELOCITY -> AMP" title labels this context (the popup
|
|
// is the only host).
|
|
fillSurface(bmp, toKitBox(r), Role::BgPanel, InteractionState::Rest);
|
|
LICE_DrawRect(bmp, r.x, r.y, r.width - 1, r.height - 1,
|
|
toLice(roleColor(Role::LineHairline)), 1.0f, 0);
|
|
|
|
const VelocityCurve::Box box = curveBoxFromRect(r);
|
|
if (box.width <= 0 || box.height <= 1) return;
|
|
const VelocityCurve& curve = params_.velocityCurve;
|
|
|
|
// Trace the monotone spline — ONE eval per x column over the mapping box, in the categorical
|
|
// secondary accent (the same grammar as the envelope trace over the waveform). The x ->
|
|
// velocity and amp -> y mappings both go through the pure module so the trace, the node
|
|
// handles, and the hit-test all share one coordinate system.
|
|
const LICE_pixel line = toLice(roleColor(Role::AccentSecondary));
|
|
int prevX = 0, prevY = 0;
|
|
for (int px = 0; px <= box.width; ++px) {
|
|
const int cx = box.left + px;
|
|
const double vel = VelocityCurve::pointFromPixel(box, cx, box.top).velocity;
|
|
const int cy = VelocityCurve::pixelFromPoint(box, {vel, curve.eval(vel)}).y;
|
|
if (px > 0) LICE_Line(bmp, prevX, prevY, cx, cy, line, 1.0f, 0, true);
|
|
prevX = cx;
|
|
prevY = cy;
|
|
}
|
|
|
|
// Draggable node handles (mirror of the envelope overlay's): accent-primary squares lifted
|
|
// 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_.x == r.x &&
|
|
dragCurveRect_.y == r.y) &&
|
|
(dragCurX_ < r.x - kCurveDragOffMargin ||
|
|
dragCurX_ > r.right() + kCurveDragOffMargin ||
|
|
dragCurY_ < r.y - 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, col, 1.0f, 0);
|
|
}
|
|
}
|
|
|
|
} // namespace reasampler::vst
|
|
|
|
#endif // _WIN32
|