latest state
This commit is contained in:
@ -8,6 +8,7 @@
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include "../graphics/renderers/UIRenderer.h"
|
||||
|
||||
// Use dynamic logical dimensions from GlobalState instead of hardcoded values
|
||||
|
||||
@ -93,30 +94,7 @@ static void Vignette(SDL_Renderer* r, int w, int h) {
|
||||
FillRect(r, SDL_FRect{(float)w - pad, 0, (float)pad, (float)h}, SDL_Color{0, 0, 0, 140});
|
||||
}
|
||||
|
||||
static SDL_FRect DrawPanel(SDL_Renderer* r, float w, float h, bool draw = true, float offX = 0.f, float offY = 0.f) {
|
||||
float PW = std::min(520.f, w * 0.65f);
|
||||
float PH = std::min(360.f, h * 0.7f);
|
||||
SDL_FRect p{ (w - PW) / 2.f + offX, (h - PH) / 2.f - 40.f + offY, PW, PH }; // Moved up by 50px
|
||||
|
||||
if (!draw) return p; // geometry only
|
||||
|
||||
// drop shadow
|
||||
FillRect(r, SDL_FRect{p.x + 6, p.y + 10, p.w, p.h}, SDL_Color{0, 0, 0, 120});
|
||||
// glow aura
|
||||
for (int i = 0; i < 6; i++) {
|
||||
SDL_FRect g{ p.x - (float)(i * 2), p.y - (float)(i * 2), p.w + (float)(i * 4), p.h + (float)(i * 4) };
|
||||
SDL_Color c = COL_CYAN_SO; c.a = (Uint8)(36 - i * 6);
|
||||
StrokeRect(r, g, c);
|
||||
}
|
||||
// outer body + border
|
||||
FillRect(r, p, COL_PANEL);
|
||||
StrokeRect(r, p, COL_CYAN);
|
||||
|
||||
// inner face
|
||||
FillRect(r, SDL_FRect{p.x + 12, p.y + 56, p.w - 24, p.h - 68}, COL_PANEL_IN);
|
||||
StrokeRect(r, SDL_FRect{p.x + 12, p.y + 56, p.w - 24, p.h - 68}, SDL_Color{24, 31, 41, 180});
|
||||
return p;
|
||||
}
|
||||
// DrawPanel removed, replaced by UIRenderer::drawSciFiPanel
|
||||
|
||||
struct Grid {
|
||||
int cols = 4, rows = 5;
|
||||
@ -195,7 +173,17 @@ void LevelSelectorState::handleEvent(const SDL_Event& e) {
|
||||
float ly = (float(e.button.y) - float(lastLogicalVP.y)) / (lastLogicalScale > 0.f ? lastLogicalScale : 1.f);
|
||||
|
||||
// Use same panel calculation as render (centered)
|
||||
SDL_FRect panel = DrawPanel(nullptr, LOGICAL_W, LOGICAL_H, /*draw=*/false, 0.f, 0.f);
|
||||
const float LOGICAL_W_F = 1200.f;
|
||||
const float LOGICAL_H_F = 1000.f;
|
||||
float winW = (float)lastLogicalVP.w;
|
||||
float winH = (float)lastLogicalVP.h;
|
||||
float contentOffsetX = 0.0f;
|
||||
float contentOffsetY = 0.0f;
|
||||
UIRenderer::computeContentOffsets(winW, winH, LOGICAL_W_F, LOGICAL_H_F, lastLogicalScale, contentOffsetX, contentOffsetY);
|
||||
|
||||
float PW = std::min(520.f, LOGICAL_W_F * 0.65f);
|
||||
float PH = std::min(360.f, LOGICAL_H_F * 0.7f);
|
||||
SDL_FRect panel{ (LOGICAL_W_F - PW) / 2.f + contentOffsetX, (LOGICAL_H_F - PH) / 2.f - 40.f + contentOffsetY, PW, PH };
|
||||
Grid g = MakeGrid(panel);
|
||||
int hit = HitTest(g, int(lx), int(ly));
|
||||
if (hit != -1) {
|
||||
@ -214,7 +202,17 @@ void LevelSelectorState::handleEvent(const SDL_Event& e) {
|
||||
float ly = (float(e.motion.y) - float(lastLogicalVP.y)) / (lastLogicalScale > 0.f ? lastLogicalScale : 1.f);
|
||||
|
||||
// Use same panel calculation as render (centered)
|
||||
SDL_FRect panel = DrawPanel(nullptr, LOGICAL_W, LOGICAL_H, /*draw=*/false, 0.f, 0.f);
|
||||
const float LOGICAL_W_F = 1200.f;
|
||||
const float LOGICAL_H_F = 1000.f;
|
||||
float winW = (float)lastLogicalVP.w;
|
||||
float winH = (float)lastLogicalVP.h;
|
||||
float contentOffsetX = 0.0f;
|
||||
float contentOffsetY = 0.0f;
|
||||
UIRenderer::computeContentOffsets(winW, winH, LOGICAL_W_F, LOGICAL_H_F, lastLogicalScale, contentOffsetX, contentOffsetY);
|
||||
|
||||
float PW = std::min(520.f, LOGICAL_W_F * 0.65f);
|
||||
float PH = std::min(360.f, LOGICAL_H_F * 0.7f);
|
||||
SDL_FRect panel{ (LOGICAL_W_F - PW) / 2.f + contentOffsetX, (LOGICAL_H_F - PH) / 2.f - 40.f + contentOffsetY, PW, PH };
|
||||
Grid g = MakeGrid(panel);
|
||||
hoveredLevel = HitTest(g, int(lx), int(ly));
|
||||
}
|
||||
@ -242,29 +240,30 @@ void LevelSelectorState::drawLevelSelectionPopup(SDL_Renderer* renderer, float l
|
||||
// Compute content offsets (same approach as MenuState 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);
|
||||
|
||||
// Draw the logo at the top (same as MenuState)
|
||||
SDL_Texture* logoToUse = ctx.logoSmallTex ? ctx.logoSmallTex : ctx.logoTex;
|
||||
if (logoToUse) {
|
||||
// Use dimensions provided by the shared context when available
|
||||
int texW = (logoToUse == ctx.logoSmallTex && ctx.logoSmallW > 0) ? ctx.logoSmallW : 872;
|
||||
int texH = (logoToUse == ctx.logoSmallTex && ctx.logoSmallH > 0) ? ctx.logoSmallH : 273;
|
||||
float maxW = LOGICAL_W * 0.6f; // Match MenuState and OptionsState
|
||||
float scale = std::min(1.0f, maxW / float(texW));
|
||||
float dw = texW * scale;
|
||||
float dh = texH * scale;
|
||||
float logoX = (LOGICAL_W - dw) / 2.f + contentOffsetX;
|
||||
float logoY = LOGICAL_H * 0.05f + contentOffsetY; // Match MenuState and OptionsState
|
||||
SDL_FRect dst{logoX, logoY, dw, dh};
|
||||
SDL_RenderTexture(renderer, logoToUse, nullptr, &dst);
|
||||
int logoW = 0, logoH = 0;
|
||||
if (logoToUse == ctx.logoSmallTex && ctx.logoSmallW > 0) {
|
||||
logoW = ctx.logoSmallW;
|
||||
logoH = ctx.logoSmallH;
|
||||
}
|
||||
UIRenderer::drawLogo(renderer, logoToUse, LOGICAL_W, LOGICAL_H, contentOffsetX, contentOffsetY, logoW, logoH);
|
||||
|
||||
// Panel and title strip (in logical space) - centered properly with offsets
|
||||
SDL_FRect panel = DrawPanel(renderer, LOGICAL_W, LOGICAL_H, /*draw=*/true, contentOffsetX, contentOffsetY);
|
||||
float PW = std::min(520.f, LOGICAL_W * 0.65f);
|
||||
float PH = std::min(360.f, LOGICAL_H * 0.7f);
|
||||
SDL_FRect panel{ (LOGICAL_W - PW) / 2.f + contentOffsetX, (LOGICAL_H - PH) / 2.f - 40.f + contentOffsetY, PW, PH };
|
||||
|
||||
UIRenderer::drawSciFiPanel(renderer, panel);
|
||||
|
||||
// Inner face (LevelSelector specific)
|
||||
SDL_FRect inner{panel.x + 12, panel.y + 56, panel.w - 24, panel.h - 68};
|
||||
FillRect(renderer, inner, COL_PANEL_IN);
|
||||
StrokeRect(renderer, inner, SDL_Color{24, 31, 41, 180});
|
||||
|
||||
// Title text - prefer pixelFont for a blocky title if available, fallback to regular font
|
||||
FontAtlas* titleFont = ctx.pixelFont ? ctx.pixelFont : ctx.font;
|
||||
@ -296,7 +295,17 @@ bool LevelSelectorState::isMouseInPopup(float mouseX, float mouseY, float& popup
|
||||
lx = (float(mouseX) - float(lastLogicalVP.x)) / lastLogicalScale;
|
||||
ly = (float(mouseY) - float(lastLogicalVP.y)) / lastLogicalScale;
|
||||
}
|
||||
SDL_FRect p = DrawPanel(nullptr, LOGICAL_W, LOGICAL_H, /*draw=*/false, 0.f, 0.f);
|
||||
const float LOGICAL_W_F = 1200.f;
|
||||
const float LOGICAL_H_F = 1000.f;
|
||||
float winW = (float)lastLogicalVP.w;
|
||||
float winH = (float)lastLogicalVP.h;
|
||||
float contentOffsetX = 0.0f;
|
||||
float contentOffsetY = 0.0f;
|
||||
UIRenderer::computeContentOffsets(winW, winH, LOGICAL_W_F, LOGICAL_H_F, lastLogicalScale, contentOffsetX, contentOffsetY);
|
||||
|
||||
float PW = std::min(520.f, LOGICAL_W_F * 0.65f);
|
||||
float PH = std::min(360.f, LOGICAL_H_F * 0.7f);
|
||||
SDL_FRect p{ (LOGICAL_W_F - PW) / 2.f + contentOffsetX, (LOGICAL_H_F - PH) / 2.f - 40.f + contentOffsetY, PW, PH };
|
||||
popupX = p.x; popupY = p.y; popupW = p.w; popupH = p.h;
|
||||
return lx >= popupX && lx <= popupX + popupW && ly >= popupY && ly <= popupY + popupH;
|
||||
}
|
||||
@ -312,7 +321,17 @@ int LevelSelectorState::getLevelFromMouse(float mouseX, float mouseY, float popu
|
||||
lx = (float(mouseX) - float(lastLogicalVP.x)) / lastLogicalScale;
|
||||
ly = (float(mouseY) - float(lastLogicalVP.y)) / lastLogicalScale;
|
||||
}
|
||||
SDL_FRect p = DrawPanel(nullptr, LOGICAL_W, LOGICAL_H, /*draw=*/false, 0.f, 0.f);
|
||||
const float LOGICAL_W_F = 1200.f;
|
||||
const float LOGICAL_H_F = 1000.f;
|
||||
float winW = (float)lastLogicalVP.w;
|
||||
float winH = (float)lastLogicalVP.h;
|
||||
float contentOffsetX = 0.0f;
|
||||
float contentOffsetY = 0.0f;
|
||||
UIRenderer::computeContentOffsets(winW, winH, LOGICAL_W_F, LOGICAL_H_F, lastLogicalScale, contentOffsetX, contentOffsetY);
|
||||
|
||||
float PW = std::min(520.f, LOGICAL_W_F * 0.65f);
|
||||
float PH = std::min(360.f, LOGICAL_H_F * 0.7f);
|
||||
SDL_FRect p{ (LOGICAL_W_F - PW) / 2.f + contentOffsetX, (LOGICAL_H_F - PH) / 2.f - 40.f + contentOffsetY, PW, PH };
|
||||
Grid g = MakeGrid(p);
|
||||
return HitTest(g, (int)lx, (int)ly);
|
||||
}
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -13,4 +13,10 @@ public:
|
||||
|
||||
private:
|
||||
int selectedButton = 0; // 0 = PLAY, 1 = LEVEL, 2 = OPTIONS, 3 = EXIT
|
||||
|
||||
// Button icons (optional - will use text if nullptr)
|
||||
SDL_Texture* playIcon = nullptr;
|
||||
SDL_Texture* levelIcon = nullptr;
|
||||
SDL_Texture* optionsIcon = nullptr;
|
||||
SDL_Texture* exitIcon = nullptr;
|
||||
};
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include "../core/Settings.h"
|
||||
#include "../graphics/renderers/UIRenderer.h"
|
||||
|
||||
OptionsState::OptionsState(StateContext& ctx) : State(ctx) {}
|
||||
|
||||
@ -89,32 +90,17 @@ void OptionsState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect l
|
||||
|
||||
float winW = static_cast<float>(logicalVP.w);
|
||||
float winH = static_cast<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);
|
||||
|
||||
SDL_Texture* logoTexture = ctx.logoSmallTex ? ctx.logoSmallTex : ctx.logoTex;
|
||||
if (logoTexture) {
|
||||
float texW = 0.0f;
|
||||
float texH = 0.0f;
|
||||
if (logoTexture == ctx.logoSmallTex && ctx.logoSmallW > 0 && ctx.logoSmallH > 0) {
|
||||
texW = static_cast<float>(ctx.logoSmallW);
|
||||
texH = static_cast<float>(ctx.logoSmallH);
|
||||
} else {
|
||||
SDL_GetTextureSize(logoTexture, &texW, &texH);
|
||||
}
|
||||
if (texW > 0.0f && texH > 0.0f) {
|
||||
float maxWidth = LOGICAL_W * 0.6f;
|
||||
float scale = std::min(1.0f, maxWidth / texW);
|
||||
float dw = texW * scale;
|
||||
float dh = texH * scale;
|
||||
float logoX = (LOGICAL_W - dw) * 0.5f + contentOffsetX;
|
||||
float logoY = LOGICAL_H * 0.05f + contentOffsetY;
|
||||
SDL_FRect dst{logoX, logoY, dw, dh};
|
||||
SDL_RenderTexture(renderer, logoTexture, nullptr, &dst);
|
||||
}
|
||||
int logoW = 0, logoH = 0;
|
||||
if (logoTexture == ctx.logoSmallTex && ctx.logoSmallW > 0) {
|
||||
logoW = ctx.logoSmallW;
|
||||
logoH = ctx.logoSmallH;
|
||||
}
|
||||
UIRenderer::drawLogo(renderer, logoTexture, LOGICAL_W, LOGICAL_H, contentOffsetX, contentOffsetY, logoW, logoH);
|
||||
|
||||
const float panelW = 520.0f;
|
||||
const float panelH = 420.0f;
|
||||
@ -125,23 +111,7 @@ void OptionsState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect l
|
||||
panelH
|
||||
};
|
||||
|
||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||
|
||||
// Panel styling similar to level selector
|
||||
SDL_FRect shadow{panel.x + 6.0f, panel.y + 10.0f, panel.w, panel.h};
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 120);
|
||||
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(42 - i * 8));
|
||||
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);
|
||||
UIRenderer::drawSciFiPanel(renderer, panel);
|
||||
|
||||
FontAtlas* retroFont = ctx.pixelFont ? ctx.pixelFont : ctx.font;
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <array>
|
||||
|
||||
// Forward declarations for frequently used types
|
||||
class Game;
|
||||
@ -66,6 +67,12 @@ struct StateContext {
|
||||
std::function<void(AppState)> requestFadeTransition; // Generic state fade requests (menu/options/level)
|
||||
// Pointer to the application's StateManager so states can request transitions
|
||||
StateManager* stateManager = nullptr;
|
||||
// Optional explicit per-button coordinates (logical coordinates). When
|
||||
// `menuButtonsExplicit` is true, MenuState will use these centers for
|
||||
// rendering and hit tests. Values are in logical units (LOGICAL_W/H).
|
||||
std::array<float, 4> menuButtonCX{};
|
||||
std::array<float, 4> menuButtonCY{};
|
||||
bool menuButtonsExplicit = false;
|
||||
};
|
||||
|
||||
class State {
|
||||
|
||||
Reference in New Issue
Block a user