// sample_chrome.cpp — see sample_chrome.h. Pure math; no host types. #include "core/instrument/ui/sample_chrome.h" #include #include "core/instrument/ui/sample_bands.h" // kPad namespace reasampler::instrument::ui { namespace { // The toolbar row carries the whole control run, so it is taller than the Browse modal's // plain kTitleHeight bar — the velocity knob cell (knob over label) sets the floor. Both // rows still fit the band the allocator hands out (kTitleHeight + kChromeRowHeight). constexpr int kToolbarHeight = 58; constexpr int kStripBandHeight = 30; constexpr int kRunGap = 6; // between adjacent items of the toolbar run constexpr int kChanSegW = 52; constexpr int kChanSegH = 18; constexpr int kVelCellW = 56; constexpr int kHoldCellW = 56; // the bake Hold cell, same grammar as the velocity cell constexpr int kVelLabelH = 16; constexpr int kPreviewBtnW = 64; constexpr int kRunButtonH = 24; // Browse and Preview constexpr int kPreviewGlyphMinH = 6; constexpr int kPreviewGlyphMaxH = 14; constexpr int kPreviewGlyphPad = 4; // clearance between the glyph and the button edge } // namespace ChromeRects chromeRects(const Rect& chrome, int knobSize) { ChromeRects r; if (chrome.empty()) return r; const int toolbarH = std::min(kToolbarHeight, chrome.height); r.toolbar = Rect::ltrb(chrome.x, chrome.y, chrome.right(), chrome.y + toolbarH); r.controls = Rect::ltrb(chrome.x, r.toolbar.bottom(), chrome.right(), chrome.bottom()); const Rect& row = r.toolbar; const auto topFor = [&row](int h) { return row.y + (row.height - h) / 2; }; const auto leftOf = [&row](int edge, int w) { return std::max(row.x, edge - w); }; // The fixed run, right to left: Browse, Mono|Stereo, velocity cell, preview, bake, hold. // The velocity-curve button that used to sit here now lives in the deck's VELOCITY group. const int navH = std::min(kRunButtonH, row.height); const int navTop = topFor(navH); const int navRight = std::max(row.x, row.right() - kPad); r.navBrowse = Rect::ltrb(leftOf(navRight, kNavButtonWidth), navTop, navRight, navTop + navH); const int chanTop = topFor(kChanSegH); const int chanRight = leftOf(r.navBrowse.x, kRunGap); r.chanStereo = Rect::ltrb(leftOf(chanRight, kChanSegW), chanTop, chanRight, chanTop + kChanSegH); r.chanMono = Rect::ltrb(leftOf(r.chanStereo.x, kChanSegW), chanTop, r.chanStereo.x, chanTop + kChanSegH); const int cellH = std::min(row.height, knobSize + kVelLabelH); const int cellTop = topFor(cellH); const int cellRight = leftOf(r.chanMono.x, kRunGap); r.velCell = Rect::ltrb(leftOf(cellRight, kVelCellW), cellTop, cellRight, cellTop + cellH); const int knobLeft = r.velCell.x + (r.velCell.width - knobSize) / 2; r.velKnob = Rect::ltrb(knobLeft, r.velCell.y, knobLeft + knobSize, r.velCell.y + std::min(knobSize, cellH)); r.velLabel = Rect::ltrb(r.velCell.x, r.velKnob.bottom(), r.velCell.right(), r.velCell.bottom()); const int prevTop = topFor(std::min(kRunButtonH, row.height)); const int prevRight = leftOf(r.velCell.x, kRunGap); r.preview = Rect::ltrb(leftOf(prevRight, kPreviewBtnW), prevTop, prevRight, prevTop + std::min(kRunButtonH, row.height)); const int bakeRight = leftOf(r.preview.x, kRunGap); r.bake = Rect::ltrb(leftOf(bakeRight, kBakeButtonWidth), prevTop, bakeRight, prevTop + std::min(kRunButtonH, row.height)); // Hold: the same knob-cell grammar as the velocity cell, immediately left of Bake. const int holdRight = leftOf(r.bake.x, kRunGap); r.holdCell = Rect::ltrb(leftOf(holdRight, kHoldCellW), cellTop, holdRight, cellTop + cellH); const int holdKnobLeft = r.holdCell.x + (r.holdCell.width - knobSize) / 2; r.holdKnob = Rect::ltrb(holdKnobLeft, r.holdCell.y, holdKnobLeft + knobSize, r.holdCell.y + std::min(knobSize, cellH)); r.holdLabel = Rect::ltrb(r.holdCell.x, r.holdKnob.bottom(), r.holdCell.right(), r.holdCell.bottom()); // The title takes what the run leaves; clamped so a narrow window collapses it rather // than inverting it. r.title = Rect::ltrb(row.x + kPad, row.y, std::max(row.x + kPad, r.holdCell.x - kRunGap), row.bottom()); if (r.controls.empty()) return r; // The strip row belongs to the strip alone — inset only by the shared band pad, so it // lines up with the waveform band directly beneath it. const int stripH = std::min(kStripBandHeight, r.controls.height); const int stripTop = r.controls.y + (r.controls.height - stripH) / 2; r.rootStrip = Rect::ltrb(r.controls.x + kPad, stripTop, std::max(r.controls.x + kPad, r.controls.right() - kPad), stripTop + stripH); return r; } PreviewGlyph previewGlyph(const Rect& button) { PreviewGlyph g; if (button.empty()) return g; // Even height so the apex lands exactly on the button's horizontal centre line rather // than a half-pixel off it. int h = std::min({kPreviewGlyphMaxH, button.height - 2 * kPreviewGlyphPad, button.width - 2 * kPreviewGlyphPad}); h -= h % 2; if (h < kPreviewGlyphMinH) return g; const int w = std::max(2, (h * 7) / 8); // ~equilateral: the classic transport triangle const int cx = button.x + button.width / 2; const int cy = button.y + button.height / 2; g.leftX = cx - w / 2; g.apexX = g.leftX + w; g.topY = cy - h / 2; g.bottomY = g.topY + h; g.apexY = cy; return g; } } // namespace reasampler::instrument::ui