|
|
|
|
@ -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() {
|
|
|
|
|
|