Fixed loader, main menu and level selector
This commit is contained in:
@ -137,11 +137,11 @@ add_executable(tetris_refactored
|
||||
src/audio/Audio.cpp
|
||||
src/gameplay/LineEffect.cpp
|
||||
src/audio/SoundEffect.cpp
|
||||
# State implementations (temporarily excluded - depend on main.cpp functions)
|
||||
# src/states/LoadingState.cpp
|
||||
# src/states/MenuState.cpp
|
||||
# src/states/LevelSelectorState.cpp
|
||||
# src/states/PlayingState.cpp
|
||||
# State implementations
|
||||
src/states/LoadingState.cpp
|
||||
src/states/MenuState.cpp
|
||||
src/states/LevelSelectorState.cpp
|
||||
src/states/PlayingState.cpp
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
|
||||
@ -1,16 +1,37 @@
|
||||
#include "ApplicationManager.h"
|
||||
#include "StateManager.h"
|
||||
#include "InputManager.h"
|
||||
#include <filesystem>
|
||||
#include "../audio/Audio.h"
|
||||
#include "../audio/SoundEffect.h"
|
||||
#include "../persistence/Scores.h"
|
||||
#include "../states/State.h"
|
||||
#include "../states/LoadingState.h"
|
||||
#include "../states/MenuState.h"
|
||||
#include "../states/LevelSelectorState.h"
|
||||
#include "../states/PlayingState.h"
|
||||
#include "AssetManager.h"
|
||||
#include "Config.h"
|
||||
#include "GlobalState.h"
|
||||
#include "../graphics/RenderManager.h"
|
||||
#include "../graphics/Font.h"
|
||||
#include "../graphics/Starfield3D.h"
|
||||
#include "../graphics/Starfield.h"
|
||||
#include "../gameplay/Game.h"
|
||||
#include "../gameplay/LineEffect.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
|
||||
ApplicationManager::ApplicationManager() = default;
|
||||
|
||||
static void traceFile(const char* msg) {
|
||||
std::ofstream f("tetris_trace.log", std::ios::app);
|
||||
if (f) f << msg << "\n";
|
||||
}
|
||||
|
||||
ApplicationManager::~ApplicationManager() {
|
||||
if (m_initialized) {
|
||||
shutdown();
|
||||
@ -64,8 +85,11 @@ void ApplicationManager::run() {
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Starting main application loop");
|
||||
traceFile("Main loop starting");
|
||||
|
||||
while (m_running) {
|
||||
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Main loop iteration start: m_running=%d", m_running ? 1 : 0);
|
||||
traceFile("Main loop iteration");
|
||||
// Calculate delta time
|
||||
uint64_t currentTime = SDL_GetTicks();
|
||||
float deltaTime = (currentTime - m_lastFrameTime) / 1000.0f;
|
||||
@ -81,6 +105,7 @@ void ApplicationManager::run() {
|
||||
|
||||
if (m_running) {
|
||||
update(deltaTime);
|
||||
traceFile("about to call render");
|
||||
render();
|
||||
}
|
||||
}
|
||||
@ -150,9 +175,66 @@ bool ApplicationManager::initializeManagers() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure SoundEffectManager is initialized early so SFX loads work
|
||||
SoundEffectManager::instance().init();
|
||||
|
||||
// Create StateManager (will be enhanced in next steps)
|
||||
m_stateManager = std::make_unique<StateManager>(AppState::Loading);
|
||||
|
||||
// Create and initialize starfields
|
||||
m_starfield3D = std::make_unique<Starfield3D>();
|
||||
m_starfield3D->init(Config::Logical::WIDTH, Config::Logical::HEIGHT, 200);
|
||||
|
||||
m_starfield = std::make_unique<Starfield>();
|
||||
m_starfield->init(Config::Logical::WIDTH, Config::Logical::HEIGHT, 50);
|
||||
|
||||
// Register InputManager handlers to forward events to StateManager so
|
||||
// state-specific event handlers receive SDL_Event objects just like main.cpp.
|
||||
if (m_inputManager && m_stateManager) {
|
||||
m_inputManager->registerKeyHandler([this](SDL_Scancode sc, bool pressed){
|
||||
if (!m_stateManager) return;
|
||||
SDL_Event ev{};
|
||||
ev.type = pressed ? SDL_EVENT_KEY_DOWN : SDL_EVENT_KEY_UP;
|
||||
ev.key.scancode = sc;
|
||||
ev.key.repeat = 0;
|
||||
m_stateManager->handleEvent(ev);
|
||||
});
|
||||
|
||||
m_inputManager->registerMouseButtonHandler([this](int button, bool pressed, float x, float y){
|
||||
if (!m_stateManager) return;
|
||||
SDL_Event ev{};
|
||||
ev.type = pressed ? SDL_EVENT_MOUSE_BUTTON_DOWN : SDL_EVENT_MOUSE_BUTTON_UP;
|
||||
ev.button.button = button;
|
||||
ev.button.x = int(x);
|
||||
ev.button.y = int(y);
|
||||
m_stateManager->handleEvent(ev);
|
||||
});
|
||||
|
||||
m_inputManager->registerMouseMotionHandler([this](float x, float y, float dx, float dy){
|
||||
if (!m_stateManager) return;
|
||||
SDL_Event ev{};
|
||||
ev.type = SDL_EVENT_MOUSE_MOTION;
|
||||
ev.motion.x = int(x);
|
||||
ev.motion.y = int(y);
|
||||
ev.motion.xrel = int(dx);
|
||||
ev.motion.yrel = int(dy);
|
||||
m_stateManager->handleEvent(ev);
|
||||
});
|
||||
|
||||
m_inputManager->registerWindowEventHandler([this](const SDL_WindowEvent& we){
|
||||
if (!m_stateManager) return;
|
||||
SDL_Event ev{};
|
||||
ev.type = SDL_EVENT_WINDOW_RESIZED; // generic mapping; handlers can inspect inner fields
|
||||
ev.window = we;
|
||||
m_stateManager->handleEvent(ev);
|
||||
});
|
||||
|
||||
m_inputManager->registerQuitHandler([this](){
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "InputManager quit handler invoked");
|
||||
SDL_Event ev{}; ev.type = SDL_EVENT_QUIT; if (m_stateManager) m_stateManager->handleEvent(ev);
|
||||
});
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Managers initialized successfully");
|
||||
return true;
|
||||
}
|
||||
@ -167,8 +249,17 @@ bool ApplicationManager::initializeGame() {
|
||||
AssetManager::LoadingTask blocksTask{AssetManager::LoadingTask::TEXTURE, "blocks", Config::Assets::BLOCKS_BMP};
|
||||
AssetManager::LoadingTask fontTask{AssetManager::LoadingTask::FONT, "main_font", Config::Fonts::DEFAULT_FONT_PATH, Config::Fonts::DEFAULT_FONT_SIZE};
|
||||
AssetManager::LoadingTask pixelFontTask{AssetManager::LoadingTask::FONT, "pixel_font", Config::Fonts::PIXEL_FONT_PATH, Config::Fonts::PIXEL_FONT_SIZE};
|
||||
|
||||
// Add tasks to AssetManager
|
||||
|
||||
// Pre-load the pixel (retro) font synchronously so the loading screen can render text immediately
|
||||
if (!m_assetManager->getFont("pixel_font")) {
|
||||
if (m_assetManager->loadFont("pixel_font", Config::Fonts::PIXEL_FONT_PATH, Config::Fonts::PIXEL_FONT_SIZE)) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Preloaded pixel_font for loading screen");
|
||||
} else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Failed to preload pixel_font; loading screen will fallback to main_font");
|
||||
}
|
||||
}
|
||||
|
||||
// Add tasks to AssetManager (pixel font task will be skipped if already loaded)
|
||||
m_assetManager->addLoadingTask(logoTask);
|
||||
m_assetManager->addLoadingTask(backgroundTask);
|
||||
m_assetManager->addLoadingTask(blocksTask);
|
||||
@ -180,7 +271,7 @@ bool ApplicationManager::initializeGame() {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Asset loading progress: %.1f%%", progress * 100.0f);
|
||||
});
|
||||
|
||||
// Load sound effects with fallback
|
||||
// Load sound effects with fallback (SoundEffectManager already initialized)
|
||||
m_assetManager->loadSoundEffectWithFallback("clear_line", "clear_line");
|
||||
m_assetManager->loadSoundEffectWithFallback("nice_combo", "nice_combo");
|
||||
m_assetManager->loadSoundEffectWithFallback("amazing", "amazing");
|
||||
@ -188,35 +279,559 @@ bool ApplicationManager::initializeGame() {
|
||||
// Start background music loading
|
||||
m_assetManager->startBackgroundMusicLoading();
|
||||
|
||||
// Initialize a basic test render handler that shows loaded assets
|
||||
m_stateManager->registerRenderHandler(AppState::Loading,
|
||||
[this](RenderManager& renderer) {
|
||||
// Simple test render - just clear screen and show loaded assets
|
||||
renderer.clear(20, 30, 40, 255);
|
||||
|
||||
// Try to render background if loaded
|
||||
SDL_Texture* background = m_assetManager->getTexture("background");
|
||||
if (background) {
|
||||
SDL_FRect bgRect = { 0, 0, Config::Logical::WIDTH, Config::Logical::HEIGHT };
|
||||
renderer.renderTexture(background, nullptr, &bgRect);
|
||||
}
|
||||
|
||||
// Try to render logo if loaded
|
||||
SDL_Texture* logo = m_assetManager->getTexture("logo");
|
||||
if (logo) {
|
||||
SDL_FRect logoRect = { 300, 200, 600, 200 };
|
||||
renderer.renderTexture(logo, nullptr, &logoRect);
|
||||
}
|
||||
|
||||
// Show asset loading status
|
||||
SDL_FRect statusRect = { 50, 50, 400, 30 };
|
||||
renderer.renderRect(statusRect, 0, 100, 200, 200);
|
||||
// Create and populate shared StateContext similar to main.cpp so states like MenuState
|
||||
// receive the same pointers and flags they expect.
|
||||
// Create ScoreManager and load scores
|
||||
m_scoreManager = std::make_unique<ScoreManager>();
|
||||
if (m_scoreManager) m_scoreManager->load();
|
||||
|
||||
// Create gameplay and line effect objects to populate StateContext like main.cpp
|
||||
m_lineEffect = std::make_unique<LineEffect>();
|
||||
m_game = std::make_unique<Game>(m_startLevelSelection);
|
||||
// Wire up sound callbacks as main.cpp did
|
||||
if (m_game) {
|
||||
m_game->setSoundCallback([&](int linesCleared){
|
||||
SoundEffectManager::instance().playSound("clear_line", 1.0f);
|
||||
// voice lines handled via asset manager loaded sounds
|
||||
if (linesCleared == 2) SoundEffectManager::instance().playRandomSound({"nice_combo"}, 1.0f);
|
||||
else if (linesCleared == 3) SoundEffectManager::instance().playRandomSound({"great_move"}, 1.0f);
|
||||
else if (linesCleared == 4) SoundEffectManager::instance().playRandomSound({"amazing"}, 1.0f);
|
||||
});
|
||||
m_game->setLevelUpCallback([&](int newLevel){
|
||||
SoundEffectManager::instance().playSound("lets_go", 1.0f);
|
||||
});
|
||||
}
|
||||
|
||||
// Prepare a StateContext-like struct by setting up handlers that capture
|
||||
// pointers and flags. State objects in this refactor expect these to be
|
||||
// available via StateManager event/update/render hooks, so we'll store them
|
||||
// as lambdas that reference members here.
|
||||
|
||||
// Start background music loading similar to main.cpp: Audio init + file discovery
|
||||
Audio::instance().init();
|
||||
// Discover available tracks (up to 100) and queue for background loading
|
||||
m_totalTracks = 0;
|
||||
for (int i = 1; i <= 100; ++i) {
|
||||
char buf[128];
|
||||
std::snprintf(buf, sizeof(buf), "assets/music/music%03d.mp3", i);
|
||||
// Use simple file existence check via std::filesystem
|
||||
if (std::filesystem::exists(buf)) {
|
||||
Audio::instance().addTrackAsync(buf);
|
||||
++m_totalTracks;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m_totalTracks > 0) {
|
||||
Audio::instance().startBackgroundLoading();
|
||||
m_currentTrackLoading = 1; // mark started
|
||||
}
|
||||
|
||||
// Instantiate state objects and populate a StateContext similar to main.cpp
|
||||
// so that existing state classes (MenuState, LoadingState, etc.) receive
|
||||
// the resources they expect.
|
||||
{
|
||||
m_stateContext.stateManager = m_stateManager.get();
|
||||
m_stateContext.game = m_game.get();
|
||||
m_stateContext.scores = m_scoreManager.get();
|
||||
m_stateContext.starfield = m_starfield.get();
|
||||
m_stateContext.starfield3D = m_starfield3D.get();
|
||||
m_stateContext.font = (FontAtlas*)m_assetManager->getFont("main_font");
|
||||
m_stateContext.pixelFont = (FontAtlas*)m_assetManager->getFont("pixel_font");
|
||||
m_stateContext.lineEffect = m_lineEffect.get();
|
||||
m_stateContext.logoTex = m_assetManager->getTexture("logo");
|
||||
// Attempt to load a small logo variant if present to match original UX
|
||||
SDL_Texture* logoSmall = m_assetManager->getTexture("logo_small");
|
||||
if (!logoSmall) {
|
||||
// Try to load image from disk and register with AssetManager
|
||||
if (m_assetManager->loadTexture("logo_small", "assets/images/logo_small.bmp")) {
|
||||
logoSmall = m_assetManager->getTexture("logo_small");
|
||||
}
|
||||
}
|
||||
m_stateContext.logoSmallTex = logoSmall;
|
||||
if (logoSmall) {
|
||||
int w = 0, h = 0; if (m_renderManager) m_renderManager->getTextureSize(logoSmall, w, h);
|
||||
m_stateContext.logoSmallW = w; m_stateContext.logoSmallH = h;
|
||||
} else { m_stateContext.logoSmallW = 0; m_stateContext.logoSmallH = 0; }
|
||||
m_stateContext.backgroundTex = m_assetManager->getTexture("background");
|
||||
m_stateContext.blocksTex = m_assetManager->getTexture("blocks");
|
||||
m_stateContext.musicEnabled = &m_musicEnabled;
|
||||
m_stateContext.startLevelSelection = &m_startLevelSelection;
|
||||
m_stateContext.hoveredButton = &m_hoveredButton;
|
||||
m_stateContext.showSettingsPopup = &m_showSettingsPopup;
|
||||
m_stateContext.showExitConfirmPopup = &m_showExitConfirmPopup;
|
||||
|
||||
// Create state instances
|
||||
m_loadingState = std::make_unique<LoadingState>(m_stateContext);
|
||||
m_menuState = std::make_unique<MenuState>(m_stateContext);
|
||||
m_levelSelectorState = std::make_unique<LevelSelectorState>(m_stateContext);
|
||||
m_playingState = std::make_unique<PlayingState>(m_stateContext);
|
||||
|
||||
// Register handlers that forward to these state objects
|
||||
if (m_stateManager) {
|
||||
m_stateManager->registerEventHandler(AppState::Loading, [this](const SDL_Event& e){ if (m_loadingState) m_loadingState->handleEvent(e); });
|
||||
m_stateManager->registerOnEnter(AppState::Loading, [this](){ if (m_loadingState) m_loadingState->onEnter(); });
|
||||
m_stateManager->registerOnExit(AppState::Loading, [this](){ if (m_loadingState) m_loadingState->onExit(); });
|
||||
|
||||
m_stateManager->registerEventHandler(AppState::Menu, [this](const SDL_Event& e){ if (m_menuState) m_menuState->handleEvent(e); });
|
||||
m_stateManager->registerOnEnter(AppState::Menu, [this](){ if (m_menuState) m_menuState->onEnter(); });
|
||||
m_stateManager->registerOnExit(AppState::Menu, [this](){ if (m_menuState) m_menuState->onExit(); });
|
||||
|
||||
m_stateManager->registerEventHandler(AppState::LevelSelector, [this](const SDL_Event& e){ if (m_levelSelectorState) m_levelSelectorState->handleEvent(e); });
|
||||
m_stateManager->registerOnEnter(AppState::LevelSelector, [this](){ if (m_levelSelectorState) m_levelSelectorState->onEnter(); });
|
||||
m_stateManager->registerOnExit(AppState::LevelSelector, [this](){ if (m_levelSelectorState) m_levelSelectorState->onExit(); });
|
||||
|
||||
m_stateManager->registerEventHandler(AppState::Playing, [this](const SDL_Event& e){ if (m_playingState) m_playingState->handleEvent(e); });
|
||||
m_stateManager->registerOnEnter(AppState::Playing, [this](){ if (m_playingState) m_playingState->onEnter(); });
|
||||
m_stateManager->registerOnExit(AppState::Playing, [this](){ if (m_playingState) m_playingState->onExit(); });
|
||||
}
|
||||
}
|
||||
|
||||
// Finally call setupStateHandlers for inline visuals and additional hooks
|
||||
setupStateHandlers();
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Game systems initialized with asset loading");
|
||||
return true;
|
||||
}
|
||||
|
||||
void ApplicationManager::setupStateHandlers() {
|
||||
// Helper function for drawing rectangles
|
||||
auto drawRect = [](SDL_Renderer* renderer, float x, float y, float w, float h, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
|
||||
SDL_SetRenderDrawColor(renderer, r, g, b, a);
|
||||
SDL_FRect rect = { x, y, w, h };
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
};
|
||||
|
||||
// Helper function for drawing menu buttons with enhanced styling
|
||||
auto drawEnhancedButton = [drawRect](SDL_Renderer* renderer, FontAtlas* font, float cx, float cy, float w, float h,
|
||||
const std::string& label, bool isHovered, bool isSelected) {
|
||||
float x = cx - w/2;
|
||||
float y = cy - h/2;
|
||||
|
||||
// Button styling based on state
|
||||
SDL_Color bgColor = isSelected ? SDL_Color{100, 150, 255, 255} :
|
||||
isHovered ? SDL_Color{80, 120, 200, 255} :
|
||||
SDL_Color{60, 90, 160, 255};
|
||||
|
||||
// Draw border and background
|
||||
drawRect(renderer, x-2, y-2, w+4, h+4, 60, 80, 140, 255); // Border
|
||||
drawRect(renderer, x, y, w, h, bgColor.r, bgColor.g, bgColor.b, bgColor.a); // Background
|
||||
|
||||
// Draw text if font is available
|
||||
if (font) {
|
||||
float textScale = 1.8f;
|
||||
float approxCharW = 12.0f * textScale;
|
||||
float textW = label.length() * approxCharW;
|
||||
float textX = x + (w - textW) / 2.0f;
|
||||
float textY = y + (h - 20.0f * textScale) / 2.0f;
|
||||
|
||||
// Draw shadow
|
||||
font->draw(renderer, textX + 2, textY + 2, label, textScale, {0, 0, 0, 180});
|
||||
// Draw main text
|
||||
font->draw(renderer, textX, textY, label, textScale, {255, 255, 255, 255});
|
||||
}
|
||||
};
|
||||
|
||||
// Loading State Handlers (matching original main.cpp implementation)
|
||||
m_stateManager->registerRenderHandler(AppState::Loading,
|
||||
[this, drawRect](RenderManager& renderer) {
|
||||
// Clear background first
|
||||
renderer.clear(0, 0, 0, 255);
|
||||
|
||||
// Use 3D starfield for loading screen (full screen)
|
||||
// Ensure starfield uses actual window size so center and projection are correct
|
||||
if (m_starfield3D) {
|
||||
int winW_actual = 0, winH_actual = 0;
|
||||
if (m_renderManager) {
|
||||
m_renderManager->getWindowSize(winW_actual, winH_actual);
|
||||
}
|
||||
if (winW_actual > 0 && winH_actual > 0) {
|
||||
m_starfield3D->resize(winW_actual, winH_actual);
|
||||
}
|
||||
m_starfield3D->draw(renderer.getSDLRenderer());
|
||||
}
|
||||
|
||||
// Set viewport and scaling for content
|
||||
int winW = Config::Window::DEFAULT_WIDTH;
|
||||
int winH = Config::Window::DEFAULT_HEIGHT;
|
||||
int LOGICAL_W = Config::Logical::WIDTH;
|
||||
int LOGICAL_H = Config::Logical::HEIGHT;
|
||||
|
||||
// Calculate logical scaling and viewport
|
||||
float scaleX = static_cast<float>(winW) / LOGICAL_W;
|
||||
float scaleY = static_cast<float>(winH) / LOGICAL_H;
|
||||
float logicalScale = std::min(scaleX, scaleY);
|
||||
|
||||
int vpW = static_cast<int>(LOGICAL_W * logicalScale);
|
||||
int vpH = static_cast<int>(LOGICAL_H * logicalScale);
|
||||
int vpX = (winW - vpW) / 2;
|
||||
int vpY = (winH - vpH) / 2;
|
||||
|
||||
SDL_Rect logicalVP = { vpX, vpY, vpW, vpH };
|
||||
SDL_SetRenderViewport(renderer.getSDLRenderer(), &logicalVP);
|
||||
SDL_SetRenderScale(renderer.getSDLRenderer(), logicalScale, logicalScale);
|
||||
|
||||
// Calculate actual content area (centered within the window)
|
||||
float contentScale = logicalScale;
|
||||
float contentW = LOGICAL_W * contentScale;
|
||||
float contentH = LOGICAL_H * contentScale;
|
||||
float contentOffsetX = (winW - contentW) * 0.5f / contentScale;
|
||||
float contentOffsetY = (winH - contentH) * 0.5f / contentScale;
|
||||
|
||||
auto drawRectOriginal = [&](float x, float y, float w, float h, SDL_Color c) {
|
||||
SDL_SetRenderDrawColor(renderer.getSDLRenderer(), c.r, c.g, c.b, c.a);
|
||||
SDL_FRect fr{x + contentOffsetX, y + contentOffsetY, w, h};
|
||||
SDL_RenderFillRect(renderer.getSDLRenderer(), &fr);
|
||||
};
|
||||
|
||||
// Calculate dimensions for perfect centering (like JavaScript version)
|
||||
const bool isLimitedHeight = LOGICAL_H < 450;
|
||||
SDL_Texture* logoTex = m_assetManager->getTexture("logo");
|
||||
const float logoHeight = logoTex ? (isLimitedHeight ? LOGICAL_H * 0.25f : LOGICAL_H * 0.4f) : 0;
|
||||
const float loadingTextHeight = 20; // Height of "LOADING" text (match JS)
|
||||
const float barHeight = 20; // Loading bar height (match JS)
|
||||
const float barPaddingVertical = isLimitedHeight ? 15 : 35;
|
||||
const float percentTextHeight = 24; // Height of percentage text
|
||||
const float spacingBetweenElements = isLimitedHeight ? 5 : 15;
|
||||
|
||||
// Total content height
|
||||
const float totalContentHeight = logoHeight +
|
||||
(logoHeight > 0 ? spacingBetweenElements : 0) +
|
||||
loadingTextHeight +
|
||||
barPaddingVertical +
|
||||
barHeight +
|
||||
spacingBetweenElements +
|
||||
percentTextHeight;
|
||||
|
||||
// Start Y position for perfect vertical centering
|
||||
float currentY = (LOGICAL_H - totalContentHeight) / 2.0f;
|
||||
|
||||
// Draw logo (centered, static like JavaScript version)
|
||||
if (logoTex) {
|
||||
// Use the same original large logo dimensions as JS (we used a half-size BMP previously)
|
||||
const int lw = 872, lh = 273;
|
||||
|
||||
// Cap logo width similar to JS UI.MAX_LOGO_WIDTH (600) and available screen space
|
||||
const float maxLogoWidth = std::min(LOGICAL_W * 0.9f, 600.0f);
|
||||
const float availableHeight = isLimitedHeight ? LOGICAL_H * 0.25f : LOGICAL_H * 0.4f;
|
||||
const float availableWidth = maxLogoWidth;
|
||||
|
||||
const float scaleFactorWidth = availableWidth / static_cast<float>(lw);
|
||||
const float scaleFactorHeight = availableHeight / static_cast<float>(lh);
|
||||
const float scaleFactor = std::min(scaleFactorWidth, scaleFactorHeight);
|
||||
|
||||
const float displayWidth = lw * scaleFactor;
|
||||
const float displayHeight = lh * scaleFactor;
|
||||
const float logoX = (LOGICAL_W - displayWidth) / 2.0f;
|
||||
|
||||
SDL_FRect dst{logoX + contentOffsetX, currentY + contentOffsetY, displayWidth, displayHeight};
|
||||
SDL_RenderTexture(renderer.getSDLRenderer(), logoTex, nullptr, &dst);
|
||||
|
||||
currentY += displayHeight + spacingBetweenElements;
|
||||
}
|
||||
|
||||
// Draw "LOADING" text (centered, using pixel font with fallback to main_font)
|
||||
FontAtlas* pixelFont = (FontAtlas*)m_assetManager->getFont("pixel_font");
|
||||
FontAtlas* fallbackFont = (FontAtlas*)m_assetManager->getFont("main_font");
|
||||
FontAtlas* loadingFont = pixelFont ? pixelFont : fallbackFont;
|
||||
if (loadingFont) {
|
||||
const char* loadingText = "LOADING";
|
||||
float textWidth = strlen(loadingText) * 12.0f; // Approximate width for pixel font
|
||||
float textX = (LOGICAL_W - textWidth) / 2.0f;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Rendering LOADING text at (%f,%f)", textX + contentOffsetX, currentY + contentOffsetY);
|
||||
loadingFont->draw(renderer.getSDLRenderer(), textX + contentOffsetX, currentY + contentOffsetY, loadingText, 1.0f, {255, 204, 0, 255});
|
||||
} else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "No loading font available to render LOADING text");
|
||||
}
|
||||
|
||||
currentY += loadingTextHeight + barPaddingVertical;
|
||||
|
||||
// Draw loading bar (like JavaScript version)
|
||||
const int barW = 400, barH = 20;
|
||||
const int bx = (LOGICAL_W - barW) / 2;
|
||||
|
||||
float loadingProgress = m_assetManager->getLoadingProgress();
|
||||
|
||||
// Bar border (dark gray) - using drawRect which adds content offset
|
||||
drawRectOriginal(bx - 3, currentY - 3, barW + 6, barH + 6, {68, 68, 80, 255});
|
||||
|
||||
// Bar background (darker gray)
|
||||
drawRectOriginal(bx, currentY, barW, barH, {34, 34, 34, 255});
|
||||
|
||||
// Progress bar (gold color)
|
||||
drawRectOriginal(bx, currentY, int(barW * loadingProgress), barH, {255, 204, 0, 255});
|
||||
|
||||
currentY += barH + spacingBetweenElements;
|
||||
|
||||
// Draw percentage text (centered, using loadingFont)
|
||||
if (loadingFont) {
|
||||
int percentage = int(loadingProgress * 100);
|
||||
char percentText[16];
|
||||
std::snprintf(percentText, sizeof(percentText), "%d%%", percentage);
|
||||
|
||||
float percentWidth = strlen(percentText) * 12.0f; // Approximate width for pixel font
|
||||
float percentX = (LOGICAL_W - percentWidth) / 2.0f;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Rendering percent text '%s' at (%f,%f)", percentText, percentX + contentOffsetX, currentY + contentOffsetY);
|
||||
loadingFont->draw(renderer.getSDLRenderer(), percentX + contentOffsetX, currentY + contentOffsetY, percentText, 1.5f, {255, 204, 0, 255});
|
||||
}
|
||||
|
||||
// Reset viewport and scale
|
||||
SDL_SetRenderViewport(renderer.getSDLRenderer(), nullptr);
|
||||
SDL_SetRenderScale(renderer.getSDLRenderer(), 1.0f, 1.0f);
|
||||
});
|
||||
|
||||
m_stateManager->registerUpdateHandler(AppState::Loading,
|
||||
[this](float deltaTime) {
|
||||
// Update 3D starfield so stars move during loading
|
||||
if (m_starfield3D) {
|
||||
m_starfield3D->update(deltaTime);
|
||||
}
|
||||
|
||||
// Check if loading is complete and transition to menu
|
||||
if (m_assetManager->isLoadingComplete()) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loading complete, transitioning to Menu");
|
||||
bool ok = m_stateManager->setState(AppState::Menu);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "setState(AppState::Menu) returned %d", ok ? 1 : 0);
|
||||
traceFile("- to Menu returned");
|
||||
}
|
||||
});
|
||||
|
||||
// Menu State render: draw background full-screen, then delegate to MenuState::render
|
||||
m_stateManager->registerRenderHandler(AppState::Menu,
|
||||
[this](RenderManager& renderer) {
|
||||
// Clear and draw background to full window
|
||||
renderer.clear(0, 0, 20, 255);
|
||||
int winW = 0, winH = 0;
|
||||
if (m_renderManager) m_renderManager->getWindowSize(winW, winH);
|
||||
SDL_Texture* background = m_assetManager->getTexture("background");
|
||||
if (background && winW > 0 && winH > 0) {
|
||||
SDL_FRect bgRect = { 0, 0, (float)winW, (float)winH };
|
||||
renderer.renderTexture(background, nullptr, &bgRect);
|
||||
}
|
||||
|
||||
// Compute logical scale and viewport
|
||||
const int LOGICAL_W = Config::Logical::WIDTH;
|
||||
const int LOGICAL_H = Config::Logical::HEIGHT;
|
||||
float scaleX = winW > 0 ? (float)winW / LOGICAL_W : 1.0f;
|
||||
float scaleY = winH > 0 ? (float)winH / LOGICAL_H : 1.0f;
|
||||
float logicalScale = std::min(scaleX, scaleY);
|
||||
int vpW = (int)(LOGICAL_W * logicalScale);
|
||||
int vpH = (int)(LOGICAL_H * logicalScale);
|
||||
int vpX = (winW - vpW) / 2;
|
||||
int vpY = (winH - vpH) / 2;
|
||||
SDL_Rect logicalVP{vpX, vpY, vpW, vpH};
|
||||
|
||||
// Apply viewport+scale then call MenuState::render (shows highscores, fireworks, bottom buttons)
|
||||
SDL_SetRenderViewport(renderer.getSDLRenderer(), &logicalVP);
|
||||
SDL_SetRenderScale(renderer.getSDLRenderer(), logicalScale, logicalScale);
|
||||
if (m_menuState) {
|
||||
m_menuState->render(renderer.getSDLRenderer(), logicalScale, logicalVP);
|
||||
}
|
||||
// Reset to defaults
|
||||
SDL_SetRenderViewport(renderer.getSDLRenderer(), nullptr);
|
||||
SDL_SetRenderScale(renderer.getSDLRenderer(), 1.0f, 1.0f);
|
||||
});
|
||||
|
||||
// LevelSelector State render: draw background full-screen, then delegate to LevelSelectorState::render
|
||||
m_stateManager->registerRenderHandler(AppState::LevelSelector,
|
||||
[this](RenderManager& renderer) {
|
||||
// Clear and draw background to full window
|
||||
renderer.clear(0, 0, 20, 255);
|
||||
int winW = 0, winH = 0;
|
||||
if (m_renderManager) m_renderManager->getWindowSize(winW, winH);
|
||||
SDL_Texture* background = m_assetManager->getTexture("background");
|
||||
if (background && winW > 0 && winH > 0) {
|
||||
SDL_FRect bgRect = { 0, 0, (float)winW, (float)winH };
|
||||
renderer.renderTexture(background, nullptr, &bgRect);
|
||||
}
|
||||
|
||||
// Compute logical scale and viewport
|
||||
const int LOGICAL_W = Config::Logical::WIDTH;
|
||||
const int LOGICAL_H = Config::Logical::HEIGHT;
|
||||
float scaleX = winW > 0 ? (float)winW / LOGICAL_W : 1.0f;
|
||||
float scaleY = winH > 0 ? (float)winH / LOGICAL_H : 1.0f;
|
||||
float logicalScale = std::min(scaleX, scaleY);
|
||||
int vpW = (int)(LOGICAL_W * logicalScale);
|
||||
int vpH = (int)(LOGICAL_H * logicalScale);
|
||||
int vpX = (winW - vpW) / 2;
|
||||
int vpY = (winH - vpH) / 2;
|
||||
SDL_Rect logicalVP{vpX, vpY, vpW, vpH};
|
||||
|
||||
// Apply viewport+scale then call LevelSelectorState::render (shows level selection popup)
|
||||
SDL_SetRenderViewport(renderer.getSDLRenderer(), &logicalVP);
|
||||
SDL_SetRenderScale(renderer.getSDLRenderer(), logicalScale, logicalScale);
|
||||
if (m_levelSelectorState) {
|
||||
m_levelSelectorState->render(renderer.getSDLRenderer(), logicalScale, logicalVP);
|
||||
}
|
||||
// Reset to defaults
|
||||
SDL_SetRenderViewport(renderer.getSDLRenderer(), nullptr);
|
||||
SDL_SetRenderScale(renderer.getSDLRenderer(), 1.0f, 1.0f);
|
||||
});
|
||||
|
||||
m_stateManager->registerUpdateHandler(AppState::Menu,
|
||||
[this](float deltaTime) {
|
||||
// Update logo animation counter
|
||||
m_logoAnimCounter += deltaTime;
|
||||
|
||||
// Update fireworks effect
|
||||
GlobalState& globalState = GlobalState::instance();
|
||||
globalState.updateFireworks(deltaTime);
|
||||
|
||||
// Start background music once tracks are available and not yet started
|
||||
if (m_musicEnabled && !m_musicStarted) {
|
||||
if (Audio::instance().getLoadedTrackCount() > 0) {
|
||||
Audio::instance().shuffle();
|
||||
Audio::instance().start();
|
||||
m_musicStarted = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
m_stateManager->registerEventHandler(AppState::Menu,
|
||||
[this](const SDL_Event& event) {
|
||||
// Forward keyboard events (Enter/Escape) to trigger actions, match original main.cpp
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
|
||||
if (event.key.scancode == SDL_SCANCODE_RETURN || event.key.scancode == SDL_SCANCODE_RETURN2 || event.key.scancode == SDL_SCANCODE_KP_ENTER) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Starting game from menu (Enter)");
|
||||
// Reset start level and transition
|
||||
// In the original main, game.reset(...) was called; here we only switch state.
|
||||
m_stateManager->setState(AppState::Playing);
|
||||
return;
|
||||
}
|
||||
if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
|
||||
// If an exit confirmation is already showing, accept it and quit.
|
||||
if (m_showExitConfirmPopup) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Quitting from menu (Escape confirmed)");
|
||||
m_running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, show the exit confirmation popup instead of quitting immediately.
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Showing exit confirmation (Escape)");
|
||||
m_showExitConfirmPopup = true;
|
||||
return;
|
||||
}
|
||||
// Global toggles
|
||||
if (event.key.scancode == SDL_SCANCODE_M) {
|
||||
Audio::instance().toggleMute();
|
||||
m_musicEnabled = !m_musicEnabled;
|
||||
}
|
||||
if (event.key.scancode == SDL_SCANCODE_S) {
|
||||
SoundEffectManager::instance().setEnabled(!SoundEffectManager::instance().isEnabled());
|
||||
}
|
||||
if (event.key.scancode == SDL_SCANCODE_N) {
|
||||
SoundEffectManager::instance().playSound("lets_go", 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse handling: map SDL mouse coords into logical content coords and
|
||||
// perform hit-tests for menu buttons similar to main.cpp.
|
||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
|
||||
float mx = (float)event.button.x;
|
||||
float my = (float)event.button.y;
|
||||
int winW = 0, winH = 0;
|
||||
if (m_renderManager) m_renderManager->getWindowSize(winW, winH);
|
||||
float logicalScale = std::min(winW / (float)Config::Logical::WIDTH, winH / (float)Config::Logical::HEIGHT);
|
||||
if (logicalScale <= 0) logicalScale = 1.0f;
|
||||
SDL_Rect logicalVP{0,0,winW,winH};
|
||||
// Check bounds and compute content-local coords
|
||||
if (mx >= logicalVP.x && my >= logicalVP.y && mx <= logicalVP.x + logicalVP.w && my <= logicalVP.y + logicalVP.h) {
|
||||
float lx = (mx - logicalVP.x) / logicalScale;
|
||||
float ly = (my - logicalVP.y) / logicalScale;
|
||||
|
||||
// Respect settings popup
|
||||
if (m_showSettingsPopup) {
|
||||
m_showSettingsPopup = false;
|
||||
} else {
|
||||
bool isSmall = ((Config::Logical::WIDTH * logicalScale) < 700.0f);
|
||||
float btnW = isSmall ? (Config::Logical::WIDTH * 0.4f) : 300.0f;
|
||||
float btnH = isSmall ? 60.0f : 70.0f;
|
||||
float btnCX = Config::Logical::WIDTH * 0.5f;
|
||||
const float btnYOffset = 40.0f;
|
||||
float btnCY = Config::Logical::HEIGHT * 0.86f + btnYOffset;
|
||||
SDL_FRect playBtn{btnCX - btnW * 0.6f - btnW/2.0f, btnCY - btnH/2.0f, btnW, btnH};
|
||||
SDL_FRect levelBtn{btnCX + btnW * 0.6f - btnW/2.0f, btnCY - btnH/2.0f, btnW, btnH};
|
||||
|
||||
if (lx >= playBtn.x && lx <= playBtn.x + playBtn.w && ly >= playBtn.y && ly <= playBtn.y + playBtn.h) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Menu: Play button clicked");
|
||||
m_stateManager->setState(AppState::Playing);
|
||||
} else if (lx >= levelBtn.x && lx <= levelBtn.x + levelBtn.w && ly >= levelBtn.y && ly <= levelBtn.y + levelBtn.h) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Menu: Level button clicked");
|
||||
m_stateManager->setState(AppState::LevelSelector);
|
||||
} else {
|
||||
// Settings area detection (top-right small area)
|
||||
SDL_FRect settingsBtn{Config::Logical::WIDTH - 60, 10, 50, 30};
|
||||
if (lx >= settingsBtn.x && lx <= settingsBtn.x + settingsBtn.w && ly >= settingsBtn.y && ly <= settingsBtn.y + settingsBtn.h) {
|
||||
m_showSettingsPopup = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse motion handling for hover
|
||||
if (event.type == SDL_EVENT_MOUSE_MOTION) {
|
||||
float mx = (float)event.motion.x;
|
||||
float my = (float)event.motion.y;
|
||||
int winW = 0, winH = 0;
|
||||
if (m_renderManager) m_renderManager->getWindowSize(winW, winH);
|
||||
float logicalScale = std::min(winW / (float)Config::Logical::WIDTH, winH / (float)Config::Logical::HEIGHT);
|
||||
if (logicalScale <= 0) logicalScale = 1.0f;
|
||||
SDL_Rect logicalVP{0,0,winW,winH};
|
||||
if (mx >= logicalVP.x && my >= logicalVP.y && mx <= logicalVP.x + logicalVP.w && my <= logicalVP.y + logicalVP.h) {
|
||||
float lx = (mx - logicalVP.x) / logicalScale;
|
||||
float ly = (my - logicalVP.y) / logicalScale;
|
||||
if (!m_showSettingsPopup) {
|
||||
bool isSmall = ((Config::Logical::WIDTH * logicalScale) < 700.0f);
|
||||
float btnW = isSmall ? (Config::Logical::WIDTH * 0.4f) : 300.0f;
|
||||
float btnH = isSmall ? 60.0f : 70.0f;
|
||||
float btnCX = Config::Logical::WIDTH * 0.5f;
|
||||
const float btnYOffset = 40.0f;
|
||||
float btnCY = Config::Logical::HEIGHT * 0.86f + btnYOffset;
|
||||
SDL_FRect playBtn{btnCX - btnW * 0.6f - btnW/2.0f, btnCY - btnH/2.0f, btnW, btnH};
|
||||
SDL_FRect levelBtn{btnCX + btnW * 0.6f - btnW/2.0f, btnCY - btnH/2.0f, btnW, btnH};
|
||||
m_hoveredButton = -1;
|
||||
if (lx >= playBtn.x && lx <= playBtn.x + playBtn.w && ly >= playBtn.y && ly <= playBtn.y + playBtn.h) {
|
||||
m_hoveredButton = 0;
|
||||
} else if (lx >= levelBtn.x && lx <= levelBtn.x + levelBtn.w && ly >= levelBtn.y && ly <= levelBtn.y + levelBtn.h) {
|
||||
m_hoveredButton = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Playing State - Placeholder for now
|
||||
m_stateManager->registerRenderHandler(AppState::Playing,
|
||||
[this](RenderManager& renderer) {
|
||||
renderer.clear(0, 0, 0, 255);
|
||||
|
||||
// For now, just show a placeholder
|
||||
FontAtlas* font = (FontAtlas*)m_assetManager->getFont("main_font");
|
||||
if (font) {
|
||||
float centerX = Config::Window::DEFAULT_WIDTH / 2.0f;
|
||||
float centerY = Config::Window::DEFAULT_HEIGHT / 2.0f;
|
||||
std::string playingText = "TETRIS GAME PLAYING STATE";
|
||||
float textX = centerX - (playingText.length() * 12.0f) / 2.0f;
|
||||
font->draw(renderer.getSDLRenderer(), textX, centerY, playingText, 2.0f, {255, 255, 255, 255});
|
||||
|
||||
std::string instruction = "Press ESC to return to menu";
|
||||
float instrX = centerX - (instruction.length() * 8.0f) / 2.0f;
|
||||
font->draw(renderer.getSDLRenderer(), instrX, centerY + 60, instruction, 1.0f, {200, 200, 200, 255});
|
||||
}
|
||||
});
|
||||
|
||||
m_stateManager->registerEventHandler(AppState::Playing,
|
||||
[this](const SDL_Event& event) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN) {
|
||||
if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Returning to menu from playing state");
|
||||
m_stateManager->setState(AppState::Menu);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ApplicationManager::processEvents() {
|
||||
// Let InputManager process all SDL events
|
||||
if (m_inputManager) {
|
||||
@ -224,6 +839,7 @@ void ApplicationManager::processEvents() {
|
||||
|
||||
// Check if InputManager detected a quit request
|
||||
if (m_inputManager->shouldQuit()) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "InputManager reports shouldQuit() == true — requesting shutdown");
|
||||
requestShutdown();
|
||||
return;
|
||||
}
|
||||
@ -234,14 +850,26 @@ void ApplicationManager::processEvents() {
|
||||
}
|
||||
|
||||
void ApplicationManager::update(float deltaTime) {
|
||||
// Update AssetManager for progressive loading
|
||||
if (m_assetManager) {
|
||||
m_assetManager->update(deltaTime);
|
||||
}
|
||||
|
||||
// Update InputManager
|
||||
if (m_inputManager) {
|
||||
m_inputManager->update(deltaTime);
|
||||
}
|
||||
|
||||
// Always update 3D starfield so background animates even during loading/menu
|
||||
if (m_starfield3D) {
|
||||
m_starfield3D->update(deltaTime);
|
||||
}
|
||||
|
||||
// Update StateManager
|
||||
if (m_stateManager) {
|
||||
m_stateManager->update(deltaTime);
|
||||
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager::update - state update completed for state %s", m_stateManager->getStateName(m_stateManager->getState()));
|
||||
traceFile("update completed");
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,15 +877,21 @@ void ApplicationManager::render() {
|
||||
if (!m_renderManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager::render - about to begin frame");
|
||||
// Trace render begin
|
||||
traceFile("render begin");
|
||||
m_renderManager->beginFrame();
|
||||
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager::render - beginFrame complete");
|
||||
|
||||
// Delegate rendering to StateManager
|
||||
if (m_stateManager) {
|
||||
m_stateManager->render(*m_renderManager);
|
||||
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager::render - state render completed for state %s", m_stateManager->getStateName(m_stateManager->getState()));
|
||||
}
|
||||
|
||||
m_renderManager->endFrame();
|
||||
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager::render - endFrame complete");
|
||||
traceFile("render endFrame complete");
|
||||
}
|
||||
|
||||
void ApplicationManager::cleanupManagers() {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Config.h"
|
||||
#include "../states/State.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@ -16,6 +17,12 @@ class Starfield3D;
|
||||
class FontAtlas;
|
||||
class LineEffect;
|
||||
|
||||
// Forward declare state classes (top-level, defined under src/states)
|
||||
class LoadingState;
|
||||
class MenuState;
|
||||
class LevelSelectorState;
|
||||
class PlayingState;
|
||||
|
||||
/**
|
||||
* ApplicationManager - Central coordinator for the entire application lifecycle
|
||||
*
|
||||
@ -50,6 +57,7 @@ private:
|
||||
bool initializeSDL();
|
||||
bool initializeManagers();
|
||||
bool initializeGame();
|
||||
void setupStateHandlers();
|
||||
|
||||
// Main loop methods
|
||||
void processEvents();
|
||||
@ -66,6 +74,37 @@ private:
|
||||
std::unique_ptr<AssetManager> m_assetManager;
|
||||
std::unique_ptr<StateManager> m_stateManager;
|
||||
|
||||
// Visual effects
|
||||
std::unique_ptr<Starfield3D> m_starfield3D;
|
||||
std::unique_ptr<Starfield> m_starfield;
|
||||
|
||||
// Menu / UI state pieces mirrored from main.cpp
|
||||
bool m_musicEnabled = true;
|
||||
int m_startLevelSelection = 0;
|
||||
int m_hoveredButton = -1;
|
||||
bool m_showSettingsPopup = false;
|
||||
bool m_showExitConfirmPopup = false;
|
||||
uint64_t m_loadStartTicks = 0;
|
||||
bool m_musicStarted = false;
|
||||
bool m_musicLoaded = false;
|
||||
int m_currentTrackLoading = 0;
|
||||
int m_totalTracks = 0;
|
||||
|
||||
// Persistence (ScoreManager declared at top-level)
|
||||
std::unique_ptr<ScoreManager> m_scoreManager;
|
||||
// Gameplay pieces
|
||||
std::unique_ptr<Game> m_game;
|
||||
std::unique_ptr<LineEffect> m_lineEffect;
|
||||
|
||||
|
||||
// State context (must be a member to ensure lifetime)
|
||||
StateContext m_stateContext;
|
||||
|
||||
// State objects (mirror main.cpp pattern)
|
||||
std::unique_ptr<LoadingState> m_loadingState;
|
||||
std::unique_ptr<MenuState> m_menuState;
|
||||
std::unique_ptr<LevelSelectorState> m_levelSelectorState;
|
||||
std::unique_ptr<PlayingState> m_playingState;
|
||||
// Application state
|
||||
bool m_running = false;
|
||||
bool m_initialized = false;
|
||||
@ -77,4 +116,7 @@ private:
|
||||
int m_windowWidth = Config::Window::DEFAULT_WIDTH;
|
||||
int m_windowHeight = Config::Window::DEFAULT_HEIGHT;
|
||||
std::string m_windowTitle = Config::Window::DEFAULT_TITLE;
|
||||
|
||||
// Animation state
|
||||
float m_logoAnimCounter = 0.0f;
|
||||
};
|
||||
|
||||
@ -10,6 +10,9 @@ AssetManager::AssetManager()
|
||||
: m_renderer(nullptr)
|
||||
, m_audioSystem(nullptr)
|
||||
, m_soundSystem(nullptr)
|
||||
, m_totalLoadingTasks(0)
|
||||
, m_completedLoadingTasks(0)
|
||||
, m_loadingComplete(false)
|
||||
, m_defaultTexturePath("assets/images/")
|
||||
, m_defaultFontPath("assets/fonts/")
|
||||
, m_initialized(false) {
|
||||
@ -260,49 +263,87 @@ void AssetManager::addLoadingTask(const LoadingTask& task) {
|
||||
|
||||
void AssetManager::executeLoadingTasks(std::function<void(float)> progressCallback) {
|
||||
if (m_loadingTasks.empty()) {
|
||||
m_loadingComplete = true;
|
||||
if (progressCallback) progressCallback(1.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
logInfo("Executing " + std::to_string(m_loadingTasks.size()) + " loading tasks...");
|
||||
logInfo("Starting progressive loading of " + std::to_string(m_loadingTasks.size()) + " loading tasks...");
|
||||
|
||||
size_t totalTasks = m_loadingTasks.size();
|
||||
size_t completedTasks = 0;
|
||||
m_totalLoadingTasks = m_loadingTasks.size();
|
||||
m_completedLoadingTasks = 0;
|
||||
m_currentTaskIndex = 0;
|
||||
m_loadingComplete = false;
|
||||
m_isProgressiveLoading = true;
|
||||
m_lastLoadTime = SDL_GetTicks();
|
||||
m_musicLoadingStarted = false;
|
||||
m_musicLoadingProgress = 0.0f;
|
||||
|
||||
// Don't execute tasks immediately - let update() handle them progressively
|
||||
}
|
||||
|
||||
for (const auto& task : m_loadingTasks) {
|
||||
void AssetManager::update(float deltaTime) {
|
||||
if (!m_isProgressiveLoading || m_loadingTasks.empty()) {
|
||||
// Handle music loading progress simulation if assets are done
|
||||
if (m_musicLoadingStarted && !m_loadingComplete) {
|
||||
m_musicLoadingProgress += deltaTime * 0.4f; // Simulate music loading progress
|
||||
if (m_musicLoadingProgress >= 1.0f) {
|
||||
m_musicLoadingProgress = 1.0f;
|
||||
m_loadingComplete = true;
|
||||
logInfo("Background music loading simulation complete");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Uint64 currentTime = SDL_GetTicks();
|
||||
|
||||
// Add minimum delay between loading items (600ms per item for visual effect)
|
||||
if (currentTime - m_lastLoadTime < 600) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load one item at a time
|
||||
if (m_currentTaskIndex < m_loadingTasks.size()) {
|
||||
const auto& task = m_loadingTasks[m_currentTaskIndex];
|
||||
|
||||
bool success = false;
|
||||
|
||||
switch (task.type) {
|
||||
case LoadingTask::TEXTURE:
|
||||
success = (loadTexture(task.id, task.filepath) != nullptr);
|
||||
break;
|
||||
|
||||
case LoadingTask::FONT:
|
||||
success = loadFont(task.id, task.filepath, task.fontSize);
|
||||
break;
|
||||
|
||||
case LoadingTask::MUSIC:
|
||||
success = loadMusicTrack(task.filepath);
|
||||
break;
|
||||
|
||||
case LoadingTask::SOUND_EFFECT:
|
||||
success = loadSoundEffect(task.id, task.filepath);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (!success) {
|
||||
logError("Failed to load asset: " + task.id + " (" + task.filepath + ")");
|
||||
}
|
||||
|
||||
completedTasks++;
|
||||
|
||||
if (progressCallback) {
|
||||
float progress = static_cast<float>(completedTasks) / static_cast<float>(totalTasks);
|
||||
progressCallback(progress);
|
||||
m_currentTaskIndex++;
|
||||
m_completedLoadingTasks = m_currentTaskIndex;
|
||||
m_lastLoadTime = currentTime;
|
||||
|
||||
logInfo("Asset loading progress: " + std::to_string((float)m_completedLoadingTasks / m_totalLoadingTasks * 100.0f) + "%");
|
||||
|
||||
// Check if all asset tasks are complete
|
||||
if (m_currentTaskIndex >= m_loadingTasks.size()) {
|
||||
m_isProgressiveLoading = false;
|
||||
logInfo("Completed " + std::to_string(m_completedLoadingTasks) + "/" + std::to_string(m_totalLoadingTasks) + " loading tasks");
|
||||
|
||||
// Start background music loading simulation
|
||||
m_musicLoadingStarted = true;
|
||||
m_musicLoadingProgress = 0.0f;
|
||||
startBackgroundMusicLoading();
|
||||
}
|
||||
}
|
||||
|
||||
logInfo("Completed " + std::to_string(completedTasks) + "/" + std::to_string(totalTasks) + " loading tasks");
|
||||
}
|
||||
|
||||
void AssetManager::clearLoadingTasks() {
|
||||
@ -382,3 +423,23 @@ void AssetManager::logInfo(const std::string& message) const {
|
||||
void AssetManager::logError(const std::string& message) const {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[AssetManager] %s", message.c_str());
|
||||
}
|
||||
|
||||
// Loading progress tracking methods
|
||||
bool AssetManager::isLoadingComplete() const {
|
||||
// Loading is complete when both asset tasks and music loading are done
|
||||
return m_loadingComplete && (!m_musicLoadingStarted || m_musicLoadingProgress >= 1.0f);
|
||||
}
|
||||
|
||||
float AssetManager::getLoadingProgress() const {
|
||||
if (m_totalLoadingTasks == 0) {
|
||||
return 1.0f; // No tasks = complete
|
||||
}
|
||||
|
||||
// Asset loading progress (80% of total progress)
|
||||
float assetProgress = static_cast<float>(m_completedLoadingTasks) / static_cast<float>(m_totalLoadingTasks) * 0.8f;
|
||||
|
||||
// Music loading progress (20% of total progress)
|
||||
float musicProgress = m_musicLoadingStarted ? m_musicLoadingProgress * 0.2f : 0.0f;
|
||||
|
||||
return assetProgress + musicProgress;
|
||||
}
|
||||
|
||||
@ -69,6 +69,13 @@ public:
|
||||
void addLoadingTask(const LoadingTask& task);
|
||||
void executeLoadingTasks(std::function<void(float)> progressCallback = nullptr);
|
||||
void clearLoadingTasks();
|
||||
void update(float deltaTime); // New: Progressive loading update
|
||||
|
||||
// Loading progress tracking
|
||||
bool isLoadingComplete() const;
|
||||
float getLoadingProgress() const;
|
||||
size_t getTotalLoadingTasks() const { return m_totalLoadingTasks; }
|
||||
size_t getCompletedLoadingTasks() const { return m_completedLoadingTasks; }
|
||||
|
||||
// Resource queries
|
||||
size_t getTextureCount() const { return m_textures.size(); }
|
||||
@ -88,6 +95,18 @@ private:
|
||||
std::unordered_map<std::string, SDL_Texture*> m_textures;
|
||||
std::unordered_map<std::string, std::unique_ptr<FontAtlas>> m_fonts;
|
||||
std::vector<LoadingTask> m_loadingTasks;
|
||||
|
||||
// Loading progress tracking
|
||||
size_t m_totalLoadingTasks = 0;
|
||||
size_t m_completedLoadingTasks = 0;
|
||||
bool m_loadingComplete = false;
|
||||
|
||||
// Progressive loading state
|
||||
bool m_isProgressiveLoading = false;
|
||||
size_t m_currentTaskIndex = 0;
|
||||
Uint64 m_lastLoadTime = 0;
|
||||
bool m_musicLoadingStarted = false;
|
||||
float m_musicLoadingProgress = 0.0f;
|
||||
|
||||
// System references
|
||||
SDL_Renderer* m_renderer;
|
||||
|
||||
@ -16,11 +16,17 @@ void InputManager::processEvents() {
|
||||
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
// Trace every polled event type for debugging abrupt termination
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "InputManager: polled event type=%d\n", (int)event.type); fclose(f); }
|
||||
}
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_QUIT:
|
||||
m_shouldQuit = true;
|
||||
for (auto& handler : m_quitHandlers) {
|
||||
for (auto& handler : m_quitHandlers) {
|
||||
try {
|
||||
// Trace quit event handling
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "InputManager: SDL_EVENT_QUIT polled\n"); fclose(f); }
|
||||
handler();
|
||||
} catch (const std::exception& e) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Exception in quit handler: %s", e.what());
|
||||
|
||||
@ -84,6 +84,10 @@ bool StateManager::setState(AppState newState) {
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "State transition: %s -> %s",
|
||||
getStateName(m_currentState), getStateName(newState));
|
||||
// Persistent trace for debugging abrupt exits
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "setState start %s -> %s\n", getStateName(m_currentState), getStateName(newState)); fclose(f); }
|
||||
}
|
||||
|
||||
// Execute exit hooks for current state
|
||||
executeExitHooks(m_currentState);
|
||||
@ -95,6 +99,11 @@ bool StateManager::setState(AppState newState) {
|
||||
// Execute enter hooks for new state
|
||||
executeEnterHooks(m_currentState);
|
||||
|
||||
// Trace completion
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "setState end %s\n", getStateName(m_currentState)); fclose(f); }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -175,13 +184,20 @@ void StateManager::executeEnterHooks(AppState state) {
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = 0;
|
||||
for (auto& hook : it->second) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Executing enter hook %d for state %s", idx, getStateName(state));
|
||||
// Also write to trace file for persistent record
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "executeEnterHook %d %s\n", idx, getStateName(state)); fclose(f); }
|
||||
}
|
||||
try {
|
||||
hook();
|
||||
} catch (const std::exception& e) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Exception in enter hook for state %s: %s",
|
||||
getStateName(state), e.what());
|
||||
}
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,12 +207,18 @@ void StateManager::executeExitHooks(AppState state) {
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = 0;
|
||||
for (auto& hook : it->second) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Executing exit hook %d for state %s", idx, getStateName(state));
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "executeExitHook %d %s\n", idx, getStateName(state)); fclose(f); }
|
||||
}
|
||||
try {
|
||||
hook();
|
||||
} catch (const std::exception& e) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Exception in exit hook for state %s: %s",
|
||||
getStateName(state), e.what());
|
||||
}
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,16 +82,35 @@ void RenderManager::beginFrame() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the screen
|
||||
// Trace beginFrame entry
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::beginFrame entry\n"); fclose(f); }
|
||||
}
|
||||
|
||||
// Clear the screen (wrapped with trace)
|
||||
clear(12, 12, 16, 255); // Dark background similar to original
|
||||
|
||||
// Trace after clear
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::beginFrame after clear\n"); fclose(f); }
|
||||
}
|
||||
}
|
||||
|
||||
void RenderManager::endFrame() {
|
||||
if (!m_initialized || !m_renderer) {
|
||||
return;
|
||||
}
|
||||
// Trace before present
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::endFrame before present\n"); fclose(f); }
|
||||
}
|
||||
|
||||
SDL_RenderPresent(m_renderer);
|
||||
|
||||
// Trace after present
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::endFrame after present\n"); fclose(f); }
|
||||
}
|
||||
}
|
||||
|
||||
void RenderManager::setLogicalSize(int width, int height) {
|
||||
@ -146,7 +165,14 @@ void RenderManager::renderTexture(SDL_Texture* texture, const SDL_FRect* src, co
|
||||
return;
|
||||
}
|
||||
|
||||
// Trace renderTexture usage
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::renderTexture entry tex=%llu src=%p dst=%p\n", (unsigned long long)(uintptr_t)texture, (void*)src, (void*)dst); fclose(f); }
|
||||
}
|
||||
SDL_RenderTexture(m_renderer, texture, src, dst);
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::renderTexture after SDL_RenderTexture tex=%llu\n", (unsigned long long)(uintptr_t)texture); fclose(f); }
|
||||
}
|
||||
}
|
||||
|
||||
void RenderManager::renderTexture(SDL_Texture* texture, float x, float y) {
|
||||
@ -230,6 +256,15 @@ void RenderManager::getWindowSize(int& width, int& height) const {
|
||||
height = m_windowHeight;
|
||||
}
|
||||
|
||||
void RenderManager::getTextureSize(SDL_Texture* tex, int& w, int& h) const {
|
||||
if (!tex) { w = 0; h = 0; return; }
|
||||
// SDL3 provides SDL_GetTextureSize which accepts float or int pointers depending on overloads
|
||||
float fw = 0.0f, fh = 0.0f;
|
||||
SDL_GetTextureSize(tex, &fw, &fh);
|
||||
w = int(fw + 0.5f);
|
||||
h = int(fh + 0.5f);
|
||||
}
|
||||
|
||||
void RenderManager::updateLogicalScale() {
|
||||
if (!m_initialized || !m_renderer) {
|
||||
return;
|
||||
|
||||
@ -52,6 +52,8 @@ public:
|
||||
// Direct access to SDL objects (temporary, will be removed later)
|
||||
SDL_Renderer* getSDLRenderer() const { return m_renderer; }
|
||||
SDL_Window* getSDLWindow() const { return m_window; }
|
||||
// Texture queries
|
||||
void getTextureSize(SDL_Texture* tex, int& w, int& h) const;
|
||||
|
||||
// State queries
|
||||
bool isInitialized() const { return m_initialized; }
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
#include "MenuState.h"
|
||||
#include "persistence/Scores.h"
|
||||
#include "graphics/Font.h"
|
||||
#include "../core/GlobalState.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
@ -20,7 +21,7 @@ static constexpr int LOGICAL_H = 1000;
|
||||
MenuState::MenuState(StateContext& ctx) : State(ctx) {}
|
||||
|
||||
void MenuState::onEnter() {
|
||||
// nothing for now
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState::onEnter called");
|
||||
}
|
||||
|
||||
void MenuState::onExit() {
|
||||
@ -37,6 +38,10 @@ void MenuState::update(double 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); }
|
||||
}
|
||||
// Compute content offset using the same math as main
|
||||
float winW = float(logicalVP.w);
|
||||
float winH = float(logicalVP.h);
|
||||
@ -50,6 +55,10 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
||||
|
||||
// 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;
|
||||
@ -61,17 +70,37 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
||||
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)
|
||||
menu_drawFireworks(renderer, ctx.blocksTex);
|
||||
{
|
||||
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
|
||||
@ -93,7 +122,7 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
||||
for (size_t i = 0; i < maxDisplay; ++i)
|
||||
{
|
||||
float baseY = scoresStartY + i * 25;
|
||||
float wave = std::sin((float)menu_getLogoAnimCounter() * 0.006f + i * 0.25f) * 6.0f; // subtle wave
|
||||
float wave = std::sin((float)GlobalState::instance().logoAnimCounter * 0.006f + i * 0.25f) * 6.0f; // subtle wave
|
||||
float y = baseY + wave;
|
||||
|
||||
// Center columns around mid X, wider
|
||||
@ -130,16 +159,55 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
||||
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);
|
||||
// left = green, right = blue like original
|
||||
menu_drawMenuButton(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});
|
||||
menu_drawMenuButton(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});
|
||||
// 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) {
|
||||
menu_drawSettingsPopup(renderer, *ctx.font, ctx.musicEnabled ? *ctx.musicEnabled : false);
|
||||
// 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); }
|
||||
}
|
||||
}
|
||||
|
||||
88
src/ui/MenuWrappers.cpp
Normal file
88
src/ui/MenuWrappers.cpp
Normal file
@ -0,0 +1,88 @@
|
||||
// MenuWrappers.cpp - implementations of menu helper wrappers used by MenuState
|
||||
#include "MenuWrappers.h"
|
||||
#include "../core/GlobalState.h"
|
||||
#include "../graphics/Font.h"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
using namespace Globals;
|
||||
|
||||
static void drawRect(SDL_Renderer* renderer, float x, float y, float w, float h, SDL_Color c) {
|
||||
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
|
||||
SDL_FRect fr{ x, y, w, h };
|
||||
SDL_RenderFillRect(renderer, &fr);
|
||||
}
|
||||
|
||||
void menu_drawFireworks(SDL_Renderer* renderer, SDL_Texture* blocksTex) {
|
||||
GlobalState::instance().drawFireworks(renderer, blocksTex);
|
||||
}
|
||||
|
||||
void menu_updateFireworks(double frameMs) {
|
||||
GlobalState::instance().updateFireworks(frameMs);
|
||||
}
|
||||
|
||||
double menu_getLogoAnimCounter() {
|
||||
return GlobalState::instance().logoAnimCounter;
|
||||
}
|
||||
|
||||
int menu_getHoveredButton() {
|
||||
return GlobalState::instance().hoveredButton;
|
||||
}
|
||||
|
||||
void menu_drawEnhancedButton(SDL_Renderer* renderer, FontAtlas& font, float cx, float cy, float w, float h,
|
||||
const std::string& label, bool isHovered, bool isSelected) {
|
||||
// Simple wrapper delegating to a basic draw implementation
|
||||
SDL_Color bgColor = isSelected ? SDL_Color{100,190,255,255} : (isHovered ? SDL_Color{120,150,240,255} : SDL_Color{80,110,200,255});
|
||||
float x = cx - w/2; float y = cy - h/2;
|
||||
drawRect(renderer, x-2, y-2, w+4, h+4, SDL_Color{60,80,140,255});
|
||||
drawRect(renderer, x, y, w, h, bgColor);
|
||||
float textScale = 1.5f;
|
||||
float textX = x + (w - label.length() * 12 * textScale) / 2;
|
||||
float textY = y + (h - 20 * textScale) / 2;
|
||||
font.draw(renderer, textX+2, textY+2, label, textScale, SDL_Color{0,0,0,180});
|
||||
font.draw(renderer, textX, textY, label, textScale, SDL_Color{255,255,255,255});
|
||||
}
|
||||
|
||||
void menu_drawMenuButton(SDL_Renderer* renderer, FontAtlas& font, float cx, float cy, float w, float h,
|
||||
const std::string& label, SDL_Color bgColor, SDL_Color borderColor) {
|
||||
float x = cx - w/2; float y = cy - h/2;
|
||||
drawRect(renderer, x-6, y-6, w+12, h+12, borderColor);
|
||||
drawRect(renderer, x-4, y-4, w+8, h+8, SDL_Color{255,255,255,255});
|
||||
drawRect(renderer, x, y, w, h, bgColor);
|
||||
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(renderer, tx+2, ty+2, label, textScale, SDL_Color{0,0,0,180});
|
||||
font.draw(renderer, tx, ty, label, textScale, SDL_Color{255,255,255,255});
|
||||
}
|
||||
|
||||
void menu_drawLevelSelectionPopup(SDL_Renderer* renderer, FontAtlas& font, SDL_Texture* bgTex, int selectedLevel) {
|
||||
(void)renderer; (void)font; (void)bgTex; (void)selectedLevel; // Not implemented here
|
||||
}
|
||||
|
||||
void menu_drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musicEnabled) {
|
||||
// Copy of main.cpp's drawSettingsPopup behavior adapted here
|
||||
float popupW = 350, popupH = 260;
|
||||
float popupX = (1200 - popupW) / 2;
|
||||
float popupY = (1000 - popupH) / 2;
|
||||
// Overlay
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 128);
|
||||
SDL_FRect overlay{0,0,1200,1000};
|
||||
SDL_RenderFillRect(renderer, &overlay);
|
||||
drawRect(renderer, popupX-4, popupY-4, popupW+8, popupH+8, SDL_Color{100,120,160,255});
|
||||
drawRect(renderer, popupX, popupY, popupW, popupH, SDL_Color{40,50,70,255});
|
||||
font.draw(renderer, popupX + 20, popupY + 20, "SETTINGS", 2.0f, SDL_Color{255,220,0,255});
|
||||
font.draw(renderer, popupX + 20, popupY + 70, "MUSIC:", 1.5f, SDL_Color{255,255,255,255});
|
||||
const char* musicStatus = musicEnabled ? "ON" : "OFF";
|
||||
SDL_Color musicColor = musicEnabled ? SDL_Color{0,255,0,255} : SDL_Color{255,0,0,255};
|
||||
font.draw(renderer, popupX + 120, popupY + 70, musicStatus, 1.5f, musicColor);
|
||||
font.draw(renderer, popupX + 20, popupY + 100, "SOUND FX:", 1.5f, SDL_Color{255,255,255,255});
|
||||
// Sound effect manager may be initialized elsewhere; show placeholder status for now
|
||||
bool sfxOn = true;
|
||||
font.draw(renderer, popupX + 140, popupY + 100, sfxOn ? "ON" : "OFF", 1.5f, sfxOn ? SDL_Color{0,255,0,255} : SDL_Color{255,0,0,255});
|
||||
font.draw(renderer, popupX + 20, popupY + 150, "M = TOGGLE MUSIC", 1.0f, SDL_Color{200,200,220,255});
|
||||
font.draw(renderer, popupX + 20, popupY + 170, "S = TOGGLE SOUND FX", 1.0f, SDL_Color{200,200,220,255});
|
||||
font.draw(renderer, popupX + 20, popupY + 190, "N = PLAY LETS_GO", 1.0f, SDL_Color{200,200,220,255});
|
||||
font.draw(renderer, popupX + 20, popupY + 210, "ESC = CLOSE", 1.0f, SDL_Color{200,200,220,255});
|
||||
}
|
||||
Reference in New Issue
Block a user