refactor: modal-gate helper + data-driven colormap table
Two extensibility easy-wins, both behavior-preserving: - UiModalOpen() centralizes the "an overlay owns input" check that was copy-pasted as `!showFileBrowser && !showAbout` across 6 input gates. New overlays now update one place. - Colormaps become a COLORMAPS[] table (name + function) indexed by enum. GetColormapColor and the sidebar both read it, so adding a colormap is one enum value + one Cmap* fn + one row — and the sidebar name can no longer drift from the enum (deleted the parallel colormapNames[] array). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+49
-18
@@ -41,48 +41,79 @@ float MeasureTextScaled(const char* text, float baseSize)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ===== Colormaps =====
|
// ===== Colormaps =====
|
||||||
static Color GetColormapColor(float t, ColormapType type)
|
// Each colormap maps t in [0,1] to a Color. To add a colormap: add an enum
|
||||||
{
|
// value in spectrogram_types.h, write a Cmap* function, and add one row to
|
||||||
t = Clamp(t, 0.0f, 1.0f);
|
// COLORMAPS[] below — the sidebar names and lookups follow automatically.
|
||||||
|
typedef Color (*ColormapFn)(float t);
|
||||||
|
|
||||||
switch (type) {
|
static Color CmapGrays(float t)
|
||||||
case COLORMAP_GRAYS: {
|
{
|
||||||
unsigned char v = (unsigned char)(t * 255);
|
unsigned char v = (unsigned char)(t * 255);
|
||||||
return (Color){ v, v, v, 255 };
|
return (Color){ v, v, v, 255 };
|
||||||
}
|
}
|
||||||
case COLORMAP_INFERNO: {
|
|
||||||
|
static Color CmapInferno(float t)
|
||||||
|
{
|
||||||
float r = 0.0f, g = 0.0f, b = 0.0f;
|
float r = 0.0f, g = 0.0f, b = 0.0f;
|
||||||
if (t < 0.25f) { t = t / 0.25f; r = 0.0f + t * 0.5f; g = 0.0f; b = 0.0f + t * 0.3f; }
|
if (t < 0.25f) { t = t / 0.25f; r = 0.0f + t * 0.5f; g = 0.0f; b = 0.0f + t * 0.3f; }
|
||||||
else if (t < 0.5f) { t = (t - 0.25f) / 0.25f; r = 0.5f + t * 0.5f; g = 0.0f + t * 0.3f; b = 0.3f + t * 0.4f; }
|
else if (t < 0.5f) { t = (t - 0.25f) / 0.25f; r = 0.5f + t * 0.5f; g = 0.0f + t * 0.3f; b = 0.3f + t * 0.4f; }
|
||||||
else if (t < 0.75f) { t = (t - 0.5f) / 0.25f; r = 1.0f; g = 0.3f + t * 0.5f; b = 0.7f + t * 0.2f; }
|
else if (t < 0.75f) { t = (t - 0.5f) / 0.25f; r = 1.0f; g = 0.3f + t * 0.5f; b = 0.7f + t * 0.2f; }
|
||||||
else { t = (t - 0.75f) / 0.25f; r = 1.0f; g = 0.8f + t * 0.2f; b = 0.9f + t * 0.1f; }
|
else { t = (t - 0.75f) / 0.25f; r = 1.0f; g = 0.8f + t * 0.2f; b = 0.9f + t * 0.1f; }
|
||||||
return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 };
|
return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 };
|
||||||
}
|
}
|
||||||
case COLORMAP_VIRIDIS: {
|
|
||||||
|
static Color CmapViridis(float t)
|
||||||
|
{
|
||||||
float r, g, b;
|
float r, g, b;
|
||||||
if (t < 0.25f) { t = t / 0.25f; r = 0.27f + t * 0.13f; g = 0.00f + t * 0.33f; b = 0.33f + t * 0.27f; }
|
if (t < 0.25f) { t = t / 0.25f; r = 0.27f + t * 0.13f; g = 0.00f + t * 0.33f; b = 0.33f + t * 0.27f; }
|
||||||
else if (t < 0.5f) { t = (t - 0.25f) / 0.25f; r = 0.40f + t * 0.16f; g = 0.33f + t * 0.29f; b = 0.60f - t * 0.20f; }
|
else if (t < 0.5f) { t = (t - 0.25f) / 0.25f; r = 0.40f + t * 0.16f; g = 0.33f + t * 0.29f; b = 0.60f - t * 0.20f; }
|
||||||
else if (t < 0.75f) { t = (t - 0.5f) / 0.25f; r = 0.56f + t * 0.24f; g = 0.62f + t * 0.23f; b = 0.40f - t * 0.20f; }
|
else if (t < 0.75f) { t = (t - 0.5f) / 0.25f; r = 0.56f + t * 0.24f; g = 0.62f + t * 0.23f; b = 0.40f - t * 0.20f; }
|
||||||
else { t = (t - 0.75f) / 0.25f; r = 0.80f + t * 0.17f; g = 0.85f + t * 0.12f; b = 0.20f - t * 0.15f; }
|
else { t = (t - 0.75f) / 0.25f; r = 0.80f + t * 0.17f; g = 0.85f + t * 0.12f; b = 0.20f - t * 0.15f; }
|
||||||
return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 };
|
return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 };
|
||||||
}
|
}
|
||||||
case COLORMAP_PLASMA: {
|
|
||||||
|
static Color CmapPlasma(float t)
|
||||||
|
{
|
||||||
float r = 0.05f + t * 0.9f;
|
float r = 0.05f + t * 0.9f;
|
||||||
float g = 0.0f + t * 0.6f + (t > 0.5f ? (t - 0.5f) * 0.4f : 0.0f);
|
float g = 0.0f + t * 0.6f + (t > 0.5f ? (t - 0.5f) * 0.4f : 0.0f);
|
||||||
float b = 0.6f - t * 0.5f;
|
float b = 0.6f - t * 0.5f;
|
||||||
return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 };
|
return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 };
|
||||||
}
|
}
|
||||||
case COLORMAP_HOT: {
|
|
||||||
|
static Color CmapHot(float t)
|
||||||
|
{
|
||||||
float r = Clamp(t * 3.0f, 0.0f, 1.0f);
|
float r = Clamp(t * 3.0f, 0.0f, 1.0f);
|
||||||
float g = Clamp((t - 0.33f) * 3.0f, 0.0f, 1.0f);
|
float g = Clamp((t - 0.33f) * 3.0f, 0.0f, 1.0f);
|
||||||
float b = Clamp((t - 0.66f) * 3.0f, 0.0f, 1.0f);
|
float b = Clamp((t - 0.66f) * 3.0f, 0.0f, 1.0f);
|
||||||
return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 };
|
return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 };
|
||||||
}
|
}
|
||||||
case COLORMAP_COOL: {
|
|
||||||
|
static Color CmapCool(float t)
|
||||||
|
{
|
||||||
return (Color){ (unsigned char)(t * 255), (unsigned char)((1.0f - t) * 255), 255, 255 };
|
return (Color){ (unsigned char)(t * 255), (unsigned char)((1.0f - t) * 255), 255, 255 };
|
||||||
}
|
}
|
||||||
default: return GRAY;
|
|
||||||
}
|
typedef struct { const char* name; ColormapFn fn; } ColormapDef;
|
||||||
|
|
||||||
|
static const ColormapDef COLORMAPS[COLORMAP_COUNT] = {
|
||||||
|
[COLORMAP_GRAYS] = { "Grays", CmapGrays },
|
||||||
|
[COLORMAP_INFERNO] = { "Inferno", CmapInferno },
|
||||||
|
[COLORMAP_VIRIDIS] = { "Viridis", CmapViridis },
|
||||||
|
[COLORMAP_PLASMA] = { "Plasma", CmapPlasma },
|
||||||
|
[COLORMAP_HOT] = { "Hot", CmapHot },
|
||||||
|
[COLORMAP_COOL] = { "Cool", CmapCool },
|
||||||
|
};
|
||||||
|
|
||||||
|
static Color GetColormapColor(float t, ColormapType type)
|
||||||
|
{
|
||||||
|
if (type < 0 || type >= COLORMAP_COUNT) return GRAY;
|
||||||
|
return COLORMAPS[type].fn(Clamp(t, 0.0f, 1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* ColormapName(ColormapType type)
|
||||||
|
{
|
||||||
|
if (type < 0 || type >= COLORMAP_COUNT) return "?";
|
||||||
|
return COLORMAPS[type].name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenerateColormapTexture(void)
|
void GenerateColormapTexture(void)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ float MeasureTextScaled(const char* text, float baseSize);
|
|||||||
|
|
||||||
// --- Colormaps ---
|
// --- Colormaps ---
|
||||||
void GenerateColormapTexture(void);
|
void GenerateColormapTexture(void);
|
||||||
|
const char* ColormapName(ColormapType type);
|
||||||
|
|
||||||
// --- Spectrogram texture ---
|
// --- Spectrogram texture ---
|
||||||
// GenerateSpectrogramTexture recomputes the synchrosqueezed reassignment (use
|
// GenerateSpectrogramTexture recomputes the synchrosqueezed reassignment (use
|
||||||
|
|||||||
+6
-6
@@ -217,7 +217,7 @@ int main(int argc, char* argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// File browser toggle
|
// File browser toggle
|
||||||
if (IsKeyPressed(KEY_O) && !app.showFileBrowser && !app.showAbout) {
|
if (IsKeyPressed(KEY_O) && !UiModalOpen()) {
|
||||||
app.showFileBrowser = true;
|
app.showFileBrowser = true;
|
||||||
ScanDirectory(GetWorkingDirectory());
|
ScanDirectory(GetWorkingDirectory());
|
||||||
}
|
}
|
||||||
@@ -258,7 +258,7 @@ int main(int argc, char* argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// View controls
|
// View controls
|
||||||
if (app.loaded && !app.showFileBrowser && !app.showAbout) {
|
if (app.loaded && !UiModalOpen()) {
|
||||||
// Spectrogram area fills remaining window space (scaled)
|
// Spectrogram area fills remaining window space (scaled)
|
||||||
float viewScale = GetUIScale();
|
float viewScale = GetUIScale();
|
||||||
float sidebarWidth = 320 * viewScale;
|
float sidebarWidth = 320 * viewScale;
|
||||||
@@ -438,7 +438,7 @@ int main(int argc, char* argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Keyboard shortcuts (SPACE for play/stop toggle, ESC for clear)
|
// Keyboard shortcuts (SPACE for play/stop toggle, ESC for clear)
|
||||||
if (IsKeyPressed(KEY_SPACE) && !app.showFileBrowser && !app.showAbout) {
|
if (IsKeyPressed(KEY_SPACE) && !UiModalOpen()) {
|
||||||
if (app.isPlaying && AudioPlaybackSound.frameCount > 0) {
|
if (app.isPlaying && AudioPlaybackSound.frameCount > 0) {
|
||||||
// Currently playing - stop it
|
// Currently playing - stop it
|
||||||
StopSound(AudioPlaybackSound);
|
StopSound(AudioPlaybackSound);
|
||||||
@@ -459,7 +459,7 @@ int main(int argc, char* argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Export PNG
|
// Export PNG
|
||||||
if (IsKeyPressed(KEY_E) && !app.showFileBrowser && !app.showAbout && app.stftComputed) {
|
if (IsKeyPressed(KEY_E) && !UiModalOpen() && app.stftComputed) {
|
||||||
ExportPNG(&app, app.exportDir);
|
ExportPNG(&app, app.exportDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,7 +540,7 @@ int main(int argc, char* argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LMB drag = box select (time + frequency) OR drag existing selection
|
// LMB drag = box select (time + frequency) OR drag existing selection
|
||||||
if (app.loaded && !app.showFileBrowser && !app.showAbout && CheckCollisionPointRec(mousePos, selBounds)) {
|
if (app.loaded && !UiModalOpen() && CheckCollisionPointRec(mousePos, selBounds)) {
|
||||||
// Set cursor to resize all when near divider
|
// Set cursor to resize all when near divider
|
||||||
if (mouseNearDivider && !app.isDividing) {
|
if (mouseNearDivider && !app.isDividing) {
|
||||||
SetMouseCursor(MOUSE_CURSOR_RESIZE_ALL);
|
SetMouseCursor(MOUSE_CURSOR_RESIZE_ALL);
|
||||||
@@ -639,7 +639,7 @@ int main(int argc, char* argv[])
|
|||||||
|
|
||||||
// Handle divider drag. Works whether or not the scope is currently shown
|
// Handle divider drag. Works whether or not the scope is currently shown
|
||||||
// (when hidden, the handle sits at the bottom and can be dragged back up).
|
// (when hidden, the handle sits at the bottom and can be dragged back up).
|
||||||
if (app.loaded && !app.showFileBrowser && !app.showAbout) {
|
if (app.loaded && !UiModalOpen()) {
|
||||||
// Grab the handle. The starting position is the *effective* divider,
|
// Grab the handle. The starting position is the *effective* divider,
|
||||||
// which is the bottom of the view (1.0) while the scope is hidden.
|
// which is the bottom of the view (1.0) while the scope is hidden.
|
||||||
if (mouseNearDivider && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
if (mouseNearDivider && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||||
|
|||||||
@@ -226,6 +226,13 @@ extern Font mainFont;
|
|||||||
// (defined in spectrogram.c; used by every load path).
|
// (defined in spectrogram.c; used by every load path).
|
||||||
void ResetForNewSignal(void);
|
void ResetForNewSignal(void);
|
||||||
|
|
||||||
|
// True when a modal overlay owns input; normal spectrogram/keyboard interaction
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Small math helpers (header-inline so every module can use them)
|
// Small math helpers (header-inline so every module can use them)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -394,7 +394,6 @@ void DrawSidebar(void)
|
|||||||
|
|
||||||
// Colormap dropdown
|
// Colormap dropdown
|
||||||
DrawTextScaled("Colormap:", x, y, 14, LIGHTGRAY); y += 20 * scale;
|
DrawTextScaled("Colormap:", x, y, 14, LIGHTGRAY); y += 20 * scale;
|
||||||
const char* colormapNames[] = { "Grays", "Inferno", "Viridis", "Plasma", "Hot", "Cool" };
|
|
||||||
Rectangle cmapButton = { x, y, sidebarWidth - 10 * scale, 25 * scale };
|
Rectangle cmapButton = { x, y, sidebarWidth - 10 * scale, 25 * scale };
|
||||||
if (CheckCollisionPointRec(GetMousePosition(), cmapButton) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
if (CheckCollisionPointRec(GetMousePosition(), cmapButton) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||||
app.colormap = (ColormapType)((app.colormap + 1) % COLORMAP_COUNT);
|
app.colormap = (ColormapType)((app.colormap + 1) % COLORMAP_COUNT);
|
||||||
@@ -403,7 +402,7 @@ void DrawSidebar(void)
|
|||||||
}
|
}
|
||||||
DrawRectangleRec(cmapButton, (Color){ 50, 50, 60, 255 });
|
DrawRectangleRec(cmapButton, (Color){ 50, 50, 60, 255 });
|
||||||
DrawRectangleLinesEx(cmapButton, 1, GRAY);
|
DrawRectangleLinesEx(cmapButton, 1, GRAY);
|
||||||
DrawTextScaled(colormapNames[app.colormap], cmapButton.x + 10 * scale, cmapButton.y + 6 * scale, 14, WHITE);
|
DrawTextScaled(ColormapName(app.colormap), cmapButton.x + 10 * scale, cmapButton.y + 6 * scale, 14, WHITE);
|
||||||
DrawTexturePro(colormapTexture, (Rectangle){ 0, 0, 256, 1 },
|
DrawTexturePro(colormapTexture, (Rectangle){ 0, 0, 256, 1 },
|
||||||
(Rectangle){ cmapButton.x + cmapButton.width - 60 * scale, cmapButton.y + 5 * scale, 50 * scale, 15 * scale },
|
(Rectangle){ cmapButton.x + cmapButton.width - 60 * scale, cmapButton.y + 5 * scale, 50 * scale, 15 * scale },
|
||||||
(Vector2){ 0, 0 }, 0.0f, WHITE);
|
(Vector2){ 0, 0 }, 0.0f, WHITE);
|
||||||
|
|||||||
Reference in New Issue
Block a user