// MenuState.cpp #include "MenuState.h" #include "persistence/Scores.h" #include "graphics/Font.h" #include "../core/GlobalState.h" #include "../audio/Audio.h" #include #include #include #include // Local logical canvas size (matches main.cpp). Kept local to avoid changing many files. static constexpr int LOGICAL_W = 1200; static constexpr int LOGICAL_H = 1000; // Shared flags and resources are provided via StateContext `ctx`. // Removed fragile extern declarations and use `ctx.showLevelPopup`, `ctx.showSettingsPopup`, // `ctx.musicEnabled` and `ctx.hoveredButton` instead to avoid globals. // Menu helper wrappers are declared in a shared header implemented in main.cpp #include "../audio/MenuWrappers.h" MenuState::MenuState(StateContext& ctx) : State(ctx) {} void MenuState::onEnter() { SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState::onEnter called"); } void MenuState::onExit() { } void MenuState::handleEvent(const SDL_Event& e) { // Key-specific handling (allow main to handle global keys) (void)e; } void MenuState::update(double frameMs) { // Update logo animation counter and particles similar to main (void)frameMs; } void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) { // Trace entry to persistent log for debugging abrupt exit/crash during render { FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render entry\n"); fclose(f); } } // Since ApplicationManager sets up a centered viewport, we draw directly in logical coordinates // No additional content offset is needed - the viewport itself handles centering float contentOffsetX = 0.0f; float contentOffsetY = 0.0f; // Background is drawn by main (stretched to the full window) to avoid double-draw. // Draw the animated logo and fireworks using the small logo if available (show whole image) SDL_Texture* logoToUse = ctx.logoSmallTex ? ctx.logoSmallTex : ctx.logoTex; // Trace logo texture pointer { FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render logoToUse=%llu\n", (unsigned long long)(uintptr_t)logoToUse); fclose(f); } } if (logoToUse) { // Use dimensions provided by the shared context when available int texW = (logoToUse == ctx.logoSmallTex && ctx.logoSmallW > 0) ? ctx.logoSmallW : 872; int texH = (logoToUse == ctx.logoSmallTex && ctx.logoSmallH > 0) ? ctx.logoSmallH : 273; float maxW = LOGICAL_W * 0.6f; float scale = std::min(1.0f, maxW / float(texW)); float dw = texW * scale; float dh = texH * scale; float logoX = (LOGICAL_W - dw) / 2.f + contentOffsetX; float logoY = LOGICAL_H * 0.05f + contentOffsetY; SDL_FRect dst{logoX, logoY, dw, dh}; // Trace before rendering logo { FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render before SDL_RenderTexture logo ptr=%llu\n", (unsigned long long)(uintptr_t)logoToUse); fclose(f); } } SDL_RenderTexture(renderer, logoToUse, nullptr, &dst); // Trace after rendering logo { FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render after SDL_RenderTexture logo\n"); fclose(f); } } } // Fireworks (draw above high scores / near buttons) { FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render before drawFireworks blocksTex=%llu\n", (unsigned long long)(uintptr_t)ctx.blocksTex); fclose(f); } } GlobalState::instance().drawFireworks(renderer, ctx.blocksTex); { FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render after drawFireworks\n"); fclose(f); } } // Score list and top players with a sine-wave vertical animation (use pixelFont for retro look) float topPlayersY = LOGICAL_H * 0.30f + contentOffsetY; // more top padding FontAtlas* useFont = ctx.pixelFont ? ctx.pixelFont : ctx.font; if (useFont) { { FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render before useFont->draw TOP PLAYERS ptr=%llu\n", (unsigned long long)(uintptr_t)useFont); fclose(f); } } useFont->draw(renderer, LOGICAL_W * 0.5f - 110 + contentOffsetX, topPlayersY, std::string("TOP PLAYERS"), 1.8f, SDL_Color{255, 220, 0, 255}); { FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render after useFont->draw TOP PLAYERS\n"); fclose(f); } } } // High scores table with wave offset float scoresStartY = topPlayersY + 70; // more spacing under title const auto &hs = ctx.scores ? ctx.scores->all() : *(new std::vector()); size_t maxDisplay = std::min(hs.size(), size_t(12)); // Draw table header if (useFont) { float cx = LOGICAL_W * 0.5f + contentOffsetX; float colX[] = { cx - 280, cx - 180, cx - 20, cx + 90, cx + 200, cx + 300 }; useFont->draw(renderer, colX[0], scoresStartY - 28, "RANK", 1.1f, SDL_Color{200,200,220,255}); useFont->draw(renderer, colX[1], scoresStartY - 28, "PLAYER", 1.1f, SDL_Color{200,200,220,255}); useFont->draw(renderer, colX[2], scoresStartY - 28, "SCORE", 1.1f, SDL_Color{200,200,220,255}); useFont->draw(renderer, colX[3], scoresStartY - 28, "LINES", 1.1f, SDL_Color{200,200,220,255}); useFont->draw(renderer, colX[4], scoresStartY - 28, "LEVEL", 1.1f, SDL_Color{200,200,220,255}); useFont->draw(renderer, colX[5], scoresStartY - 28, "TIME", 1.1f, SDL_Color{200,200,220,255}); } // Center columns around mid X, wider float cx = LOGICAL_W * 0.5f + contentOffsetX; float colX[] = { cx - 280, cx - 180, cx - 20, cx + 90, cx + 200, cx + 300 }; for (size_t i = 0; i < maxDisplay; ++i) { float baseY = scoresStartY + i * 25; float wave = std::sin((float)GlobalState::instance().logoAnimCounter * 0.006f + i * 0.25f) * 6.0f; // subtle wave float y = baseY + wave; char rankStr[8]; std::snprintf(rankStr, sizeof(rankStr), "%zu.", i + 1); if (useFont) useFont->draw(renderer, colX[0], y, rankStr, 1.0f, SDL_Color{220, 220, 230, 255}); if (useFont) useFont->draw(renderer, colX[1], y, hs[i].name, 1.0f, SDL_Color{220, 220, 230, 255}); char scoreStr[16]; std::snprintf(scoreStr, sizeof(scoreStr), "%d", hs[i].score); if (useFont) useFont->draw(renderer, colX[2], y, scoreStr, 1.0f, SDL_Color{220, 220, 230, 255}); char linesStr[8]; std::snprintf(linesStr, sizeof(linesStr), "%d", hs[i].lines); if (useFont) useFont->draw(renderer, colX[3], y, linesStr, 1.0f, SDL_Color{220, 220, 230, 255}); char levelStr[8]; std::snprintf(levelStr, sizeof(levelStr), "%d", hs[i].level); if (useFont) useFont->draw(renderer, colX[4], y, levelStr, 1.0f, SDL_Color{220, 220, 230, 255}); char timeStr[16]; int mins = int(hs[i].timeSec) / 60; int secs = int(hs[i].timeSec) % 60; std::snprintf(timeStr, sizeof(timeStr), "%d:%02d", mins, secs); if (useFont) useFont->draw(renderer, colX[5], y, timeStr, 1.0f, SDL_Color{220, 220, 230, 255}); } // Draw bottom action buttons with responsive sizing (reduced to match main mouse hit-test) // Since we removed content offsets, calculate contentW directly from the scale and logical size float contentW = LOGICAL_W * logicalScale; bool isSmall = (contentW < 700.0f); float btnW = isSmall ? (LOGICAL_W * 0.4f) : 300.0f; float btnH = isSmall ? 60.0f : 70.0f; float btnX = LOGICAL_W * 0.5f + contentOffsetX; // Move buttons down by 40px to match original layout (user requested 30-50px) const float btnYOffset = 40.0f; float btnY = LOGICAL_H * 0.86f + contentOffsetY + btnYOffset; // align with main's button vertical position if (ctx.pixelFont) { { FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render drawing buttons; pixelFont=%llu\n", (unsigned long long)(uintptr_t)ctx.pixelFont); fclose(f); } } char levelBtnText[32]; int startLevel = ctx.startLevelSelection ? *ctx.startLevelSelection : 0; std::snprintf(levelBtnText, sizeof(levelBtnText), "LEVEL %d", startLevel); // Draw simple styled buttons (replicating menu_drawMenuButton) auto drawMenuButtonLocal = [&](SDL_Renderer* r, FontAtlas& font, float cx, float cy, float w, float h, const std::string& label, SDL_Color bg, SDL_Color border){ float x = cx - w/2; float y = cy - h/2; SDL_SetRenderDrawColor(r, border.r, border.g, border.b, border.a); SDL_FRect br{ x-6, y-6, w+12, h+12 }; SDL_RenderFillRect(r, &br); SDL_SetRenderDrawColor(r, 255,255,255,255); SDL_FRect br2{ x-4, y-4, w+8, h+8 }; SDL_RenderFillRect(r, &br2); SDL_SetRenderDrawColor(r, bg.r, bg.g, bg.b, bg.a); SDL_FRect br3{ x, y, w, h }; SDL_RenderFillRect(r, &br3); float textScale = 1.6f; float approxCharW = 12.0f * textScale; float textW = label.length() * approxCharW; float tx = x + (w - textW) / 2.0f; float ty = y + (h - 20.0f * textScale) / 2.0f; font.draw(r, tx+2, ty+2, label, textScale, SDL_Color{0,0,0,180}); font.draw(r, tx, ty, label, textScale, SDL_Color{255,255,255,255}); }; drawMenuButtonLocal(renderer, *ctx.pixelFont, btnX - btnW * 0.6f, btnY, btnW, btnH, std::string("PLAY"), SDL_Color{60,180,80,255}, SDL_Color{30,120,40,255}); { FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render after draw PLAY button\n"); fclose(f); } } drawMenuButtonLocal(renderer, *ctx.pixelFont, btnX + btnW * 0.6f, btnY, btnW, btnH, std::string(levelBtnText), SDL_Color{40,140,240,255}, SDL_Color{20,100,200,255}); { FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render after draw LEVEL button\n"); fclose(f); } } } // Popups (settings only - level popup is now a separate state) if (ctx.showSettingsPopup && *ctx.showSettingsPopup) { // draw settings popup inline bool musicOn = ctx.musicEnabled ? *ctx.musicEnabled : true; float popupW = 350, popupH = 260; float popupX = (LOGICAL_W - popupW) / 2; float popupY = (LOGICAL_H - popupH) / 2; SDL_SetRenderDrawColor(renderer, 0,0,0,128); SDL_FRect overlay{0,0,(float)LOGICAL_W,(float)LOGICAL_H}; SDL_RenderFillRect(renderer, &overlay); SDL_SetRenderDrawColor(renderer, 100,120,160,255); SDL_FRect bord{popupX-4,popupY-4,popupW+8,popupH+8}; SDL_RenderFillRect(renderer, &bord); SDL_SetRenderDrawColor(renderer, 40,50,70,255); SDL_FRect body{popupX,popupY,popupW,popupH}; SDL_RenderFillRect(renderer, &body); ctx.font->draw(renderer, popupX + 20, popupY + 20, "SETTINGS", 2.0f, SDL_Color{255,220,0,255}); ctx.font->draw(renderer, popupX + 20, popupY + 70, "MUSIC:", 1.5f, SDL_Color{255,255,255,255}); ctx.font->draw(renderer, popupX + 120, popupY + 70, musicOn ? "ON" : "OFF", 1.5f, musicOn ? SDL_Color{0,255,0,255} : SDL_Color{255,0,0,255}); ctx.font->draw(renderer, popupX + 20, popupY + 100, "SOUND FX:", 1.5f, SDL_Color{255,255,255,255}); ctx.font->draw(renderer, popupX + 140, popupY + 100, "ON", 1.5f, SDL_Color{0,255,0,255}); ctx.font->draw(renderer, popupX + 20, popupY + 150, "M = TOGGLE MUSIC", 1.0f, SDL_Color{200,200,220,255}); ctx.font->draw(renderer, popupX + 20, popupY + 170, "S = TOGGLE SOUND FX", 1.0f, SDL_Color{200,200,220,255}); ctx.font->draw(renderer, popupX + 20, popupY + 190, "N = PLAY LETS_GO", 1.0f, SDL_Color{200,200,220,255}); ctx.font->draw(renderer, popupX + 20, popupY + 210, "ESC = CLOSE", 1.0f, SDL_Color{200,200,220,255}); } // Trace exit { FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render exit\n"); fclose(f); } } }