134 lines
6.8 KiB
C++
134 lines
6.8 KiB
C++
// knob_deck.h — PURE knob-deck layout + hit-test for the r11 Sample-face recomposition
|
|
// (Wave B, FB1). NO VST3, NO REAPER, NO SWELL/LICE types at the boundary, and — like
|
|
// param_slider — NO engine types: cells and toggles carry opaque shell-owned control ids.
|
|
// The mirror of action_bar / param_slider: the fiddly group-box / caption-row / cell-grid
|
|
// arithmetic lives here, unit-tested outside the DAW, while the editor shell draws each
|
|
// group (fence, caption, compact toggles, knobs) through the L1 kit and routes clicks/drags
|
|
// via the hit-test. The KNOB PRIMITIVE itself (value<->needle-angle, vertical drag) is
|
|
// param_slider's (FA4); a knob cell here is just a rect — the shell composes the two.
|
|
//
|
|
// THE DECK (CONTEXT.md §S-VIEW r11). A horizontal run of FENCED GROUPS, left -> right, each
|
|
// a hairline-bordered bg/panel box with a CAPTION ROW (micro-caps caption left; the group's
|
|
// compact mode toggle right-anchored IN the caption row — this is where the not-full-width
|
|
// toggles live) over a KNOB ROW of fixed 48x58 cells (28px knob centered, 12px label band
|
|
// beneath). A group may additionally place one 18px-tall two-segment toggle IN the knob row
|
|
// after its cells (the VOICE group's Retrig|Legato — same Mono/Stereo segment grammar,
|
|
// vertically centered). Groups that must keep stable geometry across a mode flip reserve
|
|
// blank cells (id -1): the AMP ENVELOPE group always spans 5 cells so Gate<->Trigger never
|
|
// reflows its neighbours.
|
|
//
|
|
// WRAP (deterministic): groups place left-to-right with kDeckGroupGap between; a group that
|
|
// does not fit the remaining width starts a new deck row (whole groups only, never split).
|
|
// The first group of a row always places even if wider than the row (degenerate width).
|
|
// deckHeight() exposes the resulting height so the shell can bottom-anchor the deck band and
|
|
// give the ELASTIC HERO the rest (r11 band order).
|
|
|
|
#pragma once
|
|
|
|
#include <vector>
|
|
|
|
#include "editor_geometry.h" // Rect, contains — the shared geometry idiom
|
|
|
|
namespace reasampler::vst {
|
|
|
|
// Fixed deck metrics (spec r11), exposed so the shell and tests agree.
|
|
inline constexpr int kDeckCellW = 48; // one knob cell
|
|
inline constexpr int kDeckCellH = 58;
|
|
inline constexpr int kDeckKnobSize = 28; // knob diameter inside the cell
|
|
inline constexpr int kDeckCellLabelH = 12; // the Micro label band under the knob
|
|
inline constexpr int kDeckCaptionH = 20; // the group caption row
|
|
inline constexpr int kDeckToggleH = 18; // compact toggle segment height
|
|
inline constexpr int kDeckGroupPadX = 6; // group box horizontal inner padding
|
|
inline constexpr int kDeckGroupPadY = 4; // group box vertical inner padding
|
|
inline constexpr int kDeckCaptionGap = 2; // caption row -> knob row gap
|
|
inline constexpr int kDeckToggleGap = 4; // caption text -> toggle / cells -> row toggle gap
|
|
inline constexpr int kDeckGroupGap = 12; // gap between groups on a row
|
|
inline constexpr int kDeckRowGap = 8; // gap between wrapped deck rows
|
|
// One group box: padding + caption + gap + cell row + padding.
|
|
inline constexpr int kDeckGroupH =
|
|
kDeckGroupPadY + kDeckCaptionH + kDeckCaptionGap + kDeckCellH + kDeckGroupPadY;
|
|
|
|
// A two-segment compact toggle (always 2 segments — the Mono/Stereo grammar). id -1 = absent.
|
|
struct DeckToggleDesc {
|
|
int id = -1; // shell control id returned by the hit-test; -1 = no toggle
|
|
int segWidth = 44; // px per segment
|
|
};
|
|
|
|
// One fenced group, in deck order. `cellIds` are the knob cells left-to-right; an id of -1
|
|
// is a RESERVED BLANK cell (geometry held, never hit — the AMP ENVELOPE Trigger face).
|
|
// `captionWidth` is the px the shell reserves for the caption text (this module does not
|
|
// measure text — the house constant-metrics pattern).
|
|
struct DeckGroupDesc {
|
|
int id = 0; // shell group id (opaque here)
|
|
int captionWidth = 60;
|
|
DeckToggleDesc captionToggle; // right-anchored in the caption row; id -1 = none
|
|
std::vector<int> cellIds; // knob cells; -1 = blank reserve
|
|
DeckToggleDesc rowToggle; // in the knob row after the cells; id -1 = none
|
|
};
|
|
|
|
// --- Laid-out geometry ---------------------------------------------------------------
|
|
|
|
struct DeckToggleLayout {
|
|
int id = -1;
|
|
Rect seg0; // left segment
|
|
Rect seg1; // right segment
|
|
};
|
|
|
|
struct DeckCellLayout {
|
|
int id = -1;
|
|
Rect cell; // the full 48x58 cell
|
|
Rect knob; // the centered kDeckKnobSize square (the knob circle inscribes it)
|
|
Rect label; // the 12px label band beneath the knob
|
|
};
|
|
|
|
struct DeckGroupLayout {
|
|
int id = 0;
|
|
Rect box; // the fenced group box
|
|
Rect caption; // caption text rect (left part of the caption row)
|
|
DeckToggleLayout captionToggle; // id -1 when absent (rects empty)
|
|
std::vector<DeckCellLayout> cells;
|
|
DeckToggleLayout rowToggle; // id -1 when absent
|
|
};
|
|
|
|
struct DeckLayout {
|
|
std::vector<DeckGroupLayout> groups;
|
|
int rowCount = 0;
|
|
int height = 0; // rowCount * kDeckGroupH + (rowCount-1) * kDeckRowGap; 0 for no groups
|
|
};
|
|
|
|
// The width of one group box: the wider of its caption row (caption + gap + toggle) and its
|
|
// knob row (cells + gap + row toggle), plus the horizontal padding. Pure.
|
|
int deckGroupWidth(const DeckGroupDesc& g);
|
|
|
|
// The number of deck rows the groups occupy at `availWidth` under the greedy whole-group
|
|
// wrap (a group that does not fit the remaining row width starts a new row; the first group
|
|
// of a row always places). 0 for an empty group list. Pure — the wrap is deterministic.
|
|
int deckRowCount(const std::vector<DeckGroupDesc>& groups, int availWidth);
|
|
|
|
// The total deck height at `availWidth` (rows * kDeckGroupH + inter-row gaps). 0 for an
|
|
// empty list. The shell bottom-anchors a band of exactly this height. Pure.
|
|
int deckHeight(const std::vector<DeckGroupDesc>& groups, int availWidth);
|
|
|
|
// Lay the groups out from (left, top) within `availWidth`, wrapping per deckRowCount's rule.
|
|
// Every rect is absolute. Pure — same inputs, same layout.
|
|
DeckLayout layoutDeck(const std::vector<DeckGroupDesc>& groups, int left, int top,
|
|
int availWidth);
|
|
|
|
// --- Hit-test --------------------------------------------------------------------------
|
|
|
|
enum class DeckHitKind { None, Knob, CaptionToggle, RowToggle };
|
|
|
|
struct DeckHit {
|
|
DeckHitKind kind = DeckHitKind::None;
|
|
int id = -1; // the control id of the hit element (cell id / toggle id)
|
|
int segment = -1; // 0/1 for a toggle hit; -1 otherwise
|
|
};
|
|
|
|
// The deck element a point lands on: a knob CELL (the whole 48x58 cell — friendlier than the
|
|
// bare knob circle; the shell anchors the vertical drag wherever the grab lands), a caption-
|
|
// toggle segment, or a row-toggle segment. Blank cells (id -1) and everything else miss.
|
|
// Pure — the shell's routing entry point.
|
|
DeckHit hitTestDeck(const DeckLayout& layout, int x, int y);
|
|
|
|
} // namespace reasampler::vst
|