fix(instrument-ui): distinguish Disabled from Off, add hover cue to mode toggles, close review minors

Adds a hairline outline for Disabled chrome buttons, resolves Hover on the four
mode-selector single-buttons, re-measures the control-surface doc's 1.2 layout
table post-reflow, and adds a structural test pinning every enable/mode toggle
to a derived segment.
This commit is contained in:
2026-08-03 13:43:38 -04:00
parent 450559f155
commit 73c69f689c
8 changed files with 108 additions and 33 deletions
+22 -8
View File
@@ -135,10 +135,13 @@ void ReaSamplerEditor::paintDeck(LICE_IBitmap* bmp, const FaceLayout& fl) {
disabled ? Role::TextDim
: (seg1Active ? Role::BgBase : Role::TextPrimary));
};
// The single-button forms. ENABLE names what it controls and reads Primary on / dim gray
// off; MODE reads the current mode and has no off state. A control the MODE refuses draws
// Disabled, which is a third state and not a synonym for off: off is live and clickable,
// and the washed Disabled surface is what separates them.
// The single-button ENABLE form. Reads Primary on / dim gray off; a control the ENABLE
// gates draws Disabled, a third state and not a synonym for off (off is live and
// clickable). No deck ENABLE currently drives `disabled` true through this path — the
// deck's one Disabled control (FILTER's Band|Notch law when the filter itself is off)
// stays segmented and draws through `drawToggle` above — but the parameter stays for
// parity with that segmented form and because a future ENABLE could plausibly gate on
// something else the way Loop (chrome) already does outside this deck.
const auto drawButtonToggle = [&](const DeckToggleLayout& t, const char* label, bool active,
bool disabled) {
const bool hov = !disabled && isHovered(HoverKind::kControl, t.id);
@@ -151,9 +154,19 @@ void ReaSamplerEditor::paintDeck(LICE_IBitmap* bmp, const FaceLayout& fl) {
active && !disabled ? Role::BgBase
: (hov ? Role::TextPrimary : Role::TextDim));
};
// The single-button MODE form: the label reads the current mode, so there is no off state
// and no dim-gray rest — it is always "on". Filled by accent/primary rather than routed
// through drawButtonToggle (which would force InteractionState::Active regardless of hov,
// leaving MODE with no hover cue at all): Hover mixes toward accent/hot FROM whatever base
// roleColorState is handed, so passing accent/primary here is what lets the already-lit
// button visibly brighten on hover instead of a hover mix nobody would notice against
// bg/cell's dark base.
const auto drawModeToggle = [&](const DeckToggleLayout& t, EnvMode mode) {
drawButtonToggle(t, mode == EnvMode::Spline ? "Spline" : "Stage", /*active=*/true,
/*disabled=*/false);
const char* label = mode == EnvMode::Spline ? "Spline" : "Stage";
const bool hov = isHovered(HoverKind::kControl, t.id);
const InteractionState st = hov ? InteractionState::Hover : InteractionState::Active;
fillSurface(bmp, toKitBox(t.seg0), Role::AccentPrimary, st);
kitTextCentered(bmp, t.seg0, label, Font::Micro, Role::BgBase);
};
const bool anySpline = splineActive(play);
@@ -224,8 +237,9 @@ void ReaSamplerEditor::paintDeck(LICE_IBitmap* bmp, const FaceLayout& fl) {
}
kitText(bmp, g.caption, caption, Font::Micro, Role::TextDim);
// The gain-reduction lamp. ROUND, where the overlay radios in this same slot are
// square, so it reads as a lamp rather than a control.
// The gain-reduction lamp — MASTER's own corner slot, the one the env decks' overlay
// radios used to share before focus-by-click replaced them. ROUND, not square, so it
// reads as a passive readout rather than a control.
if (g.captionRadio.id >= 0 && g.captionRadio.passive) {
const Rect& rb = g.captionRadio.box;
const float r = rb.width / 2.0f - 0.5f;