b4b64ce68f
Tag LoadedInstrument with installedAt; process() publishes that field (not a re-read of reloadGeneration_) so the pruner's displacedAt <= seen bound is airtight. Also fixes sampleRowHitTest canvas.bottom over-read and adds interleave-stride + canvas-clip tests.
79 lines
2.8 KiB
C++
79 lines
2.8 KiB
C++
// editor_geometry.cpp — see editor_geometry.h. Pure math; no host types.
|
|
|
|
#include "editor_geometry.h"
|
|
|
|
#include <algorithm>
|
|
|
|
namespace reasampler::vst {
|
|
|
|
namespace {
|
|
|
|
// Spike editor layout constants. These are the editor's fixed metrics; the real
|
|
// editor (S4/S5) will parameterize as its content demands.
|
|
constexpr int kTitleBarHeight = 28;
|
|
constexpr int kButtonMargin = 10;
|
|
constexpr int kButtonWidth = 120;
|
|
constexpr int kButtonHeight = 24;
|
|
|
|
} // namespace
|
|
|
|
bool contains(const Rect& r, int x, int y) {
|
|
if (r.width() <= 0 || r.height() <= 0) return false;
|
|
return x >= r.left && x < r.right && y >= r.top && y < r.bottom;
|
|
}
|
|
|
|
EditorLayout layoutEditor(int w, int h) {
|
|
// Clamp the surface to non-negative extents so a degenerate view can't produce
|
|
// inverted rects.
|
|
const int cw = std::max(0, w);
|
|
const int ch = std::max(0, h);
|
|
|
|
EditorLayout out;
|
|
|
|
// Title bar spans the top, clamped so it never exceeds the client height.
|
|
const int titleH = std::min(kTitleBarHeight, ch);
|
|
out.titleBar = Rect{0, 0, cw, titleH};
|
|
|
|
// Canvas is everything below the title bar.
|
|
out.canvas = Rect{0, titleH, cw, ch};
|
|
|
|
// Button sits at the top-left of the canvas, inset by a margin, and is clamped to
|
|
// fit inside the canvas so it never overhangs on a small view.
|
|
const int bx = out.canvas.left + kButtonMargin;
|
|
const int by = out.canvas.top + kButtonMargin;
|
|
const int bRight = std::min(bx + kButtonWidth, out.canvas.right);
|
|
const int bBottom = std::min(by + kButtonHeight, out.canvas.bottom);
|
|
out.button = Rect{bx, by, std::max(bx, bRight), std::max(by, bBottom)};
|
|
|
|
return out;
|
|
}
|
|
|
|
HitTarget hitTest(const EditorLayout& layout, int x, int y) {
|
|
if (contains(layout.button, x, y)) return HitTarget::kButton;
|
|
return HitTarget::kNone;
|
|
}
|
|
|
|
Rect sampleRowRect(const EditorLayout& layout, int index) {
|
|
if (index < 0) return Rect{};
|
|
const int top = layout.canvas.top + index * kSampleRowHeight;
|
|
return Rect{layout.canvas.left, top, layout.canvas.right, top + kSampleRowHeight};
|
|
}
|
|
|
|
int sampleRowHitTest(const EditorLayout& layout, int rowCount, int x, int y) {
|
|
if (rowCount <= 0) return -1;
|
|
// Must be within the canvas horizontally and at/below its top.
|
|
if (x < layout.canvas.left || x >= layout.canvas.right) return -1;
|
|
if (y < layout.canvas.top) return -1;
|
|
// Clip at the canvas bottom: clicks in the canvas's dead-zone below the last
|
|
// visible row agree with sampleRowRect, which does not clamp rows to canvas.bottom.
|
|
if (y >= layout.canvas.bottom) return -1;
|
|
const int index = (y - layout.canvas.top) / kSampleRowHeight;
|
|
if (index < 0 || index >= rowCount) return -1;
|
|
// Guard the bottom edge: a click below the last row's bottom is outside.
|
|
const Rect r = sampleRowRect(layout, index);
|
|
if (y >= r.bottom) return -1;
|
|
return index;
|
|
}
|
|
|
|
} // namespace reasampler::vst
|