fix: close five Θ-W6-T1 review minors — headroom figure, knob-face radius, degenerate band clamp

Aligns inKnobFace's hit radius with computeKnob's draw-side min(w,h) rule and adds a non-square-rect test; clamps halfSpan for degenerate waveform bands with a test; fixes stale docs/comments and annotates an uncommitted perf measurement.
This commit is contained in:
2026-08-01 10:01:40 -04:00
parent ca464397b2
commit 47f2a063e7
8 changed files with 57 additions and 10 deletions
+1 -1
View File
@@ -310,7 +310,7 @@ anything for a trigger shape.
- `browser_scroll` — scroll + type-to-filter layered over `capture_browser`: vertical scroll offset, scrollbar thumb, thumb-drag mapping, and name-substring search.
- `param_slider` — parameter control-panel: vertical stack of TOGGLE (two-segment selector) and SLIDER (horizontal track) rows; maps normalized value to/from handle pixel.
- `embed_strip` — compact single-row control layout for embed mode in the track FX chain.
- `knob_deck` — pure knob-deck layout + hit-test (FB1): group-box / caption-row / compact-toggle / knob-cell geometry, deterministic whole-group wrap, `DeckLayout` / `DeckHit`. Mirror of `action_bar`/`param_slider`; no LICE or REAPER types. Carries a SECOND hit-test, `hitTestKnobFace`, resolved against the drawn CIRCLES rather than the cell: a double-click reset is aimed at a dial, so the label band and the cell margins must miss where a drag grab deliberately does not, and only a radial resolve can tell the inner curve dial from the outer ring it sits inside. **The cell/knob/label sizes and `sample_bands`' editor floor move as a pair** — wider cells need a wider floor width or the deck wraps to a fourth row. A group carries TWO caption-toggle slots, laid right-to-left: the second exists because a group whose knob row is wider than its caption row has caption slack a toggle can occupy for free, and the deck has six pixels of headroom on its first row at the editor's floor width — a `rowToggle` would widen the GROUP and wrap the deck to a fourth row, past what the minimum window holds. **A group's cell run is a RESERVED WIDTH, not a fixed cell size**: a `-1` id reserves one cell's width without a cell, and the cells present divide the whole run between them at one uniform integer width (residue in symmetric end margins). That is what lets a mode flip drop controls from a face — Trigger's AMP and FILTER ENV lose their Sustain/Release stages — without either reflowing the deck or leaving dead slots in the box; a face with fewer controls simply gets roomier cells. Do not reintroduce fixed-width cells with blank slots.
- `knob_deck` — pure knob-deck layout + hit-test (FB1): group-box / caption-row / compact-toggle / knob-cell geometry, deterministic whole-group wrap, `DeckLayout` / `DeckHit`. Mirror of `action_bar`/`param_slider`; no LICE or REAPER types. Carries a SECOND hit-test, `hitTestKnobFace`, resolved against the drawn CIRCLES rather than the cell: a double-click reset is aimed at a dial, so the label band and the cell margins must miss where a drag grab deliberately does not, and only a radial resolve can tell the inner curve dial from the outer ring it sits inside. **The cell/knob/label sizes and `sample_bands`' editor floor move as a pair** — wider cells need a wider floor width or the deck wraps to a fourth row. A group carries TWO caption-toggle slots, laid right-to-left: the second exists because a group whose knob row is wider than its caption row has caption slack a toggle can occupy for free, and the deck has fourteen pixels of headroom on its first row at the editor's floor width — a `rowToggle` would widen the GROUP and wrap the deck to a fourth row, past what the minimum window holds. **A group's cell run is a RESERVED WIDTH, not a fixed cell size**: a `-1` id reserves one cell's width without a cell, and the cells present divide the whole run between them at one uniform integer width (residue in symmetric end margins). That is what lets a mode flip drop controls from a face — Trigger's AMP and FILTER ENV lose their Sustain/Release stages — without either reflowing the deck or leaving dead slots in the box; a face with fewer controls simply gets roomier cells. Do not reintroduce fixed-width cells with blank slots.
- `deck_values` — the deck's control-id ↔ parameter-set BINDING and its display units, split
from the editor shell on the same axis `deck_groups` was split from `knob_deck`: `deck_groups`
says which controls exist, this says what each one's value MEANS. Holds `deckParamNorm` /
+2 -2
View File
@@ -4,8 +4,8 @@
// The bank is read over the live-state seam, audio over the file seam; both raw inputs
// cross the bridge/file boundary in the shell, everything after (bank parse via the shared
// bank_book JSON path, sample pick, channel policy, SampleData build) is pure and
// unit-tested here. Links bank_book, wav_codec, and play_params (all pure) — deliberately
// NOT the voice engine: the build's product is plain SampleData.
// unit-tested here. Links bank_book, wav_codec, play_params, and play_seconds (all pure) —
// deliberately NOT the voice engine: the build's product is plain SampleData.
#include <cstdint>
#include <optional>
+3 -1
View File
@@ -193,7 +193,9 @@ DeckHit hitTestDeck(const DeckLayout& layout, int x, int y) {
bool inKnobFace(const Rect& knob, int x, int y) {
const double dx = x - (knob.x + knob.width / 2.0);
const double dy = y - (knob.y + knob.height / 2.0);
const double r = knob.width / 2.0;
// Matches computeKnob's radius rule (param_slider.cpp) so the hit rule never claims more
// circle than is actually drawn when a caller's rect is non-square (e.g. a squashed chrome row).
const double r = (std::min)(knob.width, knob.height) / 2.0;
return dx * dx + dy * dy < r * r;
}
+5 -3
View File
@@ -153,9 +153,11 @@ struct DeckFaceHit {
bool inner = false; // inside the concentric inner disc
};
// A point inside the circle inscribed in `knob`, boundary-EXCLUSIVE. THE target rule for the
// reset gesture wherever a radial knob is drawn — the deck's own faces and the chrome's
// preview-velocity dial both resolve through it, so one gesture cannot grow two target rules.
// A point inside the circle inscribed in `knob` — radius is min(width, height)/2, same rule as
// computeKnob's draw-side circle, so a non-square rect can never claim a hit past the drawn disc
// — boundary-EXCLUSIVE. THE target rule for the reset gesture wherever a radial knob is drawn —
// the deck's own faces and the chrome's preview-velocity dial both resolve through it, so one
// gesture cannot grow two target rules.
bool inKnobFace(const Rect& knob, int x, int y);
// Resolved against the drawn CIRCLES, not the cell: a reset is aimed at a dial, so the label
+7 -1
View File
@@ -102,7 +102,13 @@ WaveformBand waveformBand(int bandTop, int bandHeight) {
b.top = bandTop;
b.height = bandHeight;
b.midY = bandTop + bandHeight / 2;
b.halfSpan = (bandHeight / 2) - 2; // matches drawWaveform's 2px vertical breathing room
// matches drawWaveform's 2px vertical breathing room; clamped at 0 so a degenerate band
// (height <= 3, where this would otherwise go negative) can't flip a positive peak below
// the zero line.
const int rawHalfSpan = (bandHeight / 2) - 2;
b.halfSpan = rawHalfSpan > 0 ? rawHalfSpan : 0;
// lo/hi (waveformColumnSpan) are midY-height/2 .. midY+height/2-1 — asymmetric by one px for
// even heights. Unreachable while |compressAmplitudeForDisplay| <= 1, so left as-is.
return b;
}