Created LevelSelectorState

- code removed from main.cpp and added into a new class
This commit is contained in:
2025-08-17 09:10:49 +02:00
parent eddd1a24b2
commit 0e0519b0e9
9 changed files with 346 additions and 173 deletions

View File

@ -26,6 +26,7 @@
#include "states/State.h"
#include "states/LoadingState.h"
#include "states/MenuState.h"
#include "states/LevelSelectorState.h"
#include "states/PlayingState.h"
#include "audio/MenuWrappers.h"
@ -72,7 +73,7 @@ static void drawRect(SDL_Renderer *r, float x, float y, float w, float h, SDL_Co
}
// Hover state for level popup ( -1 = none, 0..19 = hovered level )
static int hoveredLevel = -1;
// Now managed by LevelSelectorState
// ...existing code...
@ -106,13 +107,8 @@ void menu_drawEnhancedButton(SDL_Renderer* renderer, FontAtlas& font, float cx,
// Popup wrappers
// Forward declarations for popup functions defined later in this file
static void drawLevelSelectionPopup(SDL_Renderer* renderer, FontAtlas& font, SDL_Texture* bgTex, int selectedLevel);
static void drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musicEnabled);
void menu_drawLevelSelectionPopup(SDL_Renderer* renderer, FontAtlas& font, SDL_Texture* bgTex, int selectedLevel) {
drawLevelSelectionPopup(renderer, font, bgTex, selectedLevel);
}
void menu_drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musicEnabled) {
drawSettingsPopup(renderer, font, musicEnabled);
}
@ -230,77 +226,6 @@ static void drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, Piece
// -----------------------------------------------------------------------------
// Popup Drawing Functions
// -----------------------------------------------------------------------------
static void drawLevelSelectionPopup(SDL_Renderer* renderer, FontAtlas& font, SDL_Texture* bgTex, int selectedLevel) {
// Popup dims scale with logical size for responsiveness
float popupW = 400, popupH = 300;
float popupX = (LOGICAL_W - popupW) / 2;
float popupY = (LOGICAL_H - popupH) / 2;
// Draw the background picture stretched to full logical viewport if available
if (bgTex) {
// Dim the background by rendering it then overlaying a semi-transparent black rect
SDL_FRect dst{0, 0, (float)LOGICAL_W, (float)LOGICAL_H};
SDL_RenderTexture(renderer, bgTex, nullptr, &dst);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 160);
SDL_FRect dim{0,0,(float)LOGICAL_W,(float)LOGICAL_H};
SDL_RenderFillRect(renderer, &dim);
} else {
// Fallback to semi-transparent overlay
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180);
SDL_FRect overlay{0, 0, (float)LOGICAL_W, (float)LOGICAL_H};
SDL_RenderFillRect(renderer, &overlay);
}
// Popup panel with border and subtle background
drawRect(renderer, popupX-6, popupY-6, popupW+12, popupH+12, {90, 110, 140, 200}); // outer border
drawRect(renderer, popupX-3, popupY-3, popupW+6, popupH+6, {30, 38, 60, 220}); // inner border
drawRect(renderer, popupX, popupY, popupW, popupH, {18, 22, 34, 235}); // panel
// Title (use retro pixel font)
font.draw(renderer, popupX + 28, popupY + 18, "SELECT STARTING LEVEL", 2.4f, {255, 220, 0, 255});
// Grid layout for levels: 4 columns x 5 rows
int cols = 4, rows = 5;
float padding = 24.0f;
float gridW = popupW - padding * 2;
float gridH = popupH - 120.0f; // leave space for title and instructions
float cellW = gridW / cols;
float cellH = std::min(80.0f, gridH / rows - 12.0f);
float gridStartX = popupX + padding;
float gridStartY = popupY + 70;
for (int level = 0; level < 20; ++level) {
int row = level / cols;
int col = level % cols;
float cx = gridStartX + col * cellW;
float cy = gridStartY + row * (cellH + 12.0f);
bool isSelected = (level == selectedLevel);
SDL_Color bg = isSelected ? SDL_Color{255, 220, 0, 255} : SDL_Color{70, 85, 120, 240};
SDL_Color fg = isSelected ? SDL_Color{0, 0, 0, 255} : SDL_Color{240, 240, 245, 255};
// Button background
// Hover highlight
bool isHovered = (level == hoveredLevel);
if (isHovered && !isSelected) {
// slightly brighter hover background
SDL_Color hoverBg = SDL_Color{95, 120, 170, 255};
drawRect(renderer, cx + 8 - 2, cy - 2, cellW - 12, cellH + 4, hoverBg);
}
drawRect(renderer, cx + 8, cy, cellW - 16, cellH, bg);
// Level label centered
char levelStr[8]; snprintf(levelStr, sizeof(levelStr), "%d", level);
float tx = cx + (cellW / 2.0f) - (6.0f * 1.8f); // rough centering
float ty = cy + (cellH / 2.0f) - 10.0f;
font.draw(renderer, tx, ty, levelStr, 1.8f, fg);
}
// Instructions under grid
font.draw(renderer, popupX + 28, popupY + popupH - 40, "CLICK A LEVEL TO SELECT • ESC = CANCEL", 1.0f, {200,200,220,255});
}
static void drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musicEnabled) {
float popupW = 350, popupH = 260;
float popupX = (LOGICAL_W - popupW) / 2;
@ -349,7 +274,6 @@ static void drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musi
// Intro/Menu state variables
// -----------------------------------------------------------------------------
static double logoAnimCounter = 0.0;
static bool showLevelPopup = false;
static bool showSettingsPopup = false;
static bool showExitConfirmPopup = false;
static bool musicEnabled = true;
@ -718,13 +642,13 @@ int main(int, char **)
ctx.musicEnabled = &musicEnabled;
ctx.startLevelSelection = &startLevelSelection;
ctx.hoveredButton = &hoveredButton;
ctx.showLevelPopup = &showLevelPopup;
ctx.showSettingsPopup = &showSettingsPopup;
ctx.showExitConfirmPopup = &showExitConfirmPopup;
// Instantiate state objects
auto loadingState = std::make_unique<LoadingState>(ctx);
auto menuState = std::make_unique<MenuState>(ctx);
auto levelSelectorState = std::make_unique<LevelSelectorState>(ctx);
auto playingState = std::make_unique<PlayingState>(ctx);
// Register handlers and lifecycle hooks
@ -736,6 +660,10 @@ int main(int, char **)
stateMgr.registerOnEnter(AppState::Menu, [&](){ menuState->onEnter(); });
stateMgr.registerOnExit(AppState::Menu, [&](){ menuState->onExit(); });
stateMgr.registerHandler(AppState::LevelSelector, [&](const SDL_Event& e){ levelSelectorState->handleEvent(e); });
stateMgr.registerOnEnter(AppState::LevelSelector, [&](){ levelSelectorState->onEnter(); });
stateMgr.registerOnExit(AppState::LevelSelector, [&](){ levelSelectorState->onExit(); });
// Combined Playing state handler: run playingState handler and inline gameplay mapping
stateMgr.registerHandler(AppState::Playing, [&](const SDL_Event& e){
// First give the PlayingState a chance to handle the event
@ -832,41 +760,7 @@ int main(int, char **)
float contentOffsetX = (winW - contentW) * 0.5f / logicalScale;
float contentOffsetY = (winH - contentH) * 0.5f / logicalScale;
if (showLevelPopup) {
// Handle level selection popup clicks (use same math as drawLevelSelectionPopup)
float popupW = 400, popupH = 300;
float popupX = (LOGICAL_W - popupW) / 2;
float popupY = (LOGICAL_H - popupH) / 2;
int cols = 4, rows = 5;
float padding = 24.0f;
float gridW = popupW - padding * 2;
float gridH = popupH - 120.0f;
float cellW = gridW / cols;
float cellH = std::min(80.0f, gridH / rows - 12.0f);
float gridStartX = popupX + padding;
float gridStartY = popupY + 70;
if (lx >= popupX && lx <= popupX + popupW && ly >= popupY && ly <= popupY + popupH) {
// Click inside popup - check level grid
if (lx >= gridStartX && ly >= gridStartY) {
int col = int((lx - gridStartX) / cellW);
int row = int((ly - gridStartY) / (cellH + 12.0f));
if (col >= 0 && col < cols && row >= 0 && row < rows) {
int selectedLevel = row * cols + col;
if (selectedLevel < 20) {
startLevelSelection = selectedLevel;
showLevelPopup = false;
hoveredLevel = -1;
}
}
}
} else {
// Click outside popup - close it
showLevelPopup = false;
hoveredLevel = -1;
}
} else if (showSettingsPopup) {
if (showSettingsPopup) {
// Click anywhere closes settings popup
showSettingsPopup = false;
} else {
@ -890,7 +784,8 @@ int main(int, char **)
}
else if (lx >= levelBtn.x && lx <= levelBtn.x + levelBtn.w && ly >= levelBtn.y && ly <= levelBtn.y + levelBtn.h)
{
showLevelPopup = true;
state = AppState::LevelSelector;
stateMgr.setState(state);
}
// Settings button (gear icon area - top right)
@ -921,24 +816,24 @@ int main(int, char **)
float localY = ly - contentOffsetY;
// Popup rect in logical coordinates (content-local)
float popupW = 420, popupH = 180;
float popupX = (LOGICAL_W - popupW) / 2;
float popupY = (LOGICAL_H - popupH) / 2;
float popupW = 400, popupH = 200;
float popupX = (LOGICAL_W - popupW) / 2.0f;
float popupY = (LOGICAL_H - popupH) / 2.0f;
// Simple Yes/No buttons
float btnW = 120.0f, btnH = 40.0f;
float yesX = popupX + popupW * 0.25f - btnW / 2.0f;
float noX = popupX + popupW * 0.75f - btnW / 2.0f;
float btnY = popupY + popupH - btnH - 20.0f;
if (localX >= popupX && localX <= popupX + popupW && localY >= popupY && localY <= popupY + popupH) {
// Inside popup: two buttons Yes / No
float btnW = 140, btnH = 46;
float yesX = popupX + popupW * 0.25f - btnW/2.0f;
float noX = popupX + popupW * 0.75f - btnW/2.0f;
float btnY = popupY + popupH - 60;
// Click inside popup - check buttons
if (localX >= yesX && localX <= yesX + btnW && localY >= btnY && localY <= btnY + btnH) {
// Yes -> go back to menu
showExitConfirmPopup = false;
game.reset(startLevelSelection);
state = AppState::Menu;
stateMgr.setState(state);
}
else if (localX >= noX && localX <= noX + btnW && localY >= btnY && localY <= btnY + btnH) {
} else if (localX >= noX && localX <= noX + btnW && localY >= btnY && localY <= btnY + btnH) {
// No -> close popup and resume
showExitConfirmPopup = false;
game.setPaused(false);
@ -973,40 +868,12 @@ int main(int, char **)
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 level popup is not shown, clear hoveredLevel; otherwise compute it
// Check menu button hovers (no level popup to handle anymore)
hoveredButton = -1;
if (!showLevelPopup) {
if (lx >= playBtn.x && lx <= playBtn.x + playBtn.w && ly >= playBtn.y && ly <= playBtn.y + playBtn.h)
hoveredButton = 0;
else if (lx >= levelBtn.x && lx <= levelBtn.x + levelBtn.w && ly >= levelBtn.y && ly <= levelBtn.y + levelBtn.h)
hoveredButton = 1;
hoveredLevel = -1;
} else {
// compute hover over popup grid using same math as drawLevelSelectionPopup
float popupW = 400, popupH = 300;
float popupX = (LOGICAL_W - popupW) / 2;
float popupY = (LOGICAL_H - popupH) / 2;
int cols = 4, rows = 5;
float padding = 24.0f;
float gridW = popupW - padding * 2;
float gridH = popupH - 120.0f;
float cellW = gridW / cols;
float cellH = std::min(80.0f, gridH / rows - 12.0f);
float gridStartX = popupX + padding;
float gridStartY = popupY + 70;
hoveredLevel = -1;
if (lx >= popupX && lx <= popupX + popupW && ly >= popupY && ly <= popupY + popupH) {
if (lx >= gridStartX && ly >= gridStartY) {
int col = int((lx - gridStartX) / cellW);
int row = int((ly - gridStartY) / (cellH + 12.0f));
if (col >= 0 && col < cols && row >= 0 && row < rows) {
int sel = row * cols + col;
if (sel < 20) hoveredLevel = sel;
}
}
}
}
if (lx >= playBtn.x && lx <= playBtn.x + playBtn.w && ly >= playBtn.y && ly <= playBtn.y + playBtn.h)
hoveredButton = 0;
else if (lx >= levelBtn.x && lx <= levelBtn.x + levelBtn.w && ly >= levelBtn.y && ly <= levelBtn.y + levelBtn.h)
hoveredButton = 1;
}
}
}
@ -1186,6 +1053,9 @@ int main(int, char **)
case AppState::Menu:
menuState->update(frameMs);
break;
case AppState::LevelSelector:
levelSelectorState->update(frameMs);
break;
case AppState::Playing:
playingState->update(frameMs);
break;
@ -1363,14 +1233,23 @@ int main(int, char **)
// Delegate full menu rendering to MenuState object now
menuState->render(renderer, logicalScale, logicalVP);
break;
case AppState::LevelSelector:
// Delegate level selector rendering to LevelSelectorState
levelSelectorState->render(renderer, logicalScale, logicalVP);
break;
case AppState::LevelSelect:
font.draw(renderer, LOGICAL_W * 0.5f - 120, 80, "SELECT LEVEL", 2.5f, SDL_Color{255, 220, 0, 255});
{
char buf[64];
std::snprintf(buf, sizeof(buf), "LEVEL: %d", startLevelSelection);
font.draw(renderer, LOGICAL_W * 0.5f - 80, 180, buf, 2.0f, SDL_Color{200, 240, 255, 255});
}
{
const std::string title = "SELECT LEVEL";
int tW = 0, tH = 0;
font.measure(title, 2.5f, tW, tH);
float titleX = (LOGICAL_W - (float)tW) / 2.0f;
font.draw(renderer, titleX, 80, title, 2.5f, SDL_Color{255, 220, 0, 255});
char buf[64];
std::snprintf(buf, sizeof(buf), "LEVEL: %d", startLevelSelection);
font.draw(renderer, LOGICAL_W * 0.5f - 80, 180, buf, 2.0f, SDL_Color{200, 240, 255, 255});
font.draw(renderer, LOGICAL_W * 0.5f - 180, 260, "ARROWS CHANGE ENTER=OK ESC=BACK", 1.2f, SDL_Color{200, 200, 220, 255});
}
break;
case AppState::Playing:
{