// editor_geometry.cpp — see editor_geometry.h. Pure math; no host types. #include "editor_geometry.h" #include 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; } // --- Keymap editor ----------------------------------------------------------- KeymapEditorLayout layoutKeymapEditor(int w, int h) { KeymapEditorLayout out; out.base = layoutEditor(w, h); const Rect& canvas = out.base.canvas; // Split the canvas vertically: the left column is the bank-sample list, the right // column (1/kZonePanelFraction of the width) is the zone panel. Guard tiny widths so // the split point never crosses the canvas edges. const int canvasW = std::max(0, canvas.width()); const int splitW = canvasW / kZonePanelFraction; // width of the zone panel const int splitX = std::max(canvas.left, canvas.right - splitW); out.sampleList = Rect{canvas.left, canvas.top, splitX, canvas.bottom}; out.zonePanel = Rect{splitX, canvas.top, canvas.right, canvas.bottom}; // "Add Zone" button spans the top of the zone panel, clamped to its height. const int addH = std::min(kAddZoneHeight, std::max(0, out.zonePanel.height())); out.addZoneButton = Rect{out.zonePanel.left, out.zonePanel.top, out.zonePanel.right, out.zonePanel.top + addH}; // Zone rows stack below the button. out.zoneRowArea = Rect{out.zonePanel.left, out.addZoneButton.bottom, out.zonePanel.right, out.zonePanel.bottom}; return out; } Rect keymapSampleRowRect(const KeymapEditorLayout& layout, int index) { if (index < 0) return Rect{}; const int top = layout.sampleList.top + index * kSampleRowHeight; return Rect{layout.sampleList.left, top, layout.sampleList.right, top + kSampleRowHeight}; } int keymapSampleRowHitTest(const KeymapEditorLayout& layout, int rowCount, int x, int y) { if (rowCount <= 0) return -1; const Rect& list = layout.sampleList; if (x < list.left || x >= list.right) return -1; if (y < list.top || y >= list.bottom) return -1; const int index = (y - list.top) / kSampleRowHeight; if (index < 0 || index >= rowCount) return -1; const Rect r = keymapSampleRowRect(layout, index); if (y >= r.bottom) return -1; return index; } Rect zoneRowRect(const KeymapEditorLayout& layout, int index) { if (index < 0) return Rect{}; const int top = layout.zoneRowArea.top + index * kZoneRowHeight; return Rect{layout.zoneRowArea.left, top, layout.zoneRowArea.right, top + kZoneRowHeight}; } ZoneHit zoneHitTest(const KeymapEditorLayout& layout, int zoneCount, int x, int y) { if (zoneCount <= 0) return ZoneHit{}; const Rect& area = layout.zoneRowArea; if (x < area.left || x >= area.right) return ZoneHit{}; if (y < area.top || y >= area.bottom) return ZoneHit{}; const int index = (y - area.top) / kZoneRowHeight; if (index < 0 || index >= zoneCount) return ZoneHit{}; const Rect row = zoneRowRect(layout, index); if (y >= row.bottom) return ZoneHit{}; // Seven mini-buttons pinned to the right edge, right-to-left: // delete, root+, root-, high+, high-, low+, low- // Each is kZoneCtrlWidth wide. A click left of the leftmost is the label ("select"). // The fields laid out LEFT-TO-RIGHT in slot order 0..6. const ZoneField fields[7] = { ZoneField::kLowDown, ZoneField::kLowUp, ZoneField::kHighDown, ZoneField::kHighUp, ZoneField::kRootDown, ZoneField::kRootUp, ZoneField::kDelete, }; const int slots = 7; const int ctrlBlockLeft = row.right - slots * kZoneCtrlWidth; if (x < ctrlBlockLeft) return ZoneHit{index, ZoneField::kZoneNone}; // label -> select const int slot = (x - ctrlBlockLeft) / kZoneCtrlWidth; if (slot < 0 || slot >= slots) return ZoneHit{index, ZoneField::kZoneNone}; return ZoneHit{index, fields[slot]}; } bool addZoneHitTest(const KeymapEditorLayout& layout, int x, int y) { return contains(layout.addZoneButton, x, y); } } // namespace reasampler::vst