From 7f0444bc308fef0b78b2fd4fa8aa269da619324c Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sat, 27 Jun 2026 23:50:08 +0900 Subject: fix: extract help text + checkbox into ui_render_overlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The help text and study-mode checkbox were inside ui_render_player, so they only appeared when an audio file was loaded. In the original single-file main.c they rendered unconditionally (outside the 'if loaded' block). Extracted ui_render_overlay() — renders help text + checkbox — and call it from main.c after either ui_render_player or ui_render_empty, matching the original behavior. Also added a screenshot mode (env STUDY_PLAYER_SCREENSHOT) for automated visual testing: renders to an offscreen RenderTexture, fixes FBO alpha artifacts (ClearBackground doesn't fill alpha on some Mesa drivers), and exports a PNG. Verified all UI elements render correctly. --- src/main.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/ui.c | 55 +++++++++++++++++++++++++++---------------------------- src/ui.h | 5 +++++ 3 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/main.c b/src/main.c index 91dc984..dd90946 100644 --- a/src/main.c +++ b/src/main.c @@ -10,6 +10,7 @@ #include "raygui.h" #include +#include #include #include "types.h" @@ -37,6 +38,11 @@ static char exeDir[512]; static int activeTab = 0; static int prevTab = 0; +/* --- Screenshot mode (env: STUDY_PLAYER_SCREENSHOT=filename) --- */ +static const char *screenshotFile = NULL; +static int screenshotFrameCount = 0; +#define SCREENSHOT_DELAY_FRAMES 60 /* ~1 second at 60fps for window to fully map */ + /* ------------------------------------------------------------------ */ /* File loading (drag-drop desktop / JS callback web) */ /* ------------------------------------------------------------------ */ @@ -108,11 +114,54 @@ static void update_frame(void) ui_render_player(&ui, &state, &layout); else ui_render_empty(&ui, &layout); + ui_render_overlay(&ui, &state, &layout); } else { layout_editor_draw(exeDir, &layout); } EndDrawing(); + + /* --- Screenshot mode: render to FBO, save as PNG, then exit --- */ + if (screenshotFile) { + screenshotFrameCount++; + if (screenshotFrameCount >= SCREENSHOT_DELAY_FRAMES) { + RenderTexture2D target = LoadRenderTexture(SCREEN_W, SCREEN_H); + BeginTextureMode(target); + ClearBackground(ui.bgColor); + /* Explicitly fill the entire texture with bg color + alpha 255 */ + DrawRectangle(0, 0, SCREEN_W, SCREEN_H, ui.bgColor); + char *tabNames2[] = { "Player", "Layout" }; + GuiTabBar((Rectangle){ 0, 10, SCREEN_W, 32 }, tabNames2, 2, &activeTab); + if (state.loaded) + ui_render_player(&ui, &state, &layout); + else + ui_render_empty(&ui, &layout); + ui_render_overlay(&ui, &state, &layout); + EndTextureMode(); + + /* Read from the render texture — it has proper content */ + Image img = LoadImageFromTexture(target.texture); + ImageFlipVertical(&img); + /* ClearBackground doesn't fill FBO alpha on some Mesa drivers. + * Manually replace all transparent (alpha=0) pixels with the + * background color, so the saved PNG has a proper background. */ + if (img.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) { + unsigned char *px = (unsigned char *)img.data; + for (int i = 0; i < img.width * img.height * 4; i += 4) { + if (px[i+3] == 0) { + px[i] = ui.bgColor.r; + px[i+1] = ui.bgColor.g; + px[i+2] = ui.bgColor.b; + px[i+3] = 255; + } + } + } + ExportImage(img, screenshotFile); + UnloadImage(img); + UnloadRenderTexture(target); + screenshotFile = NULL; + } + } } /* ------------------------------------------------------------------ */ @@ -125,6 +174,11 @@ int main(void) InitAudioDevice(); SetTargetFPS(60); + /* --- Screenshot mode (env-controlled, for automated testing) --- */ + screenshotFile = getenv("STUDY_PLAYER_SCREENSHOT"); + if (screenshotFile) + MaximizeWindow(); /* force window to be mapped/visible */ + ui_init(&ui); memset(&state, 0, sizeof(state)); @@ -156,8 +210,11 @@ int main(void) #ifdef PLATFORM_WEB emscripten_set_main_loop(update_frame, 0, 1); #else - while (!WindowShouldClose()) + while (!WindowShouldClose()) { update_frame(); + if (screenshotFile == NULL && screenshotFrameCount >= SCREENSHOT_DELAY_FRAMES) + break; /* screenshot taken, exit */ + } #endif player_unload(&state); diff --git a/src/ui.c b/src/ui.c index 7d6a808..600c368 100644 --- a/src/ui.c +++ b/src/ui.c @@ -426,34 +426,6 @@ void ui_render_player(const UIState *ui, const PlayerState *state, float ty = smartBtn.y + (smartBtn.height - ui->szSmall) / 2.0f; DrawTextEx(ui->fontSmall, "Play", (Vector2){ tx, ty }, ui->szSmall, btnSpacing, btnTextColor); } - - /* --- Help text --- */ - { - float helpSpacing = ui->szHelp * 0.03f; - DrawTextEx(ui->fontHelp, - "C: pause N: play Space(hold): override V/B: prev/next Arrows: seek 0-9: jump", - (Vector2){ layout->helpX, layout->helpY }, - ui->szHelp, helpSpacing, ui->mutedColor); - } - - /* --- Study mode checkbox --- */ - { - float helpSpacing = ui->szHelp * 0.03f; - const char *label = "Study Mode"; - float cbSize = 30.0f; - Vector2 labelSize = MeasureTextEx(ui->fontHelp, label, ui->szHelp, helpSpacing); - float totalW = cbSize + 10 + labelSize.x; - float cbX = SCREEN_W - totalW - layout->helpX; - float cbY = layout->helpY + (ui->szHelp - cbSize) / 2.0f; - - Rectangle cbRect = { cbX, cbY, cbSize, cbSize }; - DrawRectangleLinesEx(cbRect, 2, ui->mutedColor); - if (state->studyMode) - DrawRectangleRec((Rectangle){ cbX + 6, cbY + 6, cbSize - 12, cbSize - 12 }, ui->accentColor); - DrawTextEx(ui->fontHelp, label, - (Vector2){ cbX + cbSize + 10, layout->helpY }, - ui->szHelp, helpSpacing, ui->mutedColor); - } } void ui_render_empty(const UIState *ui, const UILayout *layout) @@ -468,3 +440,30 @@ void ui_render_empty(const UIState *ui, const UILayout *layout) (float)SCREEN_W / 2.0f, SCREEN_H / 2.0f - 20, ui->szMed, ui->mutedColor); #endif } + +void ui_render_overlay(const UIState *ui, const PlayerState *state, + const UILayout *layout) +{ + /* --- Help text --- */ + float helpSpacing = ui->szHelp * 0.03f; + DrawTextEx(ui->fontHelp, + "C: pause N: play Space(hold): override V/B: prev/next Arrows: seek 0-9: jump", + (Vector2){ layout->helpX, layout->helpY }, + ui->szHelp, helpSpacing, ui->mutedColor); + + /* --- Study mode checkbox --- */ + const char *label = "Study Mode"; + float cbSize = 30.0f; + Vector2 labelSize = MeasureTextEx(ui->fontHelp, label, ui->szHelp, helpSpacing); + float totalW = cbSize + 10 + labelSize.x; + float cbX = SCREEN_W - totalW - layout->helpX; + float cbY = layout->helpY + (ui->szHelp - cbSize) / 2.0f; + + Rectangle cbRect = { cbX, cbY, cbSize, cbSize }; + DrawRectangleLinesEx(cbRect, 2, ui->mutedColor); + if (state->studyMode) + DrawRectangleRec((Rectangle){ cbX + 6, cbY + 6, cbSize - 12, cbSize - 12 }, ui->accentColor); + DrawTextEx(ui->fontHelp, label, + (Vector2){ cbX + cbSize + 10, layout->helpY }, + ui->szHelp, helpSpacing, ui->mutedColor); +} diff --git a/src/ui.h b/src/ui.h index 10b4240..d6d0b24 100644 --- a/src/ui.h +++ b/src/ui.h @@ -60,3 +60,8 @@ void ui_render_player(const UIState *ui, const PlayerState *state, /* Draw the "no file loaded" splash screen. */ void ui_render_empty(const UIState *ui, const UILayout *layout); + +/* Draw the help text and study-mode checkbox (always visible on tab 0). + * Call after ui_render_player or ui_render_empty. */ +void ui_render_overlay(const UIState *ui, const PlayerState *state, + const UILayout *layout); -- cgit v1.2.3