feat: hide control annotations by default, demote auto-crop notice to a toast

Control events are zero-duration log markers about the run rather than
signals on the air. A busy capture carries thousands, and they clutter the
overlay without saying anything about what was transmitted, so the GUI now
starts with that kind hidden; the per-kind checkbox brings it back. The
headless --render path still enables every kind — an export should render
what was asked for, not a GUI preference.

The auto-crop notice was a full modal: it dimmed the window, took focus,
and demanded a click before the user could look at the file they had just
opened. Auto-crop is a helpful default, not a decision worth blocking on.
It is now a bottom-right toast offering Undo / Dismiss, with a progress
strip showing the remaining time so it doesn't just vanish mid-read, and
it is out of UiModalOpen() so it no longer swallows keys or blocks the
spectrogram underneath.

The countdown only advances while the window is focused, so a crop applied
during a background load is still readable when the user returns. The
per-frame step is clamped: auto-crop fires on the frame right after the
blocking STFT compute, and GetFrameTime() there reports the entire
compute, which drained the whole 5 s budget at once and made the toast
flash by in an instant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8ZWfr5XZyyDttvkhJUgHN
This commit is contained in:
2026-08-12 14:17:05 -07:00
parent 4c3c6a955a
commit 82294844dd
3 changed files with 72 additions and 36 deletions
+56 -35
View File
@@ -826,6 +826,13 @@ void DrawAboutDialog(void)
// [OK] — dismiss; keep the cropped view
// Esc closes (= OK). Clicks outside the panel do nothing (avoids losing the
// crop by missing a button by a few px).
// Auto-crop notice, as a bottom-right toast rather than a modal.
//
// Auto-crop is a helpful default, not a decision worth blocking on: a modal
// stole focus and demanded a click before the user could look at the file they
// had just opened. The toast states what happened, offers Undo, and expires on
// its own — but only counts down while the window is focused, so a crop applied
// during a background load is still there to read when the user comes back.
void DrawAutocropNotice(void)
{
if (!app.autocropNoticeActive) return;
@@ -833,52 +840,52 @@ void DrawAutocropNotice(void)
int sw = GetScreenWidth();
int sh = GetScreenHeight();
DrawRectangle(0, 0, sw, sh, Fade(BLACK, 0.55f));
const char* title = "Auto-crop applied";
float titleW = MeasureTextScaled(title, 13);
float msgW = MeasureTextScaled(app.autocropNoticeMsg, 11);
float btnW = 76 * scale, btnH = 24 * scale;
float pw = 460 * scale;
float ph = 170 * scale;
Rectangle panel = { (sw - pw) * 0.5f, (sh - ph) * 0.5f, pw, ph };
DrawRectangleRec(panel, (Color){ 30, 30, 40, 255 });
DrawRectangleLinesEx(panel, 2, (Color){ 160, 130, 200, 255 });
float pw = fmaxf(fmaxf(titleW, msgW) + 28 * scale, btnW * 2 + 44 * scale);
float ph = 92 * scale;
float margin = 16 * scale;
Rectangle panel = { sw - pw - margin, sh - ph - margin, pw, ph };
DrawTextScaled("Auto-crop applied",
panel.x + 20 * scale, panel.y + 16 * scale, 16,
DrawRectangleRec(panel, (Color){ 28, 28, 36, 240 });
DrawRectangleLinesEx(panel, 1, (Color){ 150, 120, 190, 255 });
DrawTextScaled(title, panel.x + 12 * scale, panel.y + 10 * scale, 13,
(Color){ 200, 170, 240, 255 });
DrawTextScaled(app.autocropNoticeMsg, panel.x + 12 * scale, panel.y + 30 * scale,
11, LIGHTGRAY);
// Body: word-wrap not needed for the short message ApplyAutoCrop builds.
DrawTextScaled(app.autocropNoticeMsg,
panel.x + 20 * scale, panel.y + 50 * scale, 13, LIGHTGRAY);
float btnY = panel.y + ph - btnH - 10 * scale;
Rectangle undoBtn = { panel.x + 12 * scale, btnY, btnW, btnH };
Rectangle okBtn = { panel.x + pw - btnW - 12 * scale, btnY, btnW, btnH };
float btnW = 110 * scale, btnH = 32 * scale;
float btnY = panel.y + ph - btnH - 16 * scale;
Rectangle uncropBtn = { panel.x + 20 * scale, btnY, btnW, btnH };
Rectangle okBtn = { panel.x + pw - btnW - 20*scale, btnY, btnW, btnH };
bool undoHover = CheckCollisionPointRec(GetMousePosition(), undoBtn);
bool okHover = CheckCollisionPointRec(GetMousePosition(), okBtn);
bool uncropHover = CheckCollisionPointRec(GetMousePosition(), uncropBtn);
bool okHover = CheckCollisionPointRec(GetMousePosition(), okBtn);
DrawPanelBox(uncropBtn,
uncropHover ? (Color){ 100, 60, 60, 255 } : (Color){ 70, 40, 40, 255 },
DrawPanelBox(undoBtn,
undoHover ? (Color){ 100, 60, 60, 255 } : (Color){ 70, 40, 40, 255 },
(Color){ 230, 160, 160, 255 });
DrawTextScaled("Uncrop",
uncropBtn.x + btnW * 0.5f - MeasureTextScaled("Uncrop", 14) * 0.5f,
uncropBtn.y + 8 * scale, 14, WHITE);
DrawTextScaled("Undo", undoBtn.x + btnW * 0.5f - MeasureTextScaled("Undo", 12) * 0.5f,
undoBtn.y + 5 * scale, 12, WHITE);
DrawPanelBox(okBtn,
okHover ? (Color){ 60, 90, 60, 255 } : (Color){ 40, 70, 40, 255 },
(Color){ 160, 220, 160, 255 });
DrawTextScaled("OK",
okBtn.x + btnW * 0.5f - MeasureTextScaled("OK", 14) * 0.5f,
okBtn.y + 8 * scale, 14, WHITE);
okHover ? (Color){ 60, 90, 60, 255 } : (Color){ 45, 60, 45, 255 },
(Color){ 160, 200, 160, 255 });
DrawTextScaled("Dismiss", okBtn.x + btnW * 0.5f - MeasureTextScaled("Dismiss", 12) * 0.5f,
okBtn.y + 5 * scale, 12, WHITE);
DrawTextScaled("Esc or Enter = OK", panel.x + pw - 156 * scale,
panel.y + ph - 14 * scale, 10, GRAY);
// Remaining-time strip along the bottom edge, so the auto-expiry is visible
// rather than the toast just vanishing mid-read.
float frac = app.autocropNoticeTimer / AUTOCROP_NOTICE_SECONDS;
if (frac < 0.0f) frac = 0.0f;
if (frac > 1.0f) frac = 1.0f;
DrawRectangle((int)panel.x, (int)(panel.y + ph - 2 * scale),
(int)(pw * frac), (int)(2 * scale), (Color){ 150, 120, 190, 200 });
// Hit handling. Esc/Enter dismiss (keep crop). Uncrop restores full view.
if (IsKeyPressed(KEY_ESCAPE) || IsKeyPressed(KEY_ENTER)) {
app.autocropNoticeActive = false;
}
if (uncropHover && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
if (undoHover && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
app.displayMaxFreqHz = 0.0f;
app.view.start = 0.0f; app.view.end = 1.0f;
app.view.freqStart = 0.0f; app.view.freqEnd = 1.0f;
@@ -887,4 +894,18 @@ void DrawAutocropNotice(void)
} else if (okHover && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
app.autocropNoticeActive = false;
}
// Count down only while focused: a toast that expired behind another window
// would be gone before it was ever seen.
//
// The step is clamped because auto-crop fires on the frame right after the
// blocking STFT compute, and GetFrameTime() on that frame reports the whole
// compute — seconds on a long capture. Unclamped, that single frame drained
// the entire 5 s budget and the toast flashed by in an instant.
if (IsWindowFocused()) {
float dt = GetFrameTime();
if (dt > 0.1f) dt = 0.1f; // ignore load hitches / debugger stalls
app.autocropNoticeTimer -= dt;
if (app.autocropNoticeTimer <= 0.0f) app.autocropNoticeActive = false;
}
}