Ω-W1-T5: one overlay↔waveform mapping, and loop marks that tell the truth about their mode
This commit is contained in:
@@ -150,24 +150,24 @@ instrument::ui::WaveMarks ReaSamplerEditor::waveMarksFor(const SetupMarkers& m)
|
||||
w.frame[static_cast<int>(WaveMark::kLoopEnd)] = m.loopEnd;
|
||||
// The crossfade grows LEFT from the seam it closes, which is where it is audible.
|
||||
w.frame[static_cast<int>(WaveMark::kCrossfade)] = m.loopEnd - m.crossfade;
|
||||
// Trigger has no loop at all, so the pair and the fade are ABSENT rather than shown in an
|
||||
// off state — a mark whose gesture the mode does not offer was read as broken, not as off.
|
||||
// Gate keeps the pair whatever the enable says: that is the drag-to-set-loop affordance.
|
||||
// The crossfade mark belongs to an ACTIVE loop: with the enable off there is no seam for it
|
||||
// to sit on, and no length to drag.
|
||||
const bool gate = loopControlsLive();
|
||||
w.present[static_cast<int>(WaveMark::kStart)] = true;
|
||||
w.present[static_cast<int>(WaveMark::kLoopStart)] = true;
|
||||
w.present[static_cast<int>(WaveMark::kLoopEnd)] = true;
|
||||
w.present[static_cast<int>(WaveMark::kCrossfade)] = m.hasLoop;
|
||||
w.present[static_cast<int>(WaveMark::kLoopStart)] = gate;
|
||||
w.present[static_cast<int>(WaveMark::kLoopEnd)] = gate;
|
||||
w.present[static_cast<int>(WaveMark::kCrossfade)] = gate && m.hasLoop;
|
||||
return w;
|
||||
}
|
||||
|
||||
instrument::ui::WaveMarks ReaSamplerEditor::grabbableMarks(const SetupMarkers& m) const {
|
||||
using instrument::ui::WaveMark;
|
||||
instrument::ui::WaveMarks w = waveMarksFor(m);
|
||||
if (!loopControlsLive()) {
|
||||
w.present[static_cast<int>(WaveMark::kLoopStart)] = false;
|
||||
w.present[static_cast<int>(WaveMark::kLoopEnd)] = false;
|
||||
w.present[static_cast<int>(WaveMark::kCrossfade)] = false;
|
||||
}
|
||||
return w;
|
||||
// Drawn IFF grabbable. Kept as its own fold because paint and hit-test stay separate
|
||||
// questions, but do NOT re-add a suppression here: the Gate-with-loop-off marks are drawn
|
||||
// grey precisely so they can still be dragged, and dragging one is what turns the enable on.
|
||||
return waveMarksFor(m);
|
||||
}
|
||||
|
||||
void ReaSamplerEditor::setLoopEnabled(bool on) {
|
||||
|
||||
@@ -44,13 +44,16 @@ namespace {
|
||||
// cap, never a curve.
|
||||
constexpr Role kRoleStartMarker = Role::OverlayTrace;
|
||||
constexpr Role kRoleLoopMarker = Role::AccentSecondary;
|
||||
// A loop mark whose enable is off keeps its position and its cap — and its full weight, since
|
||||
// it is still draggable. It changes HUE, not opacity: the dim teal it replaces read as broken
|
||||
// rather than as off. Grey against the lime is deliberately under the 3:1 state-indicator floor
|
||||
// the two-neighbour rule (core/ui/CLAUDE.md) sets for a LIVE mark — an inactive control is
|
||||
// exempt, and that lower contrast is the off cue.
|
||||
constexpr Role kRoleLoopMarkerOff = Role::TextDim;
|
||||
|
||||
// Mark weights. A Disabled mark (loop off, or Trigger) keeps its position and its cap so the
|
||||
// information survives the state; the crossfade is a SOFT boundary and rides below the loop
|
||||
// pair's weight at rest.
|
||||
// Mark weights. The crossfade is a SOFT boundary and rides below the loop pair's weight at rest.
|
||||
constexpr float kMarkAlpha = 1.0f;
|
||||
constexpr float kMarkAlphaXfade = 0.7f;
|
||||
constexpr float kMarkAlphaDisabled = 0.4f;
|
||||
|
||||
// The dashed crossfade line: a 3 px stroke every 6 px down the band.
|
||||
constexpr int kDashOn = 3;
|
||||
@@ -217,9 +220,11 @@ void ReaSamplerEditor::paintWaveform(LICE_IBitmap* bmp, const Rect& band,
|
||||
// stereo view reads one loop region rather than two.
|
||||
const OverlayArea& overlay = surface.overlay;
|
||||
const Rect& overlayRect = overlay.rect;
|
||||
const bool loopLive = loopControlsLive();
|
||||
const bool loopOn = m.hasLoop && loopLive;
|
||||
const WaveMarks marks = waveMarksFor(m);
|
||||
// The mark set is the single source for whether this face has a loop to say anything about
|
||||
// — in Trigger the pair is absent, so the span, the wedges and the caption go with it.
|
||||
const bool loopShown = marks.present[static_cast<int>(WaveMark::kLoopStart)];
|
||||
const bool loopOn = m.hasLoop && loopShown;
|
||||
const LICE_pixel loopInk = toLice(roleColor(kRoleLoopMarker));
|
||||
|
||||
const int lx = frameToX(overlay, frames, m.loopStart);
|
||||
@@ -249,10 +254,10 @@ void ReaSamplerEditor::paintWaveform(LICE_IBitmap* bmp, const Rect& band,
|
||||
}
|
||||
|
||||
// The state caption, centred in the span: the two OFF states say different things because
|
||||
// they mean different things, and Trigger's refusal names its own reason.
|
||||
const char* caption = nullptr;
|
||||
if (!loopLive) caption = "LOOP \xe2\x80\x94 GATE ONLY"; // "LOOP — GATE ONLY" (em dash, UTF-8)
|
||||
else if (!m.hasLoop) caption = m.parked ? "DRAG TO SET LOOP" : "LOOP OFF";
|
||||
// they mean different things, and the grey marks alone cannot say WHICH off state this is
|
||||
// or that dragging will fix it. Trigger has no span to centre anything in.
|
||||
const char* caption =
|
||||
(loopShown && !m.hasLoop) ? (m.parked ? "DRAG TO SET LOOP" : "LOOP OFF") : nullptr;
|
||||
if (caption != nullptr && rx > lx) {
|
||||
// Tight box (kMarkLabelHeight, not the whole overlay) centered on the same midline the
|
||||
// full-height rect already centered DT_VCENTER text on, so the scrim darkens only the
|
||||
@@ -280,16 +285,16 @@ void ReaSamplerEditor::paintWaveform(LICE_IBitmap* bmp, const Rect& band,
|
||||
Role::TextDim);
|
||||
}
|
||||
|
||||
// Line + shaped cap per mark, one grammar. A mark whose gesture is refused draws Disabled
|
||||
// rather than hidden — the position is information the user put there.
|
||||
// Line + shaped cap per mark, one grammar. A loop mark with the enable off draws grey but
|
||||
// full weight — the position is information the user put there, and it is still draggable.
|
||||
for (int i = 0; i < kWaveMarkCount; ++i) {
|
||||
if (!marks.present[i]) continue;
|
||||
const WaveMark which = static_cast<WaveMark>(i);
|
||||
const bool isStart = (which == WaveMark::kStart);
|
||||
const bool dim = !isStart && !loopOn;
|
||||
const LICE_pixel ink = isStart ? toLice(roleColor(kRoleStartMarker)) : loopInk;
|
||||
const float alpha = dim ? kMarkAlphaDisabled
|
||||
: (which == WaveMark::kCrossfade ? kMarkAlphaXfade : kMarkAlpha);
|
||||
const bool off = !isStart && !loopOn;
|
||||
const LICE_pixel ink = isStart ? toLice(roleColor(kRoleStartMarker))
|
||||
: (off ? toLice(roleColor(kRoleLoopMarkerOff)) : loopInk);
|
||||
const float alpha = (which == WaveMark::kCrossfade) ? kMarkAlphaXfade : kMarkAlpha;
|
||||
const int mx = frameToX(overlay, frames, marks.frame[i]);
|
||||
if (which == WaveMark::kCrossfade) {
|
||||
// Dashed: a soft boundary, not a hard one.
|
||||
|
||||
Reference in New Issue
Block a user