upodate game start and blur on pause

This commit is contained in:
2025-11-23 09:40:00 +01:00
parent 9a035df452
commit 5bf87f2c21
8 changed files with 350 additions and 177 deletions

View File

@ -150,4 +150,11 @@ namespace Config {
constexpr int STARFIELD_PARTICLE_COUNT = 200; constexpr int STARFIELD_PARTICLE_COUNT = 200;
constexpr int STARFIELD_3D_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
}
} }

View File

@ -1107,9 +1107,7 @@ void ApplicationManager::setupStateHandlers() {
LOGICAL_H, LOGICAL_H,
logicalScale, logicalScale,
static_cast<float>(winW), static_cast<float>(winW),
static_cast<float>(winH), static_cast<float>(winH)
m_showExitConfirmPopup,
m_exitPopupSelectedButton
); );
// Reset viewport // Reset viewport

View File

@ -122,10 +122,7 @@ void GameRenderer::renderPlayingState(
float logicalH, float logicalH,
float logicalScale, float logicalScale,
float winW, float winW,
float winH, float winH
bool showExitConfirmPopup,
int exitPopupSelectedButton,
bool suppressPauseVisuals
) { ) {
if (!game || !pixelFont) return; 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) // Draw ghost piece (where current piece will land)
if (allowActivePieceRender) { if (allowActivePieceRender) {
@ -428,150 +425,177 @@ void GameRenderer::renderPlayingState(
drawSmallPiece(renderer, blocksTex, static_cast<PieceType>(game->held().type), statsX + 60, statsY + statsH - 80, finalBlockSize * 0.6f); drawSmallPiece(renderer, blocksTex, static_cast<PieceType>(game->held().type), statsX + 60, statsY + statsH - 80, finalBlockSize * 0.6f);
} }
// Pause overlay (skip when visuals are suppressed, e.g., countdown) // Pause overlay logic moved to renderPauseOverlay
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<float>(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);
}
// Switch to window coordinates for the full-screen overlay and text // Exit popup logic moved to renderExitPopup
SDL_Rect oldViewport; }
SDL_GetRenderViewport(renderer, &oldViewport);
float oldScaleX, oldScaleY; void GameRenderer::renderExitPopup(
SDL_GetRenderScale(renderer, &oldScaleX, &oldScaleY); SDL_Renderer* renderer,
FontAtlas* pixelFont,
SDL_SetRenderViewport(renderer, nullptr); float winW,
SDL_SetRenderScale(renderer, 1.0f, 1.0f); float winH,
float logicalScale,
// Draw full screen overlay int selectedButton
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180); ) {
SDL_FRect pauseOverlay{0, 0, winW, winH}; // Calculate content offsets (same as in renderPlayingState for consistency)
SDL_RenderFillRect(renderer, &pauseOverlay); // We need to re-calculate them or pass them in?
// The popup uses logical coordinates centered on screen.
// Draw centered text // Let's use the same logic as renderPauseOverlay (window coordinates) to be safe and consistent?
// Note: We multiply font scale by logicalScale to maintain consistent size // The original code used logical coordinates + contentOffset.
// since we reset the renderer scale to 1.0 // Let's stick to the original look but render it in window coordinates to ensure it covers everything properly.
const char* pausedText = "PAUSED";
float pausedScale = 2.0f * logicalScale; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
int pW = 0, pH = 0;
pixelFont->measure(pausedText, pausedScale, pW, pH); // Switch to window coordinates
pixelFont->draw(renderer, (winW - pW) * 0.5f, (winH - pH) * 0.5f - (20 * logicalScale), pausedText, pausedScale, {255, 255, 255, 255}); SDL_Rect oldViewport;
SDL_GetRenderViewport(renderer, &oldViewport);
const char* resumeText = "Press P to resume"; float oldScaleX, oldScaleY;
float resumeScale = 0.8f * logicalScale; SDL_GetRenderScale(renderer, &oldScaleX, &oldScaleY);
int rW = 0, rH = 0;
pixelFont->measure(resumeText, resumeScale, rW, rH); SDL_SetRenderViewport(renderer, nullptr);
pixelFont->draw(renderer, (winW - rW) * 0.5f, (winH - pH) * 0.5f + (40 * logicalScale), resumeText, resumeScale, {200, 200, 220, 255}); SDL_SetRenderScale(renderer, 1.0f, 1.0f);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); // Full screen dim
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 200);
// Restore previous render state SDL_FRect fullWin{0.f, 0.f, winW, winH};
SDL_SetRenderViewport(renderer, &oldViewport); SDL_RenderFillRect(renderer, &fullWin);
SDL_SetRenderScale(renderer, oldScaleX, oldScaleY);
} // Calculate panel position (centered in window)
// Original was logicalW based, let's map it to window size.
// Exit confirmation popup styled like other retro panels // Logical 640x320 scaled up.
if (showExitConfirmPopup) { float panelW = 640.0f * logicalScale;
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); float panelH = 320.0f * logicalScale;
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 200); float panelX = (winW - panelW) * 0.5f;
SDL_FRect fullWin{0.f, 0.f, winW, winH}; float panelY = (winH - panelH) * 0.5f;
SDL_RenderFillRect(renderer, &fullWin);
SDL_FRect panel{panelX, panelY, panelW, panelH};
const float panelW = 640.0f;
const float panelH = 320.0f; SDL_FRect shadow{panel.x + 6.0f * logicalScale, panel.y + 10.0f * logicalScale, panel.w, panel.h};
SDL_FRect panel{ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 140);
(logicalW - panelW) * 0.5f + contentOffsetX, SDL_RenderFillRect(renderer, &shadow);
(logicalH - panelH) * 0.5f + contentOffsetY,
panelW, for (int i = 0; i < 5; ++i) {
panelH 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_FRect shadow{panel.x + 6.0f, panel.y + 10.0f, panel.w, panel.h}; SDL_SetRenderDrawColor(renderer, 0, 180, 255, Uint8(44 - i * 7));
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 140); SDL_RenderRect(renderer, &glow);
SDL_RenderFillRect(renderer, &shadow); }
for (int i = 0; i < 5; ++i) { SDL_SetRenderDrawColor(renderer, 18, 30, 52, 255);
SDL_FRect glow{panel.x - float(i * 2), panel.y - float(i * 2), panel.w + float(i * 4), panel.h + float(i * 4)}; SDL_RenderFillRect(renderer, &panel);
SDL_SetRenderDrawColor(renderer, 0, 180, 255, Uint8(44 - i * 7)); SDL_SetRenderDrawColor(renderer, 70, 120, 210, 255);
SDL_RenderRect(renderer, &glow); 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, 18, 30, 52, 255); SDL_SetRenderDrawColor(renderer, 16, 24, 40, 235);
SDL_RenderFillRect(renderer, &panel); SDL_RenderFillRect(renderer, &inner);
SDL_SetRenderDrawColor(renderer, 70, 120, 210, 255); SDL_SetRenderDrawColor(renderer, 40, 80, 140, 235);
SDL_RenderRect(renderer, &panel); SDL_RenderRect(renderer, &inner);
SDL_FRect inner{panel.x + 24.0f, panel.y + 98.0f, panel.w - 48.0f, panel.h - 146.0f}; const std::string title = "EXIT GAME?";
SDL_SetRenderDrawColor(renderer, 16, 24, 40, 235); int titleW = 0, titleH = 0;
SDL_RenderFillRect(renderer, &inner); const float titleScale = 1.8f * logicalScale;
SDL_SetRenderDrawColor(renderer, 40, 80, 140, 235); pixelFont->measure(title, titleScale, titleW, titleH);
SDL_RenderRect(renderer, &inner); pixelFont->draw(renderer, panel.x + (panel.w - titleW) * 0.5f, panel.y + 30.0f * logicalScale, title, titleScale, {255, 230, 140, 255});
const std::string title = "EXIT GAME?"; std::array<std::string, 2> lines = {
int titleW = 0, titleH = 0; "Are you sure you want to quit?",
const float titleScale = 1.8f; "Current progress will be lost."
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}); float lineY = inner.y + 22.0f * logicalScale;
const float lineScale = 1.05f * logicalScale;
std::array<std::string, 2> lines = { for (const auto& line : lines) {
"Are you sure you want to quit?", int lineW = 0, lineH = 0;
"Current progress will be lost." pixelFont->measure(line, lineScale, lineW, lineH);
}; float textX = panel.x + (panel.w - lineW) * 0.5f;
float lineY = inner.y + 22.0f; pixelFont->draw(renderer, textX, lineY, line, lineScale, SDL_Color{210, 220, 240, 255});
const float lineScale = 1.05f; lineY += lineH + 10.0f * logicalScale;
for (const auto& line : lines) { }
int lineW = 0, lineH = 0;
pixelFont->measure(line, lineScale, lineW, lineH); const float horizontalPad = 28.0f * logicalScale;
float textX = panel.x + (panel.w - lineW) * 0.5f; const float buttonGap = 32.0f * logicalScale;
pixelFont->draw(renderer, textX, lineY, line, lineScale, SDL_Color{210, 220, 240, 255}); const float buttonH = 66.0f * logicalScale;
lineY += lineH + 10.0f; float buttonW = (inner.w - horizontalPad * 2.0f - buttonGap) * 0.5f;
} float buttonY = inner.y + inner.h - buttonH - 24.0f * logicalScale;
const float horizontalPad = 28.0f; auto drawButton = [&](int idx, float x, const char* label) {
const float buttonGap = 32.0f; bool selected = (selectedButton == idx);
const float buttonH = 66.0f; SDL_Color base = (idx == 0) ? SDL_Color{185, 70, 70, 255} : SDL_Color{60, 95, 150, 255};
float buttonW = (inner.w - horizontalPad * 2.0f - buttonGap) * 0.5f; 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;
float buttonY = inner.y + inner.h - buttonH - 24.0f; SDL_Color border = selected ? SDL_Color{255, 220, 120, 255} : SDL_Color{80, 110, 160, 255};
auto drawButton = [&](int idx, float x, const char* label) { SDL_FRect btn{x, buttonY, buttonW, buttonH};
bool selected = (exitPopupSelectedButton == idx); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 120);
SDL_Color base = (idx == 0) ? SDL_Color{185, 70, 70, 255} : SDL_Color{60, 95, 150, 255}; SDL_FRect btnShadow{btn.x + 4.0f * logicalScale, btn.y + 6.0f * logicalScale, btn.w, btn.h};
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_RenderFillRect(renderer, &btnShadow);
SDL_Color border = selected ? SDL_Color{255, 220, 120, 255} : SDL_Color{80, 110, 160, 255}; SDL_SetRenderDrawColor(renderer, body.r, body.g, body.b, body.a);
SDL_RenderFillRect(renderer, &btn);
SDL_FRect btn{x, buttonY, buttonW, buttonH}; SDL_SetRenderDrawColor(renderer, border.r, border.g, border.b, border.a);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 120); SDL_RenderRect(renderer, &btn);
SDL_FRect btnShadow{btn.x + 4.0f, btn.y + 6.0f, btn.w, btn.h};
SDL_RenderFillRect(renderer, &btnShadow); int textW = 0, textH = 0;
SDL_SetRenderDrawColor(renderer, body.r, body.g, body.b, body.a); const float labelScale = 1.4f * logicalScale;
SDL_RenderFillRect(renderer, &btn); pixelFont->measure(label, labelScale, textW, textH);
SDL_SetRenderDrawColor(renderer, border.r, border.g, border.b, border.a); float textX = btn.x + (btn.w - textW) * 0.5f;
SDL_RenderRect(renderer, &btn); 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};
int textW = 0, textH = 0; pixelFont->draw(renderer, textX, textY, label, labelScale, textColor);
const float labelScale = 1.4f; };
pixelFont->measure(label, labelScale, textW, textH);
float textX = btn.x + (btn.w - textW) * 0.5f; float yesX = inner.x + horizontalPad;
float textY = btn.y + (btn.h - textH) * 0.5f; float noX = yesX + buttonW + buttonGap;
SDL_Color textColor = selected ? SDL_Color{255, 255, 255, 255} : SDL_Color{230, 235, 250, 255}; drawButton(0, yesX, "YES");
pixelFont->draw(renderer, textX, textY, label, labelScale, textColor); drawButton(1, noX, "NO");
};
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
float yesX = inner.x + horizontalPad;
float noX = yesX + buttonW + buttonGap; // Restore previous render state
drawButton(0, yesX, "YES"); SDL_SetRenderViewport(renderer, &oldViewport);
drawButton(1, noX, "NO"); 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);
} }

View File

@ -25,10 +25,26 @@ public:
float logicalH, float logicalH,
float logicalScale, float logicalScale,
float winW, float winW,
float winH
);
// Render the pause overlay (full screen)
static void renderPauseOverlay(
SDL_Renderer* renderer,
FontAtlas* pixelFont,
float winW,
float winH, float winH,
bool showExitConfirmPopup, float logicalScale
int exitPopupSelectedButton = 1, // 0=YES, 1=NO );
bool suppressPauseVisuals = false
// Render the exit confirmation popup
static void renderExitPopup(
SDL_Renderer* renderer,
FontAtlas* pixelFont,
float winW,
float winH,
float logicalScale,
int selectedButton
); );
private: private:

View File

@ -729,7 +729,7 @@ int main(int, char **)
bool gameplayCountdownActive = false; bool gameplayCountdownActive = false;
double gameplayCountdownElapsed = 0.0; double gameplayCountdownElapsed = 0.0;
int gameplayCountdownIndex = 0; int gameplayCountdownIndex = 0;
const double GAMEPLAY_COUNTDOWN_STEP_MS = 600.0; const double GAMEPLAY_COUNTDOWN_STEP_MS = 400.0;
const std::array<const char*, 4> GAMEPLAY_COUNTDOWN_LABELS = { "3", "2", "1", "START" }; const std::array<const char*, 4> GAMEPLAY_COUNTDOWN_LABELS = { "3", "2", "1", "START" };
// Instantiate state manager // Instantiate state manager
@ -758,6 +758,8 @@ int main(int, char **)
ctx.showSettingsPopup = &showSettingsPopup; ctx.showSettingsPopup = &showSettingsPopup;
ctx.showExitConfirmPopup = &showExitConfirmPopup; ctx.showExitConfirmPopup = &showExitConfirmPopup;
ctx.exitPopupSelectedButton = &exitPopupSelectedButton; ctx.exitPopupSelectedButton = &exitPopupSelectedButton;
ctx.gameplayCountdownActive = &gameplayCountdownActive;
ctx.menuPlayCountdownArmed = &menuPlayCountdownArmed;
ctx.playerName = &playerName; ctx.playerName = &playerName;
ctx.fullscreenFlag = &isFullscreen; ctx.fullscreenFlag = &isFullscreen;
ctx.applyFullscreen = [window, &isFullscreen](bool enable) { ctx.applyFullscreen = [window, &isFullscreen](bool enable) {
@ -1505,21 +1507,7 @@ int main(int, char **)
} }
break; break;
case AppState::Playing: case AppState::Playing:
GameRenderer::renderPlayingState( playingState->render(renderer, logicalScale, logicalVP);
renderer,
&game,
&pixelFont,
&lineEffect,
blocksTex,
(float)LOGICAL_W,
(float)LOGICAL_H,
logicalScale,
(float)winW,
(float)winH,
showExitConfirmPopup,
exitPopupSelectedButton,
(gameplayCountdownActive || menuPlayCountdownArmed)
);
break; break;
case AppState::GameOver: case AppState::GameOver:
// Draw the game state in the background // Draw the game state in the background
@ -1533,8 +1521,7 @@ int main(int, char **)
(float)LOGICAL_H, (float)LOGICAL_H,
logicalScale, logicalScale,
(float)winW, (float)winW,
(float)winH, (float)winH
false // No exit popup in Game Over
); );
// Draw Game Over Overlay // Draw Game Over Overlay
@ -1662,9 +1649,10 @@ int main(int, char **)
SDL_SetRenderViewport(renderer, nullptr); SDL_SetRenderViewport(renderer, nullptr);
SDL_SetRenderScale(renderer, 1.f, 1.f); SDL_SetRenderScale(renderer, 1.f, 1.f);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 160); // Removed background overlay for cleaner countdown
SDL_FRect dimRect{0.f, 0.f, (float)winW, (float)winH}; // SDL_SetRenderDrawColor(renderer, 0, 0, 0, 160);
SDL_RenderFillRect(renderer, &dimRect); // SDL_FRect dimRect{0.f, 0.f, (float)winW, (float)winH};
// SDL_RenderFillRect(renderer, &dimRect);
SDL_SetRenderViewport(renderer, &logicalVP); SDL_SetRenderViewport(renderer, &logicalVP);
SDL_SetRenderScale(renderer, logicalScale, logicalScale); SDL_SetRenderScale(renderer, logicalScale, logicalScale);

View File

@ -4,6 +4,8 @@
#include "../gameplay/effects/LineEffect.h" #include "../gameplay/effects/LineEffect.h"
#include "../persistence/Scores.h" #include "../persistence/Scores.h"
#include "../audio/Audio.h" #include "../audio/Audio.h"
#include "../graphics/renderers/GameRenderer.h"
#include "../core/Config.h"
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
PlayingState::PlayingState(StateContext& ctx) : State(ctx) {} PlayingState::PlayingState(StateContext& ctx) : State(ctx) {}
@ -18,6 +20,10 @@ void PlayingState::onEnter() {
} }
void PlayingState::onExit() { void PlayingState::onExit() {
if (m_renderTarget) {
SDL_DestroyTexture(m_renderTarget);
m_renderTarget = nullptr;
}
} }
void PlayingState::handleEvent(const SDL_Event& e) { 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) { void PlayingState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) {
if (!ctx.game) return; 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
);
}
} }

View File

@ -14,4 +14,9 @@ public:
private: private:
// Local per-state variables if needed // Local per-state variables if needed
bool localPaused = false; bool localPaused = false;
// Render target for blur effect
SDL_Texture* m_renderTarget = nullptr;
int m_targetW = 0;
int m_targetH = 0;
}; };

View File

@ -50,6 +50,8 @@ struct StateContext {
bool* showSettingsPopup = nullptr; bool* showSettingsPopup = nullptr;
bool* showExitConfirmPopup = nullptr; // If true, show "Exit game?" confirmation while playing bool* showExitConfirmPopup = nullptr; // If true, show "Exit game?" confirmation while playing
int* exitPopupSelectedButton = nullptr; // 0 = YES, 1 = NO (default) 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 std::string* playerName = nullptr; // Shared player name buffer for highscores/options
bool* fullscreenFlag = nullptr; // Tracks current fullscreen state when available bool* fullscreenFlag = nullptr; // Tracks current fullscreen state when available
std::function<void(bool)> applyFullscreen; // Allows states to request fullscreen changes std::function<void(bool)> applyFullscreen; // Allows states to request fullscreen changes