// knob_deck.h — knob-deck layout + hit-test for the Sample-face knob deck. Engine-free // like param_slider: cells and toggles carry opaque shell-owned control ids. Mirror of // action_bar/param_slider; the knob primitive itself (value<->needle-angle, drag) is // param_slider's — a knob cell here is just a rect the shell composes it into. // // The deck is a horizontal run of fenced groups, left->right, each a bordered box with a // caption row (caption left, the group's compact mode toggle right-anchored) over a knob // row of fixed cells (knob centered, label band beneath). A group may also place one // two-segment toggle in the knob row after its cells. Groups that must keep stable // geometry across a mode flip reserve blank cells (id -1) so a mode flip never reflows // neighbouring groups. // // Wrap is deterministic: groups place left-to-right with kDeckGroupGap between; a group // that does not fit the remaining width starts a new row (whole groups only, never // split); the first group of a row always places even if wider than the row. #pragma once #include #include "core/instrument/ui/editor_geometry.h" // Rect, contains — the shared geometry idiom namespace reasampler::instrument::ui { // Fixed deck metrics, 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). `captionWidth` is the px the shell // reserves for the caption text (this module does not measure text). 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 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 cells; DeckToggleLayout rowToggle; // id -1 when absent }; struct DeckLayout { std::vector groups; int rowCount = 0; int height = 0; // rowCount * kDeckGroupH + (rowCount-1) * kDeckRowGap; 0 for no groups }; // Width of one group box: the wider of its caption row (caption + gap + toggle) and its // knob row (cells + gap + row toggle), plus horizontal padding. int deckGroupWidth(const DeckGroupDesc& g); // Number of deck rows the groups occupy at `availWidth` under the greedy whole-group wrap. // 0 for an empty list. int deckRowCount(const std::vector& groups, int availWidth); // Total deck height at `availWidth` (rows * kDeckGroupH + inter-row gaps). The shell // bottom-anchors a band of exactly this height. int deckHeight(const std::vector& groups, int availWidth); // Lays the groups out from (left, top) within `availWidth`, wrapping per deckRowCount's // rule. Every rect is absolute. DeckLayout layoutDeck(const std::vector& 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 cell, not just the 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. DeckHit hitTestDeck(const DeckLayout& layout, int x, int y); } // namespace reasampler::instrument::ui