diff --git a/docs/product/instrument-control-surface.md b/docs/product/instrument-control-surface.md index 449d270..ff25e97 100644 --- a/docs/product/instrument-control-surface.md +++ b/docs/product/instrument-control-surface.md @@ -97,8 +97,8 @@ contours are row 2's first two groups. > **Updated for Ω-W1-T4's filter-mod move and toggle conversion.** The table below was > re-measured against the shipped `deck_groups.cpp`/`knob_deck.cpp` after two changes this > track made: the filter's mod-depth cell moved from FILTER to FILTER ENV (one cell each -> way), and the four toggles that used to be two-segment (`Off\|On`, `Staged\|Spline` on -> PITCH ENV/FILTER ENV/AMP ENVELOPE, `Off\|On` on FILTER) are now the single-button +> way), and the six toggles that used to be two-segment (`Off\|On` on PITCH ENV/FILTER/MASTER, +> `Staged\|Spline` on PITCH ENV/FILTER ENV/AMP ENVELOPE) are now the single-button > `kEnable`/`kMode` styles — see `knob_deck.h`'s `DeckToggleStyle`. The per-deck corner > radio these three envelope decks carried is gone (superseded by the click-to-focus > overlay border, `core/instrument/CLAUDE.md`'s envelope-overlay section); MASTER alone @@ -123,7 +123,7 @@ draws the whole span as one button. Metrics: `kDeckCellW 60`, `kDeckCellH 74`, ` | **PITCH ENV** | 2 | 58 + 4 + 52 + 4 + 46 = 164 | 4 × 60 = **240** | **252** | 0 | 4 cells (A, H, D, Depth); caption toggle `Envelope` (52, single-button ENABLE — was `Off\|On`); caption toggle 2 reads `Stage`/`Spline` (46, single-button MODE — was `Staged\|Spline`); **no corner radio** | | **FILTER ENV** | 2 | 66 + 4 + 46 = 116 | 6 × 60 = **360** | **372** | +60 vs Γ | 6 slots (Gate: A,H,D,S,R,**Mod** / Trigger: A,H,D + 2 reserves,**Mod**) — **mod amt arrives from FILTER**; caption toggle 2 reads `Stage`/`Spline` (46, single-button MODE); **no corner radio**; no enable toggle of its own (FILTER's governs both) | | **AMP ENVELOPE** | 2 | 78 + 4 + 88 + 4 + 46 = 220 | 5 × 60 = **300** | **312** | 0 | 5 slots (Gate: A,H,D,S,R / Trigger: Len,A,H,D + 1 reserve); caption toggle `Gate\|Trig` (88, segmented); caption toggle 2 reads `Stage`/`Spline` (46, single-button MODE — was `Staged\|Spline`); **no corner radio** | -| **MASTER** | **1+2** | 46 + 4 + 2·32 + 4 + 12 = **130** | 60 + 8 + 62 = **130** | **142** | +70 | 1 cell (gain, upper-left); 1 **reserved** lower-left slot; caption toggle `Limiter Off\|On` (32); corner **bubble** (12, passive); **meter column 62 px, full double height** | +| **MASTER** | **1+2** | 46 + 4 + 52 + 4 + 12 = **118** | 60 + 8 + 62 = **130** | **142** | +70 | 1 cell (gain, upper-left); 1 **reserved** lower-left slot; caption toggle `Limiter` (52, single-button ENABLE — was `Off\|On`); corner **bubble** (12, passive); **meter column 62 px, full double height** | **Row totals.** diff --git a/src/shell/instrument/editor_input.cpp b/src/shell/instrument/editor_input.cpp index 75e3475..94d7368 100644 --- a/src/shell/instrument/editor_input.cpp +++ b/src/shell/instrument/editor_input.cpp @@ -37,7 +37,9 @@ void ReaSamplerEditor::onMouseDown(int x, int y) { // The overlay focus follows the deck (mouseDownDeck sets it), and clicking a control // surface OUTSIDE the envelope decks clears it. The waveform band is deliberately not one // of those surfaces: it IS the overlay, so editing what you selected must not deselect it. - if (drag_ == DragKind::kNone && contains(fl.bands.chrome, x, y) && + // Chrome's own dead space (the title band, the gaps between its controls) is not a control + // surface either — hoverChrome answers only for chrome's actual interactive elements. + if (drag_ == DragKind::kNone && hoverChrome(fl, x, y).kind != HoverKind::kNone && overlayEnv_ != OverlayEnv::kNone) { overlayEnv_ = OverlayEnv::kNone; invalidate(); // the chrome branches below repaint only what THEY changed diff --git a/src/shell/instrument/editor_input_deck.cpp b/src/shell/instrument/editor_input_deck.cpp index c20ddc5..cf1f24f 100644 --- a/src/shell/instrument/editor_input_deck.cpp +++ b/src/shell/instrument/editor_input_deck.cpp @@ -32,9 +32,11 @@ bool ReaSamplerEditor::mouseDownDeck(const FaceLayout& fl, int x, int y) { // The deck IS the overlay's click target: anywhere inside an envelope group — panel // background, knob or button — focuses that envelope, and any other group clears it. Set // BEFORE the kind switch and it consumes nothing, so every grab and commit below still - // runs. A drag in flight owns the surface, so it refuses the change. - // Repaints HERE rather than leaning on the branches below: a click on a group's bare - // padding, or on an inert knob, changes the focus and reaches no other invalidate. + // runs. A drag in flight owns the surface, so it refuses the change — always true at this + // call site today (mouse capture makes a real WM_LBUTTONDOWN-while-dragging unreachable; + // WM_CAPTURECHANGED and WM_LBUTTONUP both reset drag_ before another down can land), but + // left explicit rather than assumed so a future capture-handling change fails loud, not + // by silently letting a drag's own surface steal its own focus mid-gesture. if (drag_ == DragKind::kNone) { const OverlayEnv focus = overlayEnvForGroup(hit.group); if (focus != overlayEnv_) { diff --git a/src/shell/instrument/editor_paint_chrome.cpp b/src/shell/instrument/editor_paint_chrome.cpp index aab8688..3ef2df0 100644 --- a/src/shell/instrument/editor_paint_chrome.cpp +++ b/src/shell/instrument/editor_paint_chrome.cpp @@ -230,9 +230,11 @@ void ReaSamplerEditor::paintChrome(LICE_IBitmap* bmp, const FaceLayout& fl, bool // its own mark, not just a slightly-quieter fill, so it draws the one outline this // control ever gets. See PLAN.md's Off-vs-Disabled ruling: Off is live and // clickable, Disabled is not, and the two must not read as the same thing. + // Outlined in text/dim rather than line/hairline: hairline over this fill composites + // to ~1.36:1, under the 3:1 state-indicator floor; text/dim clears it at ~5:1. if (disabled) { LICE_DrawRect(bmp, r.x, r.y, r.width - 1, r.height - 1, - toLice(roleColor(Role::LineHairline)), 1.0f, 0); + toLice(roleColor(Role::TextDim)), 1.0f, 0); } kitTextCentered(bmp, r, label, kToolbarFont, active && !disabled ? Role::BgBase diff --git a/src/shell/instrument/editor_paint_deck.cpp b/src/shell/instrument/editor_paint_deck.cpp index acfdce6..5b3f3bb 100644 --- a/src/shell/instrument/editor_paint_deck.cpp +++ b/src/shell/instrument/editor_paint_deck.cpp @@ -145,11 +145,15 @@ void ReaSamplerEditor::paintDeck(LICE_IBitmap* bmp, const FaceLayout& fl) { const auto drawButtonToggle = [&](const DeckToggleLayout& t, const char* label, bool active, bool disabled) { const bool hov = !disabled && isHovered(HoverKind::kControl, t.id); + // Hover outranks Active here (the reverse of the old order) so an enabled button still + // gets a hover cue — same gap drawModeToggle's comment above explains, just on the + // ENABLE form's own base role: mixing toward accent/hot FROM accent/primary (rather + // than bg/cell) is what makes the already-lit button visibly brighten on hover. const InteractionState st = disabled ? InteractionState::Disabled - : (active ? InteractionState::Active - : (hov ? InteractionState::Hover : InteractionState::Rest)); - fillSurface(bmp, toKitBox(t.seg0), Role::BgCell, st); + : (hov ? InteractionState::Hover + : (active ? InteractionState::Active : InteractionState::Rest)); + fillSurface(bmp, toKitBox(t.seg0), active ? Role::AccentPrimary : Role::BgCell, st); kitTextCentered(bmp, t.seg0, label, Font::Micro, active && !disabled ? Role::BgBase : (hov ? Role::TextPrimary : Role::TextDim));