Merge Θ-W2-T3: full-width piano strip with uniform key widths, note tooltips, one toolbar font
This commit is contained in:
@@ -218,8 +218,8 @@ slider couldn't. Two pure modules split the forward (draw) and inverse (edit) ma
|
||||
|
||||
- `editor_geometry` (`core/instrument/ui`) — the shared geometry VOCABULARY every instrument UI module speaks: the `core::ui::Rect` alias, `contains()`, and `OverlayArea` (a one-field `Rect` wrapper, no implicit conversion from `Rect`). Header-only (an INTERFACE CMake target), so it carries no layout of its own.
|
||||
- `sample_bands` — **THE band-stack allocator**, and the only module that owns the Sample face's vertical inventory: three bands top-to-bottom (CHROME toolbar+control row / WAVEFORM elastic, floored at two stacked lanes / DECKS bottom-anchored at the knob deck's own wrapped height), plus the waveform band's lane split (`waveformLanes` takes a resolved `LaneSplit`, not a raw bool — only `waveformSurface` folds the source-channel-count decision in). A shared READ-ONLY surface for every band owner — a band's interior module lays out inside the rect it is handed and never re-allocates the stack.
|
||||
- `sample_chrome` — the CHROME band's interior: the toolbar row (title + Browse) over the control row (root strip, preview, velocity knob cell, curve button, channel toggle). The fixed run is right-anchored; the root strip takes the remainder.
|
||||
- `keyboard_strip` — piano-keyboard strip: MIDI-note→key rect mapping, black/white key layout, hit-test, root-marker rect, and the drag-delta note resolver.
|
||||
- `sample_chrome` — the CHROME band's interior: the toolbar row (title + the whole right-anchored control run — preview, velocity knob cell, curve button, channel toggle, Browse) over the strip row, which the piano strip owns outright. The title takes what the run leaves; the strip takes its whole row, inset only by the shared band pad so it lines up with the waveform band beneath.
|
||||
- `keyboard_strip` — piano-keyboard strip: true white/black key geometry (whites tiled at one width, blacks overlaid at one width and height, straddling their boundary), hit-test resolving black-over-white by zone, root-marker rect, the absolute-position drag resolver, and MIDI note naming under the C4 convention. **Same-class keys are one integer width by construction; the residue of an indivisible band width (`w % 75`, up to 74 px) lands in symmetric end margins, never in a key** — uniform widths and gap-free edge-to-edge tiling cannot both hold, and uniformity wins.
|
||||
- `waveform_view` — the WAVEFORM band's interior: `waveformSurface` resolves the drawn lane(s) (two stacked lanes, L over R, only when the mode is stereo AND the source has a second channel — a mono source under stereo mode is dual-mono and draws one lane) plus **the** overlay area, and `laneEnvelope` splits one multi-channel envelope pass per lane. Also maps frame span linearly across a rect; generic named draggable markers with drag-delta resolver, clamp, and zero-crossing snap.
|
||||
- **Overlay contract (consumed by later waveform work).** `WaveformSurface::overlay` — equivalently the standalone `waveformOverlayArea(band)` — is the FULL band in both modes. Everything riding the waveform (the amp-envelope trace and its node handles, the start/loop markers, the loop region) draws ONCE into it, spanning both stacked lanes; hit-testing resolves against the same area so a grab in the lower lane reaches them. Anything drawn or hit-tested per lane is a duplicate and a defect — structurally enforced: `overlay` is the distinct `OverlayArea` type (`editor_geometry`), not `Rect`, so every overlay-consuming API (`frameToX`/`markerAtPoint`/`resolveDragFrame`, `envelope_edit`'s `nodeAtPoint`/`resolveNodeDrag`, `envelope_overlay`'s `buildEnvelopePolyline`) rejects a lane rect at compile time rather than silently accepting one.
|
||||
- `capture_browser` — capture browser: card-grid layout + bank-filter tab strip geometry and hit-test; knows only counts and rects, draws nothing.
|
||||
@@ -242,3 +242,8 @@ slider couldn't. Two pure modules split the forward (draw) and inverse (edit) ma
|
||||
Channel-mode (D-E) bus-renegotiation design and the earlier Preserve-onset-latency
|
||||
framing in the S16 guardrails. Root `CLAUDE.md` is the current source of truth
|
||||
for both — do not reintroduce either superseded design.
|
||||
- **`keyboard_strip`'s width-uniformity guarantee is client-pixel only.** Its test sweep
|
||||
covers client-pixel widths (including multiples standing in for larger client areas);
|
||||
nothing in the instrument implements `IPlugViewContentScaleSupport`, so host-side DPI
|
||||
scaling of the plugin window — which would resample the uniform integer key widths at the
|
||||
physical-pixel level — is unverified.
|
||||
|
||||
@@ -8,18 +8,29 @@ namespace reasampler::instrument::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
// Pitch class of each natural, and the count of naturals strictly below each pitch class.
|
||||
constexpr int kNaturalPitchClass[7] = {0, 2, 4, 5, 7, 9, 11};
|
||||
constexpr int kNaturalsBelowPc[12] = {0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6};
|
||||
|
||||
int clampNote(int n) {
|
||||
if (n < 0) return 0;
|
||||
if (n > kStripKeyCount - 1) return kStripKeyCount - 1;
|
||||
return n;
|
||||
}
|
||||
|
||||
// Maps a key boundary (0..128) to an x pixel. Key N's left is keyEdgeToX(N), right is
|
||||
// keyEdgeToX(N+1) — tiles adjacent keys without a seam. Mirrors embed_strip::keyEdgeToX.
|
||||
int keyEdgeToX(int bandLeft, int bandWidth, int keyEdge) {
|
||||
if (keyEdge <= 0) return bandLeft;
|
||||
if (keyEdge >= kStripKeyCount) return bandLeft + bandWidth;
|
||||
return bandLeft + (keyEdge * bandWidth) / kStripKeyCount;
|
||||
// The MIDI note of white key `index` (0..74).
|
||||
int whiteNoteAt(int index) {
|
||||
const int i = index < 0 ? 0 : (index > kStripWhiteKeyCount - 1 ? kStripWhiteKeyCount - 1
|
||||
: index);
|
||||
return clampNote((i / 7) * 12 + kNaturalPitchClass[i % 7]);
|
||||
}
|
||||
|
||||
// The black key straddling white-key boundary `b`, or -1 where the scale has none (E-F and
|
||||
// B-C are adjacent naturals).
|
||||
int blackNoteAtBoundary(int b) {
|
||||
if (b <= 0 || b >= kStripWhiteKeyCount) return -1;
|
||||
const int below = whiteNoteAt(b) - 1;
|
||||
return (below >= 0 && !isNaturalKey(below)) ? below : -1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -28,42 +39,22 @@ StripLayout layoutStrip(int w, int h) {
|
||||
const int cw = std::max(0, w);
|
||||
const int ch = std::max(0, h);
|
||||
StripLayout out;
|
||||
out.keys = Rect::ltrb(0, 0, cw, ch);
|
||||
out.band = Rect::ltrb(0, 0, cw, ch);
|
||||
if (ch <= 0) return out;
|
||||
|
||||
out.whiteWidth = cw / kStripWhiteKeyCount;
|
||||
if (out.whiteWidth <= 0) return out; // narrower than one pixel per white key
|
||||
|
||||
const int keysW = out.whiteWidth * kStripWhiteKeyCount;
|
||||
const int left = (cw - keysW) / 2; // residue split evenly into the two end margins
|
||||
out.keys = Rect::ltrb(left, 0, left + keysW, ch);
|
||||
out.blackWidth = std::max(1, (out.whiteWidth * 3) / 5);
|
||||
out.blackHeight = std::max(1, (ch * 3) / 5);
|
||||
return out;
|
||||
}
|
||||
|
||||
int keyLeftX(const StripLayout& layout, int note) {
|
||||
const Rect& band = layout.keys;
|
||||
const int bandWidth = std::max(0, band.width);
|
||||
// note is a key (0..127); callers pass note+1 to get its right edge, 128 -> band right.
|
||||
const int edge = note < 0 ? 0 : (note > kStripKeyCount ? kStripKeyCount : note);
|
||||
return keyEdgeToX(band.x, bandWidth, edge);
|
||||
}
|
||||
|
||||
Rect keyRect(const StripLayout& layout, int note) {
|
||||
const int n = clampNote(note);
|
||||
const int leftX = keyLeftX(layout, n);
|
||||
const int rightX = keyLeftX(layout, n + 1);
|
||||
return Rect::ltrb(leftX, layout.keys.y, std::max(leftX, rightX), layout.keys.bottom());
|
||||
}
|
||||
|
||||
Rect rootMarkerRect(const StripLayout& layout, int rootNote) {
|
||||
return keyRect(layout, rootNote);
|
||||
}
|
||||
|
||||
int keyAtPoint(const StripLayout& layout, int x, int y) {
|
||||
const Rect& band = layout.keys;
|
||||
if (!contains(band, x, y)) return -1;
|
||||
const int bandWidth = std::max(0, band.width);
|
||||
if (bandWidth <= 0) return -1;
|
||||
// Inverts keyEdgeToX: the key whose half-open [leftX, rightX) contains x.
|
||||
const int offset = x - band.x;
|
||||
int note = (offset * kStripKeyCount) / bandWidth;
|
||||
return clampNote(note);
|
||||
}
|
||||
|
||||
bool isNaturalKey(int note) {
|
||||
const int n = note < 0 ? 0 : (note > kStripKeyCount - 1 ? kStripKeyCount - 1 : note);
|
||||
const int n = clampNote(note);
|
||||
static constexpr bool kNatural[12] = {
|
||||
true, // 0 C
|
||||
false, // 1 C#
|
||||
@@ -81,20 +72,56 @@ bool isNaturalKey(int note) {
|
||||
return kNatural[n % 12];
|
||||
}
|
||||
|
||||
int resolveDragNote(const StripLayout& layout, int startNote, int dxPixels) {
|
||||
if (dxPixels == 0) return clampNote(startNote);
|
||||
const int bandWidth = std::max(0, layout.keys.width);
|
||||
if (bandWidth <= 0) return clampNote(startNote); // zero-width -> no motion
|
||||
// Same linear mapping as keyAtPoint/keyEdgeToX (exact rational), not a truncated-integer
|
||||
// bandWidth/128 key width — that drifted at the far end of the strip.
|
||||
const int half = bandWidth / 2;
|
||||
int shift;
|
||||
if (dxPixels > 0) {
|
||||
shift = (dxPixels * kStripKeyCount + half) / bandWidth;
|
||||
} else {
|
||||
shift = -(((-dxPixels) * kStripKeyCount + half) / bandWidth);
|
||||
int whiteIndexOf(int note) {
|
||||
const int n = clampNote(note);
|
||||
return (n / 12) * 7 + kNaturalsBelowPc[n % 12];
|
||||
}
|
||||
|
||||
Rect keyRect(const StripLayout& layout, int note) {
|
||||
if (layout.keys.empty()) return Rect{};
|
||||
const int n = clampNote(note);
|
||||
const int wi = whiteIndexOf(n);
|
||||
if (isNaturalKey(n)) {
|
||||
const int left = layout.keys.x + wi * layout.whiteWidth;
|
||||
return Rect::ltrb(left, layout.keys.y, left + layout.whiteWidth, layout.keys.bottom());
|
||||
}
|
||||
return clampNote(startNote + shift);
|
||||
const int centre = layout.keys.x + wi * layout.whiteWidth;
|
||||
const int left = centre - layout.blackWidth / 2;
|
||||
return Rect::ltrb(left, layout.keys.y, left + layout.blackWidth,
|
||||
layout.keys.y + layout.blackHeight);
|
||||
}
|
||||
|
||||
Rect rootMarkerRect(const StripLayout& layout, int rootNote) {
|
||||
return keyRect(layout, rootNote);
|
||||
}
|
||||
|
||||
int keyAtPoint(const StripLayout& layout, int x, int y) {
|
||||
if (!contains(layout.keys, x, y)) return -1;
|
||||
const int offset = x - layout.keys.x;
|
||||
const int wi = std::min(offset / layout.whiteWidth, kStripWhiteKeyCount - 1);
|
||||
if (y < layout.keys.y + layout.blackHeight) {
|
||||
// Only the two boundaries flanking this white key can carry an overlapping black.
|
||||
const int candidates[2] = {wi, wi + 1};
|
||||
for (const int b : candidates) {
|
||||
const int note = blackNoteAtBoundary(b);
|
||||
if (note >= 0 && contains(keyRect(layout, note), x, y)) return note;
|
||||
}
|
||||
}
|
||||
return whiteNoteAt(wi);
|
||||
}
|
||||
|
||||
int resolveDragNote(const StripLayout& layout, int x, int y) {
|
||||
if (layout.keys.empty()) return -1;
|
||||
const int cx = std::clamp(x, layout.keys.x, layout.keys.right() - 1);
|
||||
const int cy = std::clamp(y, layout.keys.y, layout.keys.bottom() - 1);
|
||||
return keyAtPoint(layout, cx, cy);
|
||||
}
|
||||
|
||||
std::string noteName(int note) {
|
||||
static constexpr const char* kNames[12] = {"C", "C#", "D", "D#", "E", "F",
|
||||
"F#", "G", "G#", "A", "A#", "B"};
|
||||
const int n = clampNote(note);
|
||||
return std::string(kNames[n % 12]) + std::to_string(n / 12 - 1);
|
||||
}
|
||||
|
||||
} // namespace reasampler::instrument::ui
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// keyboard_strip.h — layout + hit-test + drag math for the editor's keyboard strip.
|
||||
// Mirror of embed_strip/mode_switch; the shell draws and marshals mouse events into these
|
||||
// functions.
|
||||
// keyboard_strip.h — piano-keyboard geometry for the editor's root strip: per-class key
|
||||
// rects, hit-test, the root marker, and the note name a hovered key reports.
|
||||
//
|
||||
// The strip maps the full 128-key MIDI span across a horizontal band (the same idiom
|
||||
// embed_strip uses). The loaded capture responds across that whole span, so the strip's
|
||||
// job is the root marker: click a key, or drag the marker, to set the root.
|
||||
// See src/core/instrument/CLAUDE.md for the uniform-width-vs-edge-to-edge-tiling tradeoff.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core/instrument/ui/editor_geometry.h" // Rect, contains
|
||||
|
||||
namespace reasampler::instrument::ui {
|
||||
@@ -16,35 +15,48 @@ namespace reasampler::instrument::ui {
|
||||
// stay independent.
|
||||
inline constexpr int kStripKeyCount = 128;
|
||||
|
||||
// The keys band takes the whole strip area today; clamped so a degenerate size never
|
||||
// yields an inverted rect.
|
||||
// Naturals in MIDI 0..127 (C-1 .. G9): ten full octaves of seven, plus C D E F G.
|
||||
inline constexpr int kStripWhiteKeyCount = 75;
|
||||
|
||||
struct StripLayout {
|
||||
Rect keys;
|
||||
Rect band; // the surface handed in, edge to edge (no src/ consumer; kept as the tests'
|
||||
// reference bound instead of recomputing Rect::ltrb(0, 0, w, h) at each call site)
|
||||
Rect keys; // the tiled key area, kStripWhiteKeyCount * whiteWidth, centred in band
|
||||
int whiteWidth = 0;
|
||||
int blackWidth = 0;
|
||||
int blackHeight = 0; // black keys are short; below them the white key answers
|
||||
};
|
||||
|
||||
// Divide a (w x h) strip area into its regions. Pure.
|
||||
// Divide a (w x h) strip area into its key geometry. Pure. A band too narrow for one pixel
|
||||
// per white key yields empty `keys` — nothing draws and nothing hit-tests.
|
||||
StripLayout layoutStrip(int w, int h);
|
||||
|
||||
// x pixel of the LEFT edge of key `note` (0..127) under the linear 128-key map; key N
|
||||
// occupies [keyLeftX(N), keyLeftX(N+1)). note==128 maps to the band's right edge.
|
||||
int keyLeftX(const StripLayout& layout, int note);
|
||||
// True when `note` (clamped to [0,127]) is a natural (white) key in 12-tone equal
|
||||
// temperament; false for an accidental (black) key.
|
||||
bool isNaturalKey(int note);
|
||||
|
||||
// Half-open rect of a single key `note`, clamped to [0,127].
|
||||
// Naturals strictly below `note`. For a white key that is its own ordinal; for a black key
|
||||
// it is the white-key boundary the accidental straddles.
|
||||
int whiteIndexOf(int note);
|
||||
|
||||
// Rect of a single key, clamped to [0,127]. Whites are full band height and whiteWidth
|
||||
// wide; blacks are blackHeight tall and blackWidth wide, centred on their boundary.
|
||||
Rect keyRect(const StripLayout& layout, int note);
|
||||
|
||||
// Root-marker rect; equivalent to keyRect(layout, rootNote) but named so the intent reads
|
||||
// at the call site.
|
||||
Rect rootMarkerRect(const StripLayout& layout, int rootNote);
|
||||
|
||||
// MIDI note a point (x, y) lands on, or -1 outside the keys band.
|
||||
// MIDI note a point (x, y) lands on, or -1 outside the tiled keys. A black key wins inside
|
||||
// its own short zone; anywhere else the white key beneath answers.
|
||||
int keyAtPoint(const StripLayout& layout, int x, int y);
|
||||
|
||||
// Resolves a drag to a new MIDI note: `startNote` shifted by round(dxPixels / keyWidth),
|
||||
// clamped to [0,127]. The one arithmetic behind the root-marker drag.
|
||||
int resolveDragNote(const StripLayout& layout, int startNote, int dxPixels);
|
||||
// The note a live drag resolves to: keyAtPoint with the point clamped into the key area, so
|
||||
// a drag that wanders off the strip keeps tracking rather than dropping the edit. -1 only
|
||||
// when there is no key area at all.
|
||||
int resolveDragNote(const StripLayout& layout, int x, int y);
|
||||
|
||||
// True when `note` (clamped to [0,127]) is a natural (white) key in 12-tone equal
|
||||
// temperament; false for an accidental (black) key.
|
||||
bool isNaturalKey(int note);
|
||||
// DAW convention (the one REAPER uses): MIDI 60 is C4, so MIDI 0 is C-1.
|
||||
std::string noteName(int note);
|
||||
|
||||
} // namespace reasampler::instrument::ui
|
||||
|
||||
@@ -4,20 +4,26 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "core/instrument/ui/sample_bands.h" // kPad / kTitleHeight / kChromeRowHeight
|
||||
#include "core/instrument/ui/sample_bands.h" // kPad
|
||||
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kStripBandHeight = 40; // the root/piano strip's own height inside the row
|
||||
// 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 = 44;
|
||||
constexpr int kStripBandHeight = 30;
|
||||
|
||||
// The control row's fixed right-anchored run, right to left.
|
||||
constexpr int kRunGap = 6; // between adjacent items of the toolbar run
|
||||
constexpr int kChanSegW = 52;
|
||||
constexpr int kChanSegH = 18;
|
||||
constexpr int kCurveBtnSize = 28;
|
||||
constexpr int kVelCellW = 48;
|
||||
constexpr int kCurveBtnSize = 24;
|
||||
constexpr int kVelCellW = 44;
|
||||
constexpr int kVelLabelH = 12;
|
||||
constexpr int kPreviewBtnW = 64;
|
||||
constexpr int kRunButtonH = 24; // Browse and Preview
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -25,46 +31,61 @@ ChromeRects chromeRects(const Rect& chrome, int knobSize) {
|
||||
ChromeRects r;
|
||||
if (chrome.empty()) return r;
|
||||
|
||||
const int titleH = std::min(kTitleHeight, chrome.height);
|
||||
r.toolbar = Rect::ltrb(chrome.x, chrome.y, chrome.right(), chrome.y + titleH);
|
||||
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 int navTop = r.toolbar.y + 2;
|
||||
const int navBot = std::max(navTop, r.toolbar.bottom() - 2);
|
||||
r.navBrowse = Rect::ltrb(std::max(chrome.x, chrome.right() - kPad - kNavButtonWidth),
|
||||
navTop, std::max(chrome.x, chrome.right() - kPad), navBot);
|
||||
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); };
|
||||
|
||||
if (r.controls.empty()) return r;
|
||||
const Rect& row = r.controls;
|
||||
// The fixed run, right to left: Browse, Mono|Stereo, curve, velocity cell, preview.
|
||||
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);
|
||||
|
||||
// Vertically centre the two heights the row uses: the tall strip band (which the preview
|
||||
// button and velocity cell align to) and the smaller square/segment controls.
|
||||
const int stripTop = row.y + (row.height - kStripBandHeight) / 2;
|
||||
const int stripBot = stripTop + kStripBandHeight;
|
||||
|
||||
const int chanTop = row.y + (row.height - kChanSegH) / 2;
|
||||
const int chanRight = row.right() - kPad;
|
||||
r.chanStereo = Rect::ltrb(chanRight - kChanSegW, chanTop, chanRight, chanTop + kChanSegH);
|
||||
r.chanMono = Rect::ltrb(r.chanStereo.x - kChanSegW, chanTop, r.chanStereo.x,
|
||||
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 curveTop = row.y + (row.height - kCurveBtnSize) / 2;
|
||||
r.curveBtn = Rect::ltrb(r.chanMono.x - kPad - kCurveBtnSize, curveTop,
|
||||
r.chanMono.x - kPad, curveTop + kCurveBtnSize);
|
||||
const int curveTop = topFor(kCurveBtnSize);
|
||||
const int curveRight = leftOf(r.chanMono.x, kRunGap);
|
||||
r.curveBtn = Rect::ltrb(leftOf(curveRight, kCurveBtnSize), curveTop, curveRight,
|
||||
curveTop + kCurveBtnSize);
|
||||
|
||||
r.velCell = Rect::ltrb(r.curveBtn.x - kPad - kVelCellW, stripTop,
|
||||
r.curveBtn.x - kPad, stripBot);
|
||||
const int knobLeft = r.velCell.x + (kVelCellW - knobSize) / 2;
|
||||
const int cellH = std::min(row.height, knobSize + kVelLabelH);
|
||||
const int cellTop = topFor(cellH);
|
||||
const int cellRight = leftOf(r.curveBtn.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 + knobSize);
|
||||
r.velCell.y + std::min(knobSize, cellH));
|
||||
r.velLabel = Rect::ltrb(r.velCell.x, r.velKnob.bottom(), r.velCell.right(),
|
||||
r.velCell.bottom());
|
||||
|
||||
r.preview = Rect::ltrb(r.velCell.x - kPad - kPreviewBtnW, stripTop,
|
||||
r.velCell.x - kPad, stripBot);
|
||||
// Remainder width; clamped so a narrow window collapses the strip rather than inverting it.
|
||||
r.rootStrip = Rect::ltrb(row.x + kPad, stripTop,
|
||||
std::max(row.x + kPad, r.preview.x - kPad), stripBot);
|
||||
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));
|
||||
|
||||
// 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.preview.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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
// sample_chrome.h — interior geometry of the Sample face's CHROME band: the toolbar row
|
||||
// (title + Browse) over the control row (root/piano strip, preview trigger, preview-velocity
|
||||
// knob cell, curve-preview button, Mono|Stereo toggle). Reads the band rect the allocator
|
||||
// hands it (sample_bands) and never allocates vertical space of its own.
|
||||
// (title + the whole right-anchored control run + Browse) over the strip row, which the
|
||||
// piano strip has to itself. Reads the band rect the allocator hands it (sample_bands) and
|
||||
// never allocates vertical space of its own.
|
||||
|
||||
#include "core/instrument/ui/editor_geometry.h" // Rect
|
||||
|
||||
@@ -11,21 +11,22 @@ namespace reasampler::instrument::ui {
|
||||
inline constexpr int kNavButtonWidth = 62; // the Browse toolbar button
|
||||
|
||||
// Every interactive rect inside the chrome band, in one pass so draw and hit-test cannot
|
||||
// derive them differently. The control row's right-anchored run is fixed-width (preview,
|
||||
// velocity cell, curve button, channel toggle) and the root strip takes the remainder, so
|
||||
// the strip grows with the window.
|
||||
// derive them differently. The toolbar's fixed run is right-anchored and the title takes
|
||||
// what is left of that row; the strip row carries nothing but the strip, so the strip grows
|
||||
// with the window in both directions.
|
||||
struct ChromeRects {
|
||||
Rect toolbar; // full-width top row
|
||||
Rect navBrowse; // right-anchored in the toolbar
|
||||
Rect controls; // full-width second row
|
||||
Rect rootStrip; // remainder-width, left
|
||||
Rect preview;
|
||||
Rect title; // the title text slot: the toolbar left of the control run
|
||||
Rect preview; // ---- the right-anchored run, left to right ----
|
||||
Rect velCell; // preview-velocity knob cell (knob + label band)
|
||||
Rect velKnob;
|
||||
Rect velLabel;
|
||||
Rect curveBtn; // opens the velocity-curve popup
|
||||
Rect chanMono;
|
||||
Rect chanStereo;
|
||||
Rect navBrowse;
|
||||
Rect controls; // full-width second row
|
||||
Rect rootStrip; // the piano strip: the whole row, inset only by the shared band pad
|
||||
};
|
||||
|
||||
// `knobSize` is the deck knob square, passed in so this module does not depend on knob_deck.
|
||||
|
||||
Reference in New Issue
Block a user