latest state

This commit is contained in:
2025-12-06 09:43:33 +01:00
parent 294e935344
commit b44de25113
19 changed files with 2451 additions and 524 deletions

View File

@ -4,6 +4,7 @@
#include "../core/GlobalState.h"
#include "../core/state/StateManager.h"
#include "../audio/Audio.h"
#include "../audio/SoundEffect.h"
#include <SDL3/SDL.h>
#include <cstdio>
#include <algorithm>
@ -20,6 +21,8 @@
// Menu helper wrappers are declared in a shared header implemented in main.cpp
#include "../audio/MenuWrappers.h"
#include "../utils/ImagePathResolver.h"
#include "../graphics/renderers/UIRenderer.h"
#include "../graphics/renderers/GameRenderer.h"
#include <SDL3_image/SDL_image.h>
MenuState::MenuState(StateContext& ctx) : State(ctx) {}
@ -38,6 +41,12 @@ void MenuState::onExit() {
if (ctx.showExitConfirmPopup) {
*ctx.showExitConfirmPopup = false;
}
// Clean up icon textures
if (playIcon) { SDL_DestroyTexture(playIcon); playIcon = nullptr; }
if (levelIcon) { SDL_DestroyTexture(levelIcon); levelIcon = nullptr; }
if (optionsIcon) { SDL_DestroyTexture(optionsIcon); optionsIcon = nullptr; }
if (exitIcon) { SDL_DestroyTexture(exitIcon); exitIcon = nullptr; }
}
void MenuState::handleEvent(const SDL_Event& e) {
@ -177,10 +186,9 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
// Compute content offsets (same approach as main.cpp for proper centering)
float winW = (float)logicalVP.w;
float winH = (float)logicalVP.h;
float contentW = LOGICAL_W * logicalScale;
float contentH = LOGICAL_H * logicalScale;
float contentOffsetX = (winW - contentW) * 0.5f / logicalScale;
float contentOffsetY = (winH - contentH) * 0.5f / logicalScale;
float contentOffsetX = 0.0f;
float contentOffsetY = 0.0f;
UIRenderer::computeContentOffsets(winW, winH, LOGICAL_W, LOGICAL_H, logicalScale, contentOffsetX, contentOffsetY);
// Background is drawn by main (stretched to the full window) to avoid double-draw.
{
@ -250,106 +258,19 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
}
}
// Draw the sci-fi overlay that sits above the scoreboard but below the buttons
SDL_Texture* overlayTex = ctx.mainScreenTex;
int overlayW = ctx.mainScreenW;
int overlayH = ctx.mainScreenH;
static SDL_Texture* fallbackOverlay = nullptr;
static int fallbackW = 0;
static int fallbackH = 0;
if (!overlayTex) {
if (!fallbackOverlay) {
const std::string resolvedOverlay = AssetPath::resolveImagePath("assets/images/main_screen.bmp");
fallbackOverlay = IMG_LoadTexture(renderer, resolvedOverlay.c_str());
if (fallbackOverlay) {
SDL_SetTextureBlendMode(fallbackOverlay, SDL_BLENDMODE_BLEND);
float tmpW = 0.0f;
float tmpH = 0.0f;
SDL_GetTextureSize(fallbackOverlay, &tmpW, &tmpH);
fallbackW = static_cast<int>(tmpW);
fallbackH = static_cast<int>(tmpH);
FILE* f = fopen("tetris_trace.log", "a");
if (f) {
fprintf(f, "MenuState::render loaded fallback overlay texture %p path=%s size=%dx%d\n",
(void*)fallbackOverlay, resolvedOverlay.c_str(), fallbackW, fallbackH);
fclose(f);
}
} else {
FILE* f = fopen("tetris_trace.log", "a");
if (f) {
fprintf(f, "MenuState::render failed to load fallback overlay: %s\n", SDL_GetError());
fclose(f);
}
}
}
overlayTex = fallbackOverlay;
overlayW = fallbackW;
overlayH = fallbackH;
}
if (overlayTex) {
{
FILE* f = fopen("tetris_trace.log", "a");
if (f) {
fprintf(f, "MenuState::render overlay tex=%llu dims=%dx%d\n",
(unsigned long long)(uintptr_t)overlayTex,
overlayW,
overlayH);
fclose(f);
}
}
float texW = overlayW > 0 ? static_cast<float>(overlayW) : 0.0f;
float texH = overlayH > 0 ? static_cast<float>(overlayH) : 0.0f;
if (texW <= 0.0f || texH <= 0.0f) {
if (!SDL_GetTextureSize(overlayTex, &texW, &texH)) {
FILE* f = fopen("tetris_trace.log", "a");
if (f) {
fprintf(f, "MenuState::render failed to query overlay size: %s\n", SDL_GetError());
fclose(f);
}
texW = 0.0f;
texH = 0.0f;
}
}
if (texW > 0.0f && texH > 0.0f) {
const float drawH = LOGICAL_H;
const float scale = drawH / texH;
const float drawW = texW * scale;
SDL_FRect dst{
(LOGICAL_W - drawW) * 0.5f + contentOffsetX,
contentOffsetY,
drawW,
drawH
};
int renderResult = SDL_RenderTexture(renderer, overlayTex, nullptr, &dst);
if (renderResult < 0) {
FILE* f = fopen("tetris_trace.log", "a");
if (f) {
fprintf(f, "MenuState::render failed to draw overlay: %s\n", SDL_GetError());
fclose(f);
}
}
}
} else {
FILE* f = fopen("tetris_trace.log", "a");
if (f) {
fprintf(f, "MenuState::render no overlay texture available\n");
fclose(f);
}
}
// The main_screen overlay is drawn by main.cpp as the background
// We don't need to draw it again here as a logo
// Draw bottom action buttons with responsive sizing (reduced to match main mouse hit-test)
// Use the contentW calculated at the top with content offsets
float contentW = LOGICAL_W * logicalScale;
bool isSmall = (contentW < 700.0f);
float btnW = isSmall ? (LOGICAL_W * 0.32f) : (LOGICAL_W * 0.18f);
btnW = std::clamp(btnW, 180.0f, 260.0f); // keep buttons from consuming entire row
float btnH = isSmall ? 56.0f : 64.0f;
// Adjust button dimensions to match the background button graphics
float btnW = 200.0f; // Fixed width to match background buttons
float btnH = 70.0f; // Fixed height to match background buttons
float btnX = LOGICAL_W * 0.5f + contentOffsetX;
// Move buttons down by 40px to match original layout (user requested 30-50px)
const float btnYOffset = 40.0f;
float btnY = LOGICAL_H * 0.86f + contentOffsetY + btnYOffset; // align with main's button vertical position
// Adjust vertical position to align with background buttons
float btnY = LOGICAL_H * 0.865f + contentOffsetY;
if (ctx.pixelFont) {
{
@ -359,26 +280,6 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
int startLevel = ctx.startLevelSelection ? *ctx.startLevelSelection : 0;
std::snprintf(levelBtnText, sizeof(levelBtnText), "LEVEL %d", startLevel);
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, bool selected){
float x = cx - w/2; float y = cy - h/2;
if (selected) {
SDL_SetRenderDrawColor(r, 255, 220, 0, 110);
SDL_FRect glow{ x-10, y-10, w+20, h+20 };
SDL_RenderFillRect(r, &glow);
}
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.5f;
int textW = 0, textH = 0;
font.measure(label, textScale, textW, textH);
float tx = x + (w - static_cast<float>(textW)) * 0.5f;
float ty = y + (h - static_cast<float>(textH)) * 0.5f;
font.draw(r, tx + 2.0f, ty + 2.0f, label, textScale, SDL_Color{0, 0, 0, 200});
font.draw(r, tx, ty, label, textScale, SDL_Color{255, 255, 255, 255});
};
struct MenuButtonDef {
SDL_Color bg;
SDL_Color border;
@ -391,137 +292,104 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
MenuButtonDef{ SDL_Color{130,80,210,255}, SDL_Color{90,40,170,255}, "OPTIONS" },
MenuButtonDef{ SDL_Color{200,70,70,255}, SDL_Color{150,40,40,255}, "EXIT" }
};
// Icon array (nullptr if icon not loaded)
std::array<SDL_Texture*, 4> icons = {
playIcon,
levelIcon,
optionsIcon,
exitIcon
};
// Fixed spacing to match background button positions
float spacing = isSmall ? btnW * 1.2f : btnW * 1.15f;
for (size_t i = 0; i < buttons.size(); ++i) {
float offset = (static_cast<float>(i) - 1.5f) * spacing;
float cx = btnX + offset;
drawMenuButtonLocal(renderer, *ctx.pixelFont, cx, btnY, btnW, btnH, buttons[i].label, buttons[i].bg, buttons[i].border, selectedButton == static_cast<int>(i));
// Draw each button individually so each can have its own coordinates
// Button 0 - PLAY
{
const int i = 0;
float cxCenter = 0.0f;
float cyCenter = btnY;
if (ctx.menuButtonsExplicit) {
cxCenter = ctx.menuButtonCX[i] + contentOffsetX;
cyCenter = ctx.menuButtonCY[i] + contentOffsetY;
} else {
float offset = (static_cast<float>(i) - 1.5f) * spacing;
cxCenter = btnX + offset;
}
UIRenderer::drawButton(renderer, ctx.pixelFont, cxCenter, cyCenter, btnW, btnH,
buttons[i].label, false, selectedButton == i,
buttons[i].bg, buttons[i].border, true, icons[i]);
}
// Button 1 - LEVEL
{
const int i = 1;
float cxCenter = 0.0f;
float cyCenter = btnY;
if (ctx.menuButtonsExplicit) {
cxCenter = ctx.menuButtonCX[i] + contentOffsetX;
cyCenter = ctx.menuButtonCY[i] + contentOffsetY;
} else {
float offset = (static_cast<float>(i) - 1.5f) * spacing;
cxCenter = btnX + offset;
}
UIRenderer::drawButton(renderer, ctx.pixelFont, cxCenter, cyCenter, btnW, btnH,
buttons[i].label, false, selectedButton == i,
buttons[i].bg, buttons[i].border, true, icons[i]);
}
// Button 2 - OPTIONS
{
const int i = 2;
float cxCenter = 0.0f;
float cyCenter = btnY;
if (ctx.menuButtonsExplicit) {
cxCenter = ctx.menuButtonCX[i] + contentOffsetX;
cyCenter = ctx.menuButtonCY[i] + contentOffsetY;
} else {
float offset = (static_cast<float>(i) - 1.5f) * spacing;
cxCenter = btnX + offset;
}
UIRenderer::drawButton(renderer, ctx.pixelFont, cxCenter, cyCenter, btnW, btnH,
buttons[i].label, false, selectedButton == i,
buttons[i].bg, buttons[i].border, true, icons[i]);
}
// Button 3 - EXIT
{
const int i = 3;
float cxCenter = 0.0f;
float cyCenter = btnY;
if (ctx.menuButtonsExplicit) {
cxCenter = ctx.menuButtonCX[i] + contentOffsetX;
cyCenter = ctx.menuButtonCY[i] + contentOffsetY;
} else {
float offset = (static_cast<float>(i) - 1.5f) * spacing;
cxCenter = btnX + offset;
}
UIRenderer::drawButton(renderer, ctx.pixelFont, cxCenter, cyCenter, btnW, btnH,
buttons[i].label, false, selectedButton == i,
buttons[i].bg, buttons[i].border, true, icons[i]);
}
}
if (ctx.showExitConfirmPopup && *ctx.showExitConfirmPopup) {
int selection = ctx.exitPopupSelectedButton ? *ctx.exitPopupSelectedButton : 1;
// Switch to window coordinates for full-screen overlay
SDL_SetRenderViewport(renderer, nullptr);
SDL_SetRenderScale(renderer, 1.0f, 1.0f);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 150);
// Get actual window size
int actualWinW = 0, actualWinH = 0;
SDL_GetRenderOutputSize(renderer, &actualWinW, &actualWinH);
SDL_FRect overlay{0, 0, (float)actualWinW, (float)actualWinH};
SDL_RenderFillRect(renderer, &overlay);
// Restore viewport and scale for popup content
SDL_SetRenderViewport(renderer, &logicalVP);
SDL_SetRenderScale(renderer, logicalScale, logicalScale);
const float panelW = 640.0f;
const float panelH = 320.0f;
SDL_FRect panel{
(LOGICAL_W - panelW) * 0.5f + contentOffsetX,
(LOGICAL_H - panelH) * 0.5f + contentOffsetY,
panelW,
panelH
};
SDL_FRect shadow{panel.x + 6.0f, panel.y + 10.0f, panel.w, panel.h};
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 140);
SDL_RenderFillRect(renderer, &shadow);
for (int i = 0; i < 5; ++i) {
SDL_FRect glow{panel.x - float(i * 2), panel.y - float(i * 2), panel.w + float(i * 4), panel.h + float(i * 4)};
SDL_SetRenderDrawColor(renderer, 0, 180, 255, Uint8(44 - i * 7));
SDL_RenderRect(renderer, &glow);
}
SDL_SetRenderDrawColor(renderer, 18, 30, 52, 255);
SDL_RenderFillRect(renderer, &panel);
SDL_SetRenderDrawColor(renderer, 70, 120, 210, 255);
SDL_RenderRect(renderer, &panel);
SDL_FRect inner{panel.x + 24.0f, panel.y + 98.0f, panel.w - 48.0f, panel.h - 146.0f};
SDL_SetRenderDrawColor(renderer, 16, 24, 40, 235);
SDL_RenderFillRect(renderer, &inner);
SDL_SetRenderDrawColor(renderer, 40, 80, 140, 235);
SDL_RenderRect(renderer, &inner);
FontAtlas* retroFont = ctx.pixelFont ? ctx.pixelFont : ctx.font;
if (retroFont) {
const float titleScale = 1.9f;
const char* title = "EXIT GAME?";
int titleW = 0, titleH = 0;
retroFont->measure(title, titleScale, titleW, titleH);
float titleX = panel.x + (panel.w - static_cast<float>(titleW)) * 0.5f;
retroFont->draw(renderer, titleX, panel.y + 30.0f, title, titleScale, SDL_Color{255, 230, 140, 255});
const float bodyScale = 1.05f;
const char* line = "Are you sure you want to quit?";
int bodyW = 0, bodyH = 0;
retroFont->measure(line, bodyScale, bodyW, bodyH);
float bodyX = panel.x + (panel.w - static_cast<float>(bodyW)) * 0.5f;
retroFont->draw(renderer, bodyX, inner.y + 18.0f, line, bodyScale, SDL_Color{210, 220, 240, 255});
}
const float horizontalPad = 28.0f;
const float buttonGap = 32.0f;
const float buttonH = 66.0f;
float buttonW = (inner.w - horizontalPad * 2.0f - buttonGap) * 0.5f;
float buttonY = inner.y + inner.h - buttonH - 24.0f;
auto drawChoice = [&](int idx, float x, const char* label) {
bool selected = (selection == idx);
SDL_Color base = (idx == 0) ? SDL_Color{185, 70, 70, 255} : SDL_Color{60, 95, 150, 255};
SDL_Color body = selected ? SDL_Color{Uint8(std::min(255, base.r + 35)), Uint8(std::min(255, base.g + 35)), Uint8(std::min(255, base.b + 35)), 255} : base;
SDL_Color border = selected ? SDL_Color{255, 220, 120, 255} : SDL_Color{80, 110, 160, 255};
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 120);
SDL_FRect shadowRect{x + 4.0f, buttonY + 6.0f, buttonW, buttonH};
SDL_RenderFillRect(renderer, &shadowRect);
SDL_FRect bodyRect{x, buttonY, buttonW, buttonH};
SDL_SetRenderDrawColor(renderer, body.r, body.g, body.b, body.a);
SDL_RenderFillRect(renderer, &bodyRect);
SDL_SetRenderDrawColor(renderer, border.r, border.g, border.b, border.a);
SDL_RenderRect(renderer, &bodyRect);
if (retroFont) {
const float labelScale = 1.4f;
int textW = 0, textH = 0;
retroFont->measure(label, labelScale, textW, textH);
float textX = bodyRect.x + (bodyRect.w - static_cast<float>(textW)) * 0.5f;
float textY = bodyRect.y + (bodyRect.h - static_cast<float>(textH)) * 0.5f;
SDL_Color textColor = selected ? SDL_Color{255, 255, 255, 255} : SDL_Color{230, 235, 250, 255};
retroFont->draw(renderer, textX, textY, label, labelScale, textColor);
}
};
float yesX = inner.x + horizontalPad;
float noX = yesX + buttonW + buttonGap;
drawChoice(0, yesX, "YES");
drawChoice(1, noX, "NO");
GameRenderer::renderExitPopup(
renderer,
ctx.pixelFont,
winW,
winH,
logicalScale,
ctx.exitPopupSelectedButton ? *ctx.exitPopupSelectedButton : 1
);
}
// Popups (settings only - level popup is now a separate state)
if (ctx.showSettingsPopup && *ctx.showSettingsPopup) {
// 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, "ESC = CLOSE", 1.0f, SDL_Color{200,200,220,255});
bool soundOn = SoundEffectManager::instance().isEnabled();
UIRenderer::drawSettingsPopup(renderer, ctx.font, LOGICAL_W, LOGICAL_H, musicOn, soundOn);
}
// Trace exit
{