diff --git a/src/core/Config.h b/src/core/Config.h index 77be13f..b8f02ab 100644 --- a/src/core/Config.h +++ b/src/core/Config.h @@ -150,4 +150,11 @@ namespace Config { constexpr int STARFIELD_PARTICLE_COUNT = 200; constexpr int STARFIELD_3D_PARTICLE_COUNT = 200; } + + // Visual effects settings + namespace Visuals { + constexpr int PAUSE_BLUR_ITERATIONS = 8; // Number of blur passes (higher = more blur) + constexpr int PAUSE_BLUR_OFFSET = 3; // Pixel spread of the blur + constexpr int PAUSE_BLUR_ALPHA = 40; // Alpha intensity of blur layers + } } diff --git a/src/core/application/ApplicationManager.cpp b/src/core/application/ApplicationManager.cpp index d509779..23e7e64 100644 --- a/src/core/application/ApplicationManager.cpp +++ b/src/core/application/ApplicationManager.cpp @@ -1107,9 +1107,7 @@ void ApplicationManager::setupStateHandlers() { LOGICAL_H, logicalScale, static_cast(winW), - static_cast(winH), - m_showExitConfirmPopup, - m_exitPopupSelectedButton + static_cast(winH) ); // Reset viewport diff --git a/src/graphics/renderers/GameRenderer.cpp b/src/graphics/renderers/GameRenderer.cpp index b03b1d4..560fa2a 100644 --- a/src/graphics/renderers/GameRenderer.cpp +++ b/src/graphics/renderers/GameRenderer.cpp @@ -122,10 +122,7 @@ void GameRenderer::renderPlayingState( float logicalH, float logicalScale, float winW, - float winH, - bool showExitConfirmPopup, - int exitPopupSelectedButton, - bool suppressPauseVisuals + float winH ) { if (!game || !pixelFont) return; @@ -238,7 +235,7 @@ void GameRenderer::renderPlayingState( } } - bool allowActivePieceRender = !game->isPaused() || suppressPauseVisuals; + bool allowActivePieceRender = true; // Draw ghost piece (where current piece will land) if (allowActivePieceRender) { @@ -428,150 +425,177 @@ void GameRenderer::renderPlayingState( drawSmallPiece(renderer, blocksTex, static_cast(game->held().type), statsX + 60, statsY + statsH - 80, finalBlockSize * 0.6f); } - // Pause overlay (skip when visuals are suppressed, e.g., countdown) - if (!suppressPauseVisuals && game->isPaused() && !showExitConfirmPopup) { - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); - - // Draw blur effect around the grid (keep in logical coordinates) - for (int i = -4; i <= 4; ++i) { - float spread = static_cast(std::abs(i)); - Uint8 alpha = Uint8(std::max(8.f, 32.f - spread * 4.f)); - SDL_SetRenderDrawColor(renderer, 24, 32, 48, alpha); - SDL_FRect blurRect{ - gridX - spread * 2.0f, - gridY - spread * 1.5f, - GRID_W + spread * 4.0f, - GRID_H + spread * 3.0f - }; - SDL_RenderFillRect(renderer, &blurRect); - } + // Pause overlay logic moved to renderPauseOverlay - // Switch to window coordinates for the full-screen overlay and text - SDL_Rect oldViewport; - SDL_GetRenderViewport(renderer, &oldViewport); - float oldScaleX, oldScaleY; - SDL_GetRenderScale(renderer, &oldScaleX, &oldScaleY); - - SDL_SetRenderViewport(renderer, nullptr); - SDL_SetRenderScale(renderer, 1.0f, 1.0f); - - // Draw full screen overlay - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180); - SDL_FRect pauseOverlay{0, 0, winW, winH}; - SDL_RenderFillRect(renderer, &pauseOverlay); - - // Draw centered text - // Note: We multiply font scale by logicalScale to maintain consistent size - // since we reset the renderer scale to 1.0 - const char* pausedText = "PAUSED"; - float pausedScale = 2.0f * logicalScale; - int pW = 0, pH = 0; - pixelFont->measure(pausedText, pausedScale, pW, pH); - pixelFont->draw(renderer, (winW - pW) * 0.5f, (winH - pH) * 0.5f - (20 * logicalScale), pausedText, pausedScale, {255, 255, 255, 255}); - - const char* resumeText = "Press P to resume"; - float resumeScale = 0.8f * logicalScale; - int rW = 0, rH = 0; - pixelFont->measure(resumeText, resumeScale, rW, rH); - pixelFont->draw(renderer, (winW - rW) * 0.5f, (winH - pH) * 0.5f + (40 * logicalScale), resumeText, resumeScale, {200, 200, 220, 255}); - - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); - - // Restore previous render state - SDL_SetRenderViewport(renderer, &oldViewport); - SDL_SetRenderScale(renderer, oldScaleX, oldScaleY); - } - // Exit confirmation popup styled like other retro panels - if (showExitConfirmPopup) { - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 200); - SDL_FRect fullWin{0.f, 0.f, winW, winH}; - SDL_RenderFillRect(renderer, &fullWin); - - const float panelW = 640.0f; - const float panelH = 320.0f; - SDL_FRect panel{ - (logicalW - panelW) * 0.5f + contentOffsetX, - (logicalH - panelH) * 0.5f + contentOffsetY, - panelW, - panelH - }; - - SDL_FRect shadow{panel.x + 6.0f, panel.y + 10.0f, panel.w, panel.h}; - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 140); - SDL_RenderFillRect(renderer, &shadow); - - for (int i = 0; i < 5; ++i) { - SDL_FRect glow{panel.x - float(i * 2), panel.y - float(i * 2), panel.w + float(i * 4), panel.h + float(i * 4)}; - SDL_SetRenderDrawColor(renderer, 0, 180, 255, Uint8(44 - i * 7)); - SDL_RenderRect(renderer, &glow); - } - - SDL_SetRenderDrawColor(renderer, 18, 30, 52, 255); - SDL_RenderFillRect(renderer, &panel); - SDL_SetRenderDrawColor(renderer, 70, 120, 210, 255); - SDL_RenderRect(renderer, &panel); - - SDL_FRect inner{panel.x + 24.0f, panel.y + 98.0f, panel.w - 48.0f, panel.h - 146.0f}; - SDL_SetRenderDrawColor(renderer, 16, 24, 40, 235); - SDL_RenderFillRect(renderer, &inner); - SDL_SetRenderDrawColor(renderer, 40, 80, 140, 235); - SDL_RenderRect(renderer, &inner); - - const std::string title = "EXIT GAME?"; - int titleW = 0, titleH = 0; - const float titleScale = 1.8f; - pixelFont->measure(title, titleScale, titleW, titleH); - pixelFont->draw(renderer, panel.x + (panel.w - titleW) * 0.5f, panel.y + 30.0f, title, titleScale, {255, 230, 140, 255}); - - std::array lines = { - "Are you sure you want to quit?", - "Current progress will be lost." - }; - float lineY = inner.y + 22.0f; - const float lineScale = 1.05f; - for (const auto& line : lines) { - int lineW = 0, lineH = 0; - pixelFont->measure(line, lineScale, lineW, lineH); - float textX = panel.x + (panel.w - lineW) * 0.5f; - pixelFont->draw(renderer, textX, lineY, line, lineScale, SDL_Color{210, 220, 240, 255}); - lineY += lineH + 10.0f; - } - - const float horizontalPad = 28.0f; - const float buttonGap = 32.0f; - const float buttonH = 66.0f; - float buttonW = (inner.w - horizontalPad * 2.0f - buttonGap) * 0.5f; - float buttonY = inner.y + inner.h - buttonH - 24.0f; - - auto drawButton = [&](int idx, float x, const char* label) { - bool selected = (exitPopupSelectedButton == idx); - SDL_Color base = (idx == 0) ? SDL_Color{185, 70, 70, 255} : SDL_Color{60, 95, 150, 255}; - SDL_Color body = selected ? SDL_Color{Uint8(std::min(255, base.r + 35)), Uint8(std::min(255, base.g + 35)), Uint8(std::min(255, base.b + 35)), 255} : base; - SDL_Color border = selected ? SDL_Color{255, 220, 120, 255} : SDL_Color{80, 110, 160, 255}; - - SDL_FRect btn{x, buttonY, buttonW, buttonH}; - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 120); - SDL_FRect btnShadow{btn.x + 4.0f, btn.y + 6.0f, btn.w, btn.h}; - SDL_RenderFillRect(renderer, &btnShadow); - SDL_SetRenderDrawColor(renderer, body.r, body.g, body.b, body.a); - SDL_RenderFillRect(renderer, &btn); - SDL_SetRenderDrawColor(renderer, border.r, border.g, border.b, border.a); - SDL_RenderRect(renderer, &btn); - - int textW = 0, textH = 0; - const float labelScale = 1.4f; - pixelFont->measure(label, labelScale, textW, textH); - float textX = btn.x + (btn.w - textW) * 0.5f; - float textY = btn.y + (btn.h - textH) * 0.5f; - SDL_Color textColor = selected ? SDL_Color{255, 255, 255, 255} : SDL_Color{230, 235, 250, 255}; - pixelFont->draw(renderer, textX, textY, label, labelScale, textColor); - }; - - float yesX = inner.x + horizontalPad; - float noX = yesX + buttonW + buttonGap; - drawButton(0, yesX, "YES"); - drawButton(1, noX, "NO"); - } + // Exit popup logic moved to renderExitPopup +} + +void GameRenderer::renderExitPopup( + SDL_Renderer* renderer, + FontAtlas* pixelFont, + float winW, + float winH, + float logicalScale, + int selectedButton +) { + // Calculate content offsets (same as in renderPlayingState for consistency) + // We need to re-calculate them or pass them in? + // The popup uses logical coordinates centered on screen. + // Let's use the same logic as renderPauseOverlay (window coordinates) to be safe and consistent? + // The original code used logical coordinates + contentOffset. + // Let's stick to the original look but render it in window coordinates to ensure it covers everything properly. + + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + + // Switch to window coordinates + SDL_Rect oldViewport; + SDL_GetRenderViewport(renderer, &oldViewport); + float oldScaleX, oldScaleY; + SDL_GetRenderScale(renderer, &oldScaleX, &oldScaleY); + + SDL_SetRenderViewport(renderer, nullptr); + SDL_SetRenderScale(renderer, 1.0f, 1.0f); + + // Full screen dim + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 200); + SDL_FRect fullWin{0.f, 0.f, winW, winH}; + SDL_RenderFillRect(renderer, &fullWin); + + // Calculate panel position (centered in window) + // Original was logicalW based, let's map it to window size. + // Logical 640x320 scaled up. + float panelW = 640.0f * logicalScale; + float panelH = 320.0f * logicalScale; + float panelX = (winW - panelW) * 0.5f; + float panelY = (winH - panelH) * 0.5f; + + SDL_FRect panel{panelX, panelY, panelW, panelH}; + + SDL_FRect shadow{panel.x + 6.0f * logicalScale, panel.y + 10.0f * logicalScale, panel.w, panel.h}; + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 140); + SDL_RenderFillRect(renderer, &shadow); + + for (int i = 0; i < 5; ++i) { + float off = float(i * 2) * logicalScale; + float exp = float(i * 4) * logicalScale; + SDL_FRect glow{panel.x - off, panel.y - off, panel.w + exp, panel.h + exp}; + SDL_SetRenderDrawColor(renderer, 0, 180, 255, Uint8(44 - i * 7)); + SDL_RenderRect(renderer, &glow); + } + + SDL_SetRenderDrawColor(renderer, 18, 30, 52, 255); + SDL_RenderFillRect(renderer, &panel); + SDL_SetRenderDrawColor(renderer, 70, 120, 210, 255); + SDL_RenderRect(renderer, &panel); + + SDL_FRect inner{panel.x + 24.0f * logicalScale, panel.y + 98.0f * logicalScale, panel.w - 48.0f * logicalScale, panel.h - 146.0f * logicalScale}; + SDL_SetRenderDrawColor(renderer, 16, 24, 40, 235); + SDL_RenderFillRect(renderer, &inner); + SDL_SetRenderDrawColor(renderer, 40, 80, 140, 235); + SDL_RenderRect(renderer, &inner); + + const std::string title = "EXIT GAME?"; + int titleW = 0, titleH = 0; + const float titleScale = 1.8f * logicalScale; + pixelFont->measure(title, titleScale, titleW, titleH); + pixelFont->draw(renderer, panel.x + (panel.w - titleW) * 0.5f, panel.y + 30.0f * logicalScale, title, titleScale, {255, 230, 140, 255}); + + std::array lines = { + "Are you sure you want to quit?", + "Current progress will be lost." + }; + float lineY = inner.y + 22.0f * logicalScale; + const float lineScale = 1.05f * logicalScale; + for (const auto& line : lines) { + int lineW = 0, lineH = 0; + pixelFont->measure(line, lineScale, lineW, lineH); + float textX = panel.x + (panel.w - lineW) * 0.5f; + pixelFont->draw(renderer, textX, lineY, line, lineScale, SDL_Color{210, 220, 240, 255}); + lineY += lineH + 10.0f * logicalScale; + } + + const float horizontalPad = 28.0f * logicalScale; + const float buttonGap = 32.0f * logicalScale; + const float buttonH = 66.0f * logicalScale; + float buttonW = (inner.w - horizontalPad * 2.0f - buttonGap) * 0.5f; + float buttonY = inner.y + inner.h - buttonH - 24.0f * logicalScale; + + auto drawButton = [&](int idx, float x, const char* label) { + bool selected = (selectedButton == idx); + SDL_Color base = (idx == 0) ? SDL_Color{185, 70, 70, 255} : SDL_Color{60, 95, 150, 255}; + SDL_Color body = selected ? SDL_Color{Uint8(std::min(255, base.r + 35)), Uint8(std::min(255, base.g + 35)), Uint8(std::min(255, base.b + 35)), 255} : base; + SDL_Color border = selected ? SDL_Color{255, 220, 120, 255} : SDL_Color{80, 110, 160, 255}; + + SDL_FRect btn{x, buttonY, buttonW, buttonH}; + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 120); + SDL_FRect btnShadow{btn.x + 4.0f * logicalScale, btn.y + 6.0f * logicalScale, btn.w, btn.h}; + SDL_RenderFillRect(renderer, &btnShadow); + SDL_SetRenderDrawColor(renderer, body.r, body.g, body.b, body.a); + SDL_RenderFillRect(renderer, &btn); + SDL_SetRenderDrawColor(renderer, border.r, border.g, border.b, border.a); + SDL_RenderRect(renderer, &btn); + + int textW = 0, textH = 0; + const float labelScale = 1.4f * logicalScale; + pixelFont->measure(label, labelScale, textW, textH); + float textX = btn.x + (btn.w - textW) * 0.5f; + float textY = btn.y + (btn.h - textH) * 0.5f; + SDL_Color textColor = selected ? SDL_Color{255, 255, 255, 255} : SDL_Color{230, 235, 250, 255}; + pixelFont->draw(renderer, textX, textY, label, labelScale, textColor); + }; + + float yesX = inner.x + horizontalPad; + float noX = yesX + buttonW + buttonGap; + drawButton(0, yesX, "YES"); + drawButton(1, noX, "NO"); + + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); + + // Restore previous render state + SDL_SetRenderViewport(renderer, &oldViewport); + SDL_SetRenderScale(renderer, oldScaleX, oldScaleY); +} + +void GameRenderer::renderPauseOverlay( + SDL_Renderer* renderer, + FontAtlas* pixelFont, + float winW, + float winH, + float logicalScale +) { + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + + // Switch to window coordinates for the full-screen overlay and text + SDL_Rect oldViewport; + SDL_GetRenderViewport(renderer, &oldViewport); + float oldScaleX, oldScaleY; + SDL_GetRenderScale(renderer, &oldScaleX, &oldScaleY); + + SDL_SetRenderViewport(renderer, nullptr); + SDL_SetRenderScale(renderer, 1.0f, 1.0f); + + // Draw full screen overlay (darken) + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180); + SDL_FRect pauseOverlay{0, 0, winW, winH}; + SDL_RenderFillRect(renderer, &pauseOverlay); + + // Draw centered text + const char* pausedText = "PAUSED"; + float pausedScale = 2.0f * logicalScale; + int pW = 0, pH = 0; + pixelFont->measure(pausedText, pausedScale, pW, pH); + pixelFont->draw(renderer, (winW - pW) * 0.5f, (winH - pH) * 0.5f - (20 * logicalScale), pausedText, pausedScale, {255, 255, 255, 255}); + + const char* resumeText = "Press P to resume"; + float resumeScale = 0.8f * logicalScale; + int rW = 0, rH = 0; + pixelFont->measure(resumeText, resumeScale, rW, rH); + pixelFont->draw(renderer, (winW - rW) * 0.5f, (winH - pH) * 0.5f + (40 * logicalScale), resumeText, resumeScale, {200, 200, 220, 255}); + + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); + + // Restore previous render state + SDL_SetRenderViewport(renderer, &oldViewport); + SDL_SetRenderScale(renderer, oldScaleX, oldScaleY); } diff --git a/src/graphics/renderers/GameRenderer.h b/src/graphics/renderers/GameRenderer.h index 6ad6fee..cd24d19 100644 --- a/src/graphics/renderers/GameRenderer.h +++ b/src/graphics/renderers/GameRenderer.h @@ -25,10 +25,26 @@ public: float logicalH, float logicalScale, float winW, + float winH + ); + + // Render the pause overlay (full screen) + static void renderPauseOverlay( + SDL_Renderer* renderer, + FontAtlas* pixelFont, + float winW, float winH, - bool showExitConfirmPopup, - int exitPopupSelectedButton = 1, // 0=YES, 1=NO - bool suppressPauseVisuals = false + float logicalScale + ); + + // Render the exit confirmation popup + static void renderExitPopup( + SDL_Renderer* renderer, + FontAtlas* pixelFont, + float winW, + float winH, + float logicalScale, + int selectedButton ); private: diff --git a/src/main.cpp b/src/main.cpp index 4ae8055..b35d7b9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -729,7 +729,7 @@ int main(int, char **) bool gameplayCountdownActive = false; double gameplayCountdownElapsed = 0.0; int gameplayCountdownIndex = 0; - const double GAMEPLAY_COUNTDOWN_STEP_MS = 600.0; + const double GAMEPLAY_COUNTDOWN_STEP_MS = 400.0; const std::array GAMEPLAY_COUNTDOWN_LABELS = { "3", "2", "1", "START" }; // Instantiate state manager @@ -758,6 +758,8 @@ int main(int, char **) ctx.showSettingsPopup = &showSettingsPopup; ctx.showExitConfirmPopup = &showExitConfirmPopup; ctx.exitPopupSelectedButton = &exitPopupSelectedButton; + ctx.gameplayCountdownActive = &gameplayCountdownActive; + ctx.menuPlayCountdownArmed = &menuPlayCountdownArmed; ctx.playerName = &playerName; ctx.fullscreenFlag = &isFullscreen; ctx.applyFullscreen = [window, &isFullscreen](bool enable) { @@ -1505,21 +1507,7 @@ int main(int, char **) } break; case AppState::Playing: - GameRenderer::renderPlayingState( - renderer, - &game, - &pixelFont, - &lineEffect, - blocksTex, - (float)LOGICAL_W, - (float)LOGICAL_H, - logicalScale, - (float)winW, - (float)winH, - showExitConfirmPopup, - exitPopupSelectedButton, - (gameplayCountdownActive || menuPlayCountdownArmed) - ); + playingState->render(renderer, logicalScale, logicalVP); break; case AppState::GameOver: // Draw the game state in the background @@ -1533,8 +1521,7 @@ int main(int, char **) (float)LOGICAL_H, logicalScale, (float)winW, - (float)winH, - false // No exit popup in Game Over + (float)winH ); // Draw Game Over Overlay @@ -1662,9 +1649,10 @@ int main(int, char **) SDL_SetRenderViewport(renderer, nullptr); SDL_SetRenderScale(renderer, 1.f, 1.f); SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 160); - SDL_FRect dimRect{0.f, 0.f, (float)winW, (float)winH}; - SDL_RenderFillRect(renderer, &dimRect); + // Removed background overlay for cleaner countdown + // SDL_SetRenderDrawColor(renderer, 0, 0, 0, 160); + // SDL_FRect dimRect{0.f, 0.f, (float)winW, (float)winH}; + // SDL_RenderFillRect(renderer, &dimRect); SDL_SetRenderViewport(renderer, &logicalVP); SDL_SetRenderScale(renderer, logicalScale, logicalScale); diff --git a/src/states/PlayingState.cpp b/src/states/PlayingState.cpp index 0f5398f..7838221 100644 --- a/src/states/PlayingState.cpp +++ b/src/states/PlayingState.cpp @@ -4,6 +4,8 @@ #include "../gameplay/effects/LineEffect.h" #include "../persistence/Scores.h" #include "../audio/Audio.h" +#include "../graphics/renderers/GameRenderer.h" +#include "../core/Config.h" #include PlayingState::PlayingState(StateContext& ctx) : State(ctx) {} @@ -18,6 +20,10 @@ void PlayingState::onEnter() { } void PlayingState::onExit() { + if (m_renderTarget) { + SDL_DestroyTexture(m_renderTarget); + m_renderTarget = nullptr; + } } void PlayingState::handleEvent(const SDL_Event& e) { @@ -145,5 +151,132 @@ void PlayingState::update(double frameMs) { void PlayingState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) { if (!ctx.game) return; - // Rendering kept in main for now to avoid changing many layout calculations in one change. + + // Get current window size + int winW = 0, winH = 0; + SDL_GetRenderOutputSize(renderer, &winW, &winH); + + // Create or resize render target if needed + if (!m_renderTarget || m_targetW != winW || m_targetH != winH) { + if (m_renderTarget) SDL_DestroyTexture(m_renderTarget); + m_renderTarget = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, winW, winH); + SDL_SetTextureBlendMode(m_renderTarget, SDL_BLENDMODE_BLEND); + m_targetW = winW; + m_targetH = winH; + } + + bool paused = ctx.game->isPaused(); + bool exitPopup = ctx.showExitConfirmPopup && *ctx.showExitConfirmPopup; + bool countdown = (ctx.gameplayCountdownActive && *ctx.gameplayCountdownActive) || + (ctx.menuPlayCountdownArmed && *ctx.menuPlayCountdownArmed); + + // Only blur if paused AND NOT in countdown (and not exit popup, though exit popup implies paused) + // Actually, exit popup should probably still blur/dim. + // But countdown should definitely NOT show the "PAUSED" overlay. + bool shouldBlur = paused && !countdown; + + if (shouldBlur && m_renderTarget) { + // Render game to texture + SDL_SetRenderTarget(renderer, m_renderTarget); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); + SDL_RenderClear(renderer); + + // Apply the same view/scale as main.cpp uses + SDL_SetRenderViewport(renderer, &logicalVP); + SDL_SetRenderScale(renderer, logicalScale, logicalScale); + + // Render game content (no overlays) + GameRenderer::renderPlayingState( + renderer, + ctx.game, + ctx.pixelFont, + ctx.lineEffect, + ctx.blocksTex, + 1200.0f, // LOGICAL_W + 1000.0f, // LOGICAL_H + logicalScale, + (float)winW, + (float)winH + ); + + // Reset to screen + SDL_SetRenderTarget(renderer, nullptr); + + // Draw blurred texture + SDL_Rect oldVP; + SDL_GetRenderViewport(renderer, &oldVP); + float oldSX, oldSY; + SDL_GetRenderScale(renderer, &oldSX, &oldSY); + + SDL_SetRenderViewport(renderer, nullptr); + SDL_SetRenderScale(renderer, 1.0f, 1.0f); + + SDL_FRect dst{0, 0, (float)winW, (float)winH}; + + // Blur pass (accumulate multiple offset copies) + int offset = Config::Visuals::PAUSE_BLUR_OFFSET; + int iterations = Config::Visuals::PAUSE_BLUR_ITERATIONS; + + // Base layer + SDL_SetTextureAlphaMod(m_renderTarget, Config::Visuals::PAUSE_BLUR_ALPHA); + SDL_RenderTexture(renderer, m_renderTarget, nullptr, &dst); + + // Accumulate offset layers + for (int i = 1; i <= iterations; ++i) { + float currentOffset = (float)(offset * i); + + SDL_FRect d1 = dst; d1.x -= currentOffset; d1.y -= currentOffset; + SDL_RenderTexture(renderer, m_renderTarget, nullptr, &d1); + + SDL_FRect d2 = dst; d2.x += currentOffset; d2.y -= currentOffset; + SDL_RenderTexture(renderer, m_renderTarget, nullptr, &d2); + + SDL_FRect d3 = dst; d3.x -= currentOffset; d3.y += currentOffset; + SDL_RenderTexture(renderer, m_renderTarget, nullptr, &d3); + + SDL_FRect d4 = dst; d4.x += currentOffset; d4.y += currentOffset; + SDL_RenderTexture(renderer, m_renderTarget, nullptr, &d4); + } + + SDL_SetTextureAlphaMod(m_renderTarget, 255); + + // Restore state + SDL_SetRenderViewport(renderer, &oldVP); + SDL_SetRenderScale(renderer, oldSX, oldSY); + + // Draw overlays + if (exitPopup) { + GameRenderer::renderExitPopup( + renderer, + ctx.pixelFont, + (float)winW, + (float)winH, + logicalScale, + (ctx.exitPopupSelectedButton ? *ctx.exitPopupSelectedButton : 1) + ); + } else { + GameRenderer::renderPauseOverlay( + renderer, + ctx.pixelFont, + (float)winW, + (float)winH, + logicalScale + ); + } + + } else { + // Render normally directly to screen + GameRenderer::renderPlayingState( + renderer, + ctx.game, + ctx.pixelFont, + ctx.lineEffect, + ctx.blocksTex, + 1200.0f, + 1000.0f, + logicalScale, + (float)winW, + (float)winH + ); + } } diff --git a/src/states/PlayingState.h b/src/states/PlayingState.h index f8b177e..002411d 100644 --- a/src/states/PlayingState.h +++ b/src/states/PlayingState.h @@ -14,4 +14,9 @@ public: private: // Local per-state variables if needed bool localPaused = false; + + // Render target for blur effect + SDL_Texture* m_renderTarget = nullptr; + int m_targetW = 0; + int m_targetH = 0; }; diff --git a/src/states/State.h b/src/states/State.h index a439c70..1834d48 100644 --- a/src/states/State.h +++ b/src/states/State.h @@ -50,6 +50,8 @@ struct StateContext { bool* showSettingsPopup = nullptr; bool* showExitConfirmPopup = nullptr; // If true, show "Exit game?" confirmation while playing int* exitPopupSelectedButton = nullptr; // 0 = YES, 1 = NO (default) + bool* gameplayCountdownActive = nullptr; // True if start-of-game countdown is running + bool* menuPlayCountdownArmed = nullptr; // True if we are transitioning to play and countdown is pending std::string* playerName = nullptr; // Shared player name buffer for highscores/options bool* fullscreenFlag = nullptr; // Tracks current fullscreen state when available std::function applyFullscreen; // Allows states to request fullscreen changes