diff --git a/src/main.cpp b/src/main.cpp index 3b20749..4ae8055 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -106,6 +106,105 @@ static SDL_Texture* loadTextureFromImage(SDL_Renderer* renderer, const std::stri return texture; } +struct LevelBackgroundFader { + SDL_Texture* currentTex = nullptr; + SDL_Texture* nextTex = nullptr; + int currentLevel = -1; + int queuedLevel = -1; + float fadeElapsedMs = 0.0f; + float fadeDurationMs = 3500.0f; +}; + +static void destroyTexture(SDL_Texture*& tex) { + if (tex) { + SDL_DestroyTexture(tex); + tex = nullptr; + } +} + +static bool queueLevelBackground(LevelBackgroundFader& fader, SDL_Renderer* renderer, int level) { + if (!renderer) { + return false; + } + + level = std::clamp(level, 0, 32); + if (fader.currentLevel == level || fader.queuedLevel == level) { + return true; + } + + char bgPath[256]; + std::snprintf(bgPath, sizeof(bgPath), "assets/images/tetris_main_back_level%d.jpg", level); + + SDL_Texture* newTexture = loadTextureFromImage(renderer, bgPath); + if (!newTexture) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to queue background for level %d: %s", level, bgPath); + return false; + } + + destroyTexture(fader.nextTex); + fader.nextTex = newTexture; + fader.queuedLevel = level; + fader.fadeElapsedMs = 0.0f; + + if (!fader.currentTex) { + fader.currentTex = fader.nextTex; + fader.currentLevel = fader.queuedLevel; + fader.nextTex = nullptr; + fader.queuedLevel = -1; + } + + return true; +} + +static void updateLevelBackgroundFade(LevelBackgroundFader& fader, float frameMs) { + if (!fader.currentTex || !fader.nextTex) { + return; + } + + fader.fadeElapsedMs += frameMs; + if (fader.fadeElapsedMs >= fader.fadeDurationMs) { + destroyTexture(fader.currentTex); + fader.currentTex = fader.nextTex; + fader.currentLevel = fader.queuedLevel; + fader.nextTex = nullptr; + fader.queuedLevel = -1; + fader.fadeElapsedMs = 0.0f; + } +} + +static void renderLevelBackgrounds(const LevelBackgroundFader& fader, SDL_Renderer* renderer, int winW, int winH) { + if (!renderer) { + return; + } + + SDL_FRect fullRect{0.f, 0.f, static_cast(winW), static_cast(winH)}; + + if (fader.currentTex && fader.nextTex) { + const float duration = std::max(1.0f, fader.fadeDurationMs); + const float alpha = std::clamp(fader.fadeElapsedMs / duration, 0.0f, 1.0f); + + SDL_SetTextureAlphaMod(fader.currentTex, Uint8((1.0f - alpha) * 255.0f)); + SDL_RenderTexture(renderer, fader.currentTex, nullptr, &fullRect); + SDL_SetTextureAlphaMod(fader.currentTex, 255); + + SDL_SetTextureAlphaMod(fader.nextTex, Uint8(alpha * 255.0f)); + SDL_RenderTexture(renderer, fader.nextTex, nullptr, &fullRect); + SDL_SetTextureAlphaMod(fader.nextTex, 255); + } else if (fader.currentTex) { + SDL_RenderTexture(renderer, fader.currentTex, nullptr, &fullRect); + } else if (fader.nextTex) { + SDL_RenderTexture(renderer, fader.nextTex, nullptr, &fullRect); + } +} + +static void resetLevelBackgrounds(LevelBackgroundFader& fader) { + destroyTexture(fader.currentTex); + destroyTexture(fader.nextTex); + fader.currentLevel = -1; + fader.queuedLevel = -1; + fader.fadeElapsedMs = 0.0f; +} + // Hover state for level popup ( -1 = none, 0..19 = hovered level ) // Now managed by LevelSelectorState @@ -501,12 +600,7 @@ int main(int, char **) // States should render using `ctx.backgroundTex` rather than accessing globals. // Level background caching system - SDL_Texture *levelBackgroundTex = nullptr; - SDL_Texture *nextLevelBackgroundTex = nullptr; // used during fade transitions - int cachedLevel = -1; // Track which level background is currently cached - float levelFadeAlpha = 0.0f; // 0..1 blend factor where 1 means next fully visible - const float LEVEL_FADE_DURATION = 3500.0f; // ms for fade transition (3.5s) - float levelFadeElapsed = 0.0f; + LevelBackgroundFader levelBackgrounds; // Default start level selection: 0 (declare here so it's in scope for all handlers) int startLevelSelection = 0; @@ -1180,10 +1274,7 @@ int main(int, char **) } // Advance level background fade if a next texture is queued - if (nextLevelBackgroundTex) { - levelFadeElapsed += float(frameMs); - levelFadeAlpha = std::min(1.0f, levelFadeElapsed / LEVEL_FADE_DURATION); - } + updateLevelBackgroundFade(levelBackgrounds, float(frameMs)); // Update intro animations if (state == AppState::Menu) { @@ -1276,58 +1367,9 @@ int main(int, char **) // Draw level-based background for gameplay, starfield for other states if (state == AppState::Playing) { - // Use level-based background for gameplay with caching - int currentLevel = game.level(); - int bgLevel = (currentLevel > 32) ? 32 : currentLevel; // Cap at level 32 - - // Only load new background if level changed - if (cachedLevel != bgLevel) { - // Load new level background into nextLevelBackgroundTex - if (nextLevelBackgroundTex) { SDL_DestroyTexture(nextLevelBackgroundTex); nextLevelBackgroundTex = nullptr; } - char bgPath[256]; - snprintf(bgPath, sizeof(bgPath), "assets/images/tetris_main_back_level%d.jpg", bgLevel); - SDL_Texture* newLevelTex = loadTextureFromImage(renderer, bgPath); - if (newLevelTex) { - SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loaded background for level %d: %s", bgLevel, bgPath); - nextLevelBackgroundTex = newLevelTex; - // start fade transition - levelFadeAlpha = 0.0f; - levelFadeElapsed = 0.0f; - cachedLevel = bgLevel; - } else { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load background for level %d: %s", bgLevel, bgPath); - // don't change textures if file missing - cachedLevel = -1; - } - } - - // Draw blended backgrounds if needed - if (levelBackgroundTex || nextLevelBackgroundTex) { - // Use actual window pixel size so backgrounds always cover full screen - SDL_FRect fullRect = { 0, 0, (float)winW, (float)winH }; - // if fade in progress - if (nextLevelBackgroundTex && levelFadeAlpha < 1.0f && levelBackgroundTex) { - // draw current with inverse alpha - SDL_SetTextureAlphaMod(levelBackgroundTex, Uint8((1.0f - levelFadeAlpha) * 255)); - SDL_RenderTexture(renderer, levelBackgroundTex, nullptr, &fullRect); - SDL_SetTextureAlphaMod(nextLevelBackgroundTex, Uint8(levelFadeAlpha * 255)); - SDL_RenderTexture(renderer, nextLevelBackgroundTex, nullptr, &fullRect); - // reset mods - SDL_SetTextureAlphaMod(levelBackgroundTex, 255); - SDL_SetTextureAlphaMod(nextLevelBackgroundTex, 255); - } - else if (nextLevelBackgroundTex && (!levelBackgroundTex || levelFadeAlpha >= 1.0f)) { - // finalise swap - if (levelBackgroundTex) { SDL_DestroyTexture(levelBackgroundTex); } - levelBackgroundTex = nextLevelBackgroundTex; - nextLevelBackgroundTex = nullptr; - levelFadeAlpha = 0.0f; - SDL_RenderTexture(renderer, levelBackgroundTex, nullptr, &fullRect); - } - else if (levelBackgroundTex) { - SDL_RenderTexture(renderer, levelBackgroundTex, nullptr, &fullRect); - } - } + int bgLevel = std::clamp(game.level(), 0, 32); + queueLevelBackground(levelBackgrounds, renderer, bgLevel); + renderLevelBackgrounds(levelBackgrounds, renderer, winW, winH); } else if (state == AppState::Loading) { // Use 3D starfield for loading screen (full screen) starfield3D.draw(renderer); @@ -1648,10 +1690,7 @@ int main(int, char **) SDL_DestroyTexture(logoTex); if (backgroundTex) SDL_DestroyTexture(backgroundTex); - if (nextLevelBackgroundTex) - SDL_DestroyTexture(nextLevelBackgroundTex); - if (levelBackgroundTex) - SDL_DestroyTexture(levelBackgroundTex); + resetLevelBackgrounds(levelBackgrounds); if (blocksTex) SDL_DestroyTexture(blocksTex); if (logoSmallTex) diff --git a/src/main_dist.cpp b/src/main_dist.cpp index 8391149..9bcec03 100644 --- a/src/main_dist.cpp +++ b/src/main_dist.cpp @@ -105,6 +105,105 @@ static SDL_Texture* loadTextureFromImage(SDL_Renderer* renderer, const std::stri return texture; } +struct LevelBackgroundFader { + SDL_Texture* currentTex = nullptr; + SDL_Texture* nextTex = nullptr; + int currentLevel = -1; + int queuedLevel = -1; + float fadeElapsedMs = 0.0f; + float fadeDurationMs = 3500.0f; +}; + +static void destroyTexture(SDL_Texture*& tex) { + if (tex) { + SDL_DestroyTexture(tex); + tex = nullptr; + } +} + +static bool queueLevelBackground(LevelBackgroundFader& fader, SDL_Renderer* renderer, int level) { + if (!renderer) { + return false; + } + + level = std::clamp(level, 0, 32); + if (fader.currentLevel == level || fader.queuedLevel == level) { + return true; + } + + char bgPath[256]; + std::snprintf(bgPath, sizeof(bgPath), "assets/images/tetris_main_back_level%d.jpg", level); + + SDL_Texture* newTexture = loadTextureFromImage(renderer, bgPath); + if (!newTexture) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to queue background for level %d: %s", level, bgPath); + return false; + } + + destroyTexture(fader.nextTex); + fader.nextTex = newTexture; + fader.queuedLevel = level; + fader.fadeElapsedMs = 0.0f; + + if (!fader.currentTex) { + fader.currentTex = fader.nextTex; + fader.currentLevel = fader.queuedLevel; + fader.nextTex = nullptr; + fader.queuedLevel = -1; + } + + return true; +} + +static void updateLevelBackgroundFade(LevelBackgroundFader& fader, float frameMs) { + if (!fader.currentTex || !fader.nextTex) { + return; + } + + fader.fadeElapsedMs += frameMs; + if (fader.fadeElapsedMs >= fader.fadeDurationMs) { + destroyTexture(fader.currentTex); + fader.currentTex = fader.nextTex; + fader.currentLevel = fader.queuedLevel; + fader.nextTex = nullptr; + fader.queuedLevel = -1; + fader.fadeElapsedMs = 0.0f; + } +} + +static void renderLevelBackgrounds(const LevelBackgroundFader& fader, SDL_Renderer* renderer, int winW, int winH) { + if (!renderer) { + return; + } + + SDL_FRect fullRect{0.f, 0.f, static_cast(winW), static_cast(winH)}; + + if (fader.currentTex && fader.nextTex) { + const float duration = std::max(1.0f, fader.fadeDurationMs); + const float alpha = std::clamp(fader.fadeElapsedMs / duration, 0.0f, 1.0f); + + SDL_SetTextureAlphaMod(fader.currentTex, Uint8((1.0f - alpha) * 255.0f)); + SDL_RenderTexture(renderer, fader.currentTex, nullptr, &fullRect); + SDL_SetTextureAlphaMod(fader.currentTex, 255); + + SDL_SetTextureAlphaMod(fader.nextTex, Uint8(alpha * 255.0f)); + SDL_RenderTexture(renderer, fader.nextTex, nullptr, &fullRect); + SDL_SetTextureAlphaMod(fader.nextTex, 255); + } else if (fader.currentTex) { + SDL_RenderTexture(renderer, fader.currentTex, nullptr, &fullRect); + } else if (fader.nextTex) { + SDL_RenderTexture(renderer, fader.nextTex, nullptr, &fullRect); + } +} + +static void resetLevelBackgrounds(LevelBackgroundFader& fader) { + destroyTexture(fader.currentTex); + destroyTexture(fader.nextTex); + fader.currentLevel = -1; + fader.queuedLevel = -1; + fader.fadeElapsedMs = 0.0f; +} + // Hover state for level popup ( -1 = none, 0..19 = hovered level ) // Now managed by LevelSelectorState @@ -495,12 +594,7 @@ int main(int, char **) // States should render using `ctx.backgroundTex` rather than accessing globals. // Level background caching system - SDL_Texture *levelBackgroundTex = nullptr; - SDL_Texture *nextLevelBackgroundTex = nullptr; // used during fade transitions - int cachedLevel = -1; // Track which level background is currently cached - float levelFadeAlpha = 0.0f; // 0..1 blend factor where 1 means next fully visible - const float LEVEL_FADE_DURATION = 3500.0f; // ms for fade transition (3.5s) - float levelFadeElapsed = 0.0f; + LevelBackgroundFader levelBackgrounds; // Default start level selection: 0 (declare here so it's in scope for all handlers) int startLevelSelection = 0; @@ -814,8 +908,7 @@ int main(int, char **) showExitConfirmPopup = true; exitPopupSelectedButton = 1; } - - // Settings button (gear icon area - top right) + SDL_FRect settingsBtn{LOGICAL_W - 60, 10, 50, 30}; if (lx >= settingsBtn.x && lx <= settingsBtn.x + settingsBtn.w && ly >= settingsBtn.y && ly <= settingsBtn.y + settingsBtn.h) { @@ -830,48 +923,6 @@ int main(int, char **) stateMgr.setState(state); } else if (state == AppState::Playing && showExitConfirmPopup) { - // Convert mouse to logical coordinates and to content-local coords - float lx = (mx - logicalVP.x) / logicalScale; - float ly = (my - logicalVP.y) / logicalScale; - // Compute content offsets (same as in render path) - float contentW = LOGICAL_W * logicalScale; - float contentH = LOGICAL_H * logicalScale; - float contentOffsetX = (winW - contentW) * 0.5f / logicalScale; - float contentOffsetY = (winH - contentH) * 0.5f / logicalScale; - // Map to content-local logical coords (what drawing code uses) - float localX = lx - contentOffsetX; - float localY = ly - contentOffsetY; - - // Popup rect in logical coordinates (content-local) - float popupW = 400, popupH = 200; - float popupX = (LOGICAL_W - popupW) / 2.0f; - float popupY = (LOGICAL_H - popupH) / 2.0f; - // Simple Yes/No buttons - float btnW = 120.0f, btnH = 40.0f; - float yesX = popupX + popupW * 0.25f - btnW / 2.0f; - float noX = popupX + popupW * 0.75f - btnW / 2.0f; - float btnY = popupY + popupH - btnH - 20.0f; - - if (localX >= popupX && localX <= popupX + popupW && localY >= popupY && localY <= popupY + popupH) { - // Click inside popup - check buttons - if (localX >= yesX && localX <= yesX + btnW && localY >= btnY && localY <= btnY + btnH) { - // Yes -> go back to menu - showExitConfirmPopup = false; - game.reset(startLevelSelection); - state = AppState::Menu; - stateMgr.setState(state); - } else if (localX >= noX && localX <= noX + btnW && localY >= btnY && localY <= btnY + btnH) { - // No -> close popup and resume - showExitConfirmPopup = false; - game.setPaused(false); - } - } else { - // Click outside popup: cancel - showExitConfirmPopup = false; - game.setPaused(false); - } - } - else if (state == AppState::Menu && showExitConfirmPopup) { float contentW = LOGICAL_W * logicalScale; float contentH = LOGICAL_H * logicalScale; float contentOffsetX = (winW - contentW) * 0.5f / logicalScale; @@ -1088,10 +1139,7 @@ int main(int, char **) } // Advance level background fade if a next texture is queued - if (nextLevelBackgroundTex) { - levelFadeElapsed += float(frameMs); - levelFadeAlpha = std::min(1.0f, levelFadeElapsed / LEVEL_FADE_DURATION); - } + updateLevelBackgroundFade(levelBackgrounds, float(frameMs)); // Update intro animations if (state == AppState::Menu) { @@ -1124,56 +1172,9 @@ int main(int, char **) // Draw level-based background for gameplay, starfield for other states if (state == AppState::Playing) { - // Use level-based background for gameplay with caching - int currentLevel = game.level(); - int bgLevel = (currentLevel > 32) ? 32 : currentLevel; // Cap at level 32 - - // Only load new background if level changed - if (cachedLevel != bgLevel) { - // Load new level background into nextLevelBackgroundTex - if (nextLevelBackgroundTex) { SDL_DestroyTexture(nextLevelBackgroundTex); nextLevelBackgroundTex = nullptr; } - char bgPath[256]; - snprintf(bgPath, sizeof(bgPath), "assets/images/tetris_main_back_level%d.jpg", bgLevel); - SDL_Texture* newLevelTex = loadTextureFromImage(renderer, bgPath); - if (newLevelTex) { - nextLevelBackgroundTex = newLevelTex; - // start fade transition - levelFadeAlpha = 0.0f; - levelFadeElapsed = 0.0f; - cachedLevel = bgLevel; - } else { - // don't change textures if file missing - cachedLevel = -1; - } - } - - // Draw blended backgrounds if needed - if (levelBackgroundTex || nextLevelBackgroundTex) { - // Use actual window pixel size so backgrounds always cover full screen - SDL_FRect fullRect = { 0, 0, (float)winW, (float)winH }; - // if fade in progress - if (nextLevelBackgroundTex && levelFadeAlpha < 1.0f && levelBackgroundTex) { - // draw current with inverse alpha - SDL_SetTextureAlphaMod(levelBackgroundTex, Uint8((1.0f - levelFadeAlpha) * 255)); - SDL_RenderTexture(renderer, levelBackgroundTex, nullptr, &fullRect); - SDL_SetTextureAlphaMod(nextLevelBackgroundTex, Uint8(levelFadeAlpha * 255)); - SDL_RenderTexture(renderer, nextLevelBackgroundTex, nullptr, &fullRect); - // reset mods - SDL_SetTextureAlphaMod(levelBackgroundTex, 255); - SDL_SetTextureAlphaMod(nextLevelBackgroundTex, 255); - } - else if (nextLevelBackgroundTex && (!levelBackgroundTex || levelFadeAlpha >= 1.0f)) { - // finalise swap - if (levelBackgroundTex) { SDL_DestroyTexture(levelBackgroundTex); } - levelBackgroundTex = nextLevelBackgroundTex; - nextLevelBackgroundTex = nullptr; - levelFadeAlpha = 0.0f; - SDL_RenderTexture(renderer, levelBackgroundTex, nullptr, &fullRect); - } - else if (levelBackgroundTex) { - SDL_RenderTexture(renderer, levelBackgroundTex, nullptr, &fullRect); - } - } + int bgLevel = std::clamp(game.level(), 0, 32); + queueLevelBackground(levelBackgrounds, renderer, bgLevel); + renderLevelBackgrounds(levelBackgrounds, renderer, winW, winH); } else if (state == AppState::Loading) { // Use 3D starfield for loading screen (full screen) starfield3D.draw(renderer); @@ -1714,10 +1715,7 @@ int main(int, char **) SDL_DestroyTexture(logoTex); if (backgroundTex) SDL_DestroyTexture(backgroundTex); - if (nextLevelBackgroundTex) - SDL_DestroyTexture(nextLevelBackgroundTex); - if (levelBackgroundTex) - SDL_DestroyTexture(levelBackgroundTex); + resetLevelBackgrounds(levelBackgrounds); if (blocksTex) SDL_DestroyTexture(blocksTex); if (logoSmallTex)