// 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(i)); const bool hot = grabbed || isHovered(HoverKind::kCurveNode, static_cast(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