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:
@@ -147,6 +147,7 @@ static bool IsAppActive(void)
|
||||
if (app.sel.isDragging || app.sel.isTimeSelecting || app.sel.isFreqSelecting) return true;
|
||||
if (app.marker.dragging) return true;
|
||||
if (app.exportMessageTimer > 0.0f) return true; // notification countdown
|
||||
if (app.autocropNoticeActive) return true; // toast countdown + progress strip
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -526,6 +527,7 @@ void ApplyAutoCrop(void)
|
||||
timePart,
|
||||
srcPart);
|
||||
app.autocropNoticeActive = true;
|
||||
app.autocropNoticeTimer = AUTOCROP_NOTICE_SECONDS;
|
||||
TraceLog(LOG_INFO, "Auto-crop: %s", app.autocropNoticeMsg);
|
||||
}
|
||||
|
||||
@@ -968,6 +970,13 @@ int main(int argc, char* argv[])
|
||||
app.hoverStackCount = 0;
|
||||
app.currentCollision = -1;
|
||||
for (int i = 0; i < MLNL_KIND_MAX; i++) app.annotationKindEnabled[i] = true;
|
||||
// Control events are zero-duration log markers about the run, not signals on
|
||||
// the air. On a busy capture there are thousands of them and they clutter
|
||||
// the overlay without saying anything about what was transmitted, so they
|
||||
// start hidden; the per-kind checkbox turns them back on. (Headless
|
||||
// --render keeps every kind enabled — an export should show what was asked
|
||||
// for, not a GUI default.)
|
||||
app.annotationKindEnabled[MLNL_KIND_CONTROL] = false;
|
||||
app.showScope = true;
|
||||
app.dividerY = 0.6f; // Start with 60% spectro, 40% scope
|
||||
app.isDividing = false;
|
||||
|
||||
@@ -56,6 +56,9 @@ typedef struct {
|
||||
// still reports the total collision count, it just stops adding bands.
|
||||
#define MAX_COLLISION_REGIONS 4096
|
||||
|
||||
// How long the auto-crop toast stays up, in seconds of *focused* time.
|
||||
#define AUTOCROP_NOTICE_SECONDS 5.0f
|
||||
|
||||
// How many overlapping annotation boxes the cursor-hit stack retains. Deeper
|
||||
// piles than this are counted but not listed individually (the tooltip says
|
||||
// "+N more"), which keeps a dense pile-up from covering the spectrogram.
|
||||
@@ -306,6 +309,7 @@ typedef struct {
|
||||
// user dismisses with "OK" (keep crop) or "Uncrop" (restore full view).
|
||||
bool autocropNoticeActive;
|
||||
char autocropNoticeMsg[256];
|
||||
float autocropNoticeTimer; // seconds left; counts down only while focused
|
||||
|
||||
// Optional mLnL annotations parsed from the loaded WAV (empty if the file
|
||||
// doesn't carry the chunk). The annotations overlay has two surfaces:
|
||||
@@ -381,7 +385,9 @@ void ApplyAutoCrop(void);
|
||||
// is gated off while this is the case. Add new overlays here in one place.
|
||||
static inline bool UiModalOpen(void)
|
||||
{
|
||||
return app.showFileBrowser || app.showAbout || app.autocropNoticeActive;
|
||||
// The auto-crop notice is a toast, not a modal — it must not swallow keys
|
||||
// or block interaction with the spectrogram underneath it.
|
||||
return app.showFileBrowser || app.showAbout;
|
||||
}
|
||||
|
||||
// Reset the box selection to the full signal (the "no selection" state).
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user