Retro exit modal styling and shortcuts
This commit is contained in:
@ -3,6 +3,7 @@
|
|||||||
#include "../graphics/Font.h"
|
#include "../graphics/Font.h"
|
||||||
#include "../gameplay/LineEffect.h"
|
#include "../gameplay/LineEffect.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
@ -421,60 +422,88 @@ void GameRenderer::renderPlayingState(
|
|||||||
pixelFont->draw(renderer, logicalW * 0.5f - 120, logicalH * 0.5f + 30, "Press P to resume", 0.8f, {200, 200, 220, 255});
|
pixelFont->draw(renderer, logicalW * 0.5f - 120, logicalH * 0.5f + 30, "Press P to resume", 0.8f, {200, 200, 220, 255});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit confirmation popup
|
|
||||||
if (showExitConfirmPopup) {
|
if (showExitConfirmPopup) {
|
||||||
float popupW = 420.0f, popupH = 180.0f;
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||||
float popupX = (logicalW - popupW) * 0.5f;
|
|
||||||
float popupY = (logicalH - popupH) * 0.5f;
|
|
||||||
|
|
||||||
// Dim entire window (do not change viewport/scales here)
|
|
||||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 200);
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 200);
|
||||||
SDL_FRect fullWin{0.f, 0.f, winW, winH};
|
SDL_FRect fullWin{0.f, 0.f, winW, winH};
|
||||||
SDL_RenderFillRect(renderer, &fullWin);
|
SDL_RenderFillRect(renderer, &fullWin);
|
||||||
|
|
||||||
// Draw popup box in logical coords with content offsets
|
const float panelW = 640.0f;
|
||||||
drawRectWithOffset(popupX - 4.0f, popupY - 4.0f, popupW + 8.0f, popupH + 8.0f, {60, 70, 90, 255});
|
const float panelH = 320.0f;
|
||||||
drawRectWithOffset(popupX, popupY, popupW, popupH, {20, 22, 28, 240});
|
SDL_FRect panel{
|
||||||
|
(logicalW - panelW) * 0.5f + contentOffsetX,
|
||||||
|
(logicalH - panelH) * 0.5f + contentOffsetY,
|
||||||
|
panelW,
|
||||||
|
panelH
|
||||||
|
};
|
||||||
|
|
||||||
// Text content (measure to perfectly center)
|
SDL_FRect shadow{panel.x + 6.0f, panel.y + 10.0f, panel.w, panel.h};
|
||||||
const std::string title = "Exit game?";
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 140);
|
||||||
const std::string line1 = "Are you sure you want to";
|
SDL_RenderFillRect(renderer, &shadow);
|
||||||
const std::string line2 = "leave the current game?";
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
int wTitle=0,hTitle=0; pixelFont->measure(title, 1.6f, wTitle, hTitle);
|
SDL_SetRenderDrawColor(renderer, 18, 30, 52, 255);
|
||||||
int wL1=0,hL1=0; pixelFont->measure(line1, 0.9f, wL1, hL1);
|
SDL_RenderFillRect(renderer, &panel);
|
||||||
int wL2=0,hL2=0; pixelFont->measure(line2, 0.9f, wL2, hL2);
|
SDL_SetRenderDrawColor(renderer, 70, 120, 210, 255);
|
||||||
|
SDL_RenderRect(renderer, &panel);
|
||||||
|
|
||||||
float titleX = popupX + (popupW - (float)wTitle) * 0.5f + contentOffsetX;
|
SDL_FRect inner{panel.x + 24.0f, panel.y + 98.0f, panel.w - 48.0f, panel.h - 146.0f};
|
||||||
float l1X = popupX + (popupW - (float)wL1) * 0.5f + contentOffsetX;
|
SDL_SetRenderDrawColor(renderer, 16, 24, 40, 235);
|
||||||
float l2X = popupX + (popupW - (float)wL2) * 0.5f + contentOffsetX;
|
SDL_RenderFillRect(renderer, &inner);
|
||||||
|
SDL_SetRenderDrawColor(renderer, 40, 80, 140, 235);
|
||||||
|
SDL_RenderRect(renderer, &inner);
|
||||||
|
|
||||||
pixelFont->draw(renderer, titleX, popupY + contentOffsetY + 20.0f, title, 1.6f, {255, 220, 0, 255});
|
const std::string title = "EXIT GAME?";
|
||||||
pixelFont->draw(renderer, l1X, popupY + contentOffsetY + 60.0f, line1, 0.9f, {220, 220, 230, 255});
|
int titleW = 0, titleH = 0;
|
||||||
pixelFont->draw(renderer, l2X, popupY + contentOffsetY + 84.0f, line2, 0.9f, {220, 220, 230, 255});
|
const float titleScale = 1.8f;
|
||||||
|
pixelFont->measure(title, titleScale, titleW, titleH);
|
||||||
|
pixelFont->draw(renderer, panel.x + (panel.w - titleW) * 0.5f, panel.y + 30.0f, title, titleScale, {255, 230, 140, 255});
|
||||||
|
|
||||||
// Buttons
|
std::array<std::string, 2> lines = {
|
||||||
float btnW = 140.0f, btnH = 46.0f;
|
"Are you sure you want to quit?",
|
||||||
float yesX = popupX + popupW * 0.25f - btnW * 0.5f;
|
"Current progress will be lost."
|
||||||
float noX = popupX + popupW * 0.75f - btnW * 0.5f;
|
};
|
||||||
float btnY = popupY + popupH - 60.0f;
|
float lineY = inner.y + 22.0f;
|
||||||
|
const float lineScale = 1.05f;
|
||||||
|
for (const auto& line : lines) {
|
||||||
|
int lineW = 0, lineH = 0;
|
||||||
|
pixelFont->measure(line, lineScale, lineW, lineH);
|
||||||
|
float textX = panel.x + (panel.w - lineW) * 0.5f;
|
||||||
|
pixelFont->draw(renderer, textX, lineY, line, lineScale, SDL_Color{210, 220, 240, 255});
|
||||||
|
lineY += lineH + 10.0f;
|
||||||
|
}
|
||||||
|
|
||||||
// YES button
|
const float horizontalPad = 28.0f;
|
||||||
drawRectWithOffset(yesX - 2.0f, btnY - 2.0f, btnW + 4.0f, btnH + 4.0f, {100, 120, 140, 255});
|
const float buttonGap = 32.0f;
|
||||||
drawRectWithOffset(yesX, btnY, btnW, btnH, {200, 60, 60, 255});
|
const float buttonH = 66.0f;
|
||||||
const std::string yes = "YES";
|
float buttonW = (inner.w - horizontalPad * 2.0f - buttonGap) * 0.5f;
|
||||||
int wYes=0,hYes=0; pixelFont->measure(yes, 1.0f, wYes, hYes);
|
float buttonY = inner.y + inner.h - buttonH - 24.0f;
|
||||||
pixelFont->draw(renderer, yesX + (btnW - (float)wYes) * 0.5f + contentOffsetX,
|
|
||||||
btnY + (btnH - (float)hYes) * 0.5f + contentOffsetY,
|
|
||||||
yes, 1.0f, {255, 255, 255, 255});
|
|
||||||
|
|
||||||
// NO button
|
auto drawButton = [&](float x, const char* label, SDL_Color base) {
|
||||||
drawRectWithOffset(noX - 2.0f, btnY - 2.0f, btnW + 4.0f, btnH + 4.0f, {100, 120, 140, 255});
|
SDL_FRect btn{x, buttonY, buttonW, buttonH};
|
||||||
drawRectWithOffset(noX, btnY, btnW, btnH, {80, 140, 80, 255});
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 120);
|
||||||
const std::string no = "NO";
|
SDL_FRect btnShadow{btn.x + 4.0f, btn.y + 6.0f, btn.w, btn.h};
|
||||||
int wNo=0,hNo=0; pixelFont->measure(no, 1.0f, wNo, hNo);
|
SDL_RenderFillRect(renderer, &btnShadow);
|
||||||
pixelFont->draw(renderer, noX + (btnW - (float)wNo) * 0.5f + contentOffsetX,
|
SDL_SetRenderDrawColor(renderer, base.r, base.g, base.b, base.a);
|
||||||
btnY + (btnH - (float)hNo) * 0.5f + contentOffsetY,
|
SDL_RenderFillRect(renderer, &btn);
|
||||||
no, 1.0f, {255, 255, 255, 255});
|
SDL_SetRenderDrawColor(renderer, 90, 130, 200, 255);
|
||||||
|
SDL_RenderRect(renderer, &btn);
|
||||||
|
|
||||||
|
int textW = 0, textH = 0;
|
||||||
|
const float labelScale = 1.4f;
|
||||||
|
pixelFont->measure(label, labelScale, textW, textH);
|
||||||
|
float textX = btn.x + (btn.w - textW) * 0.5f;
|
||||||
|
float textY = btn.y + (btn.h - textH) * 0.5f;
|
||||||
|
pixelFont->draw(renderer, textX, textY, label, labelScale, SDL_Color{255, 255, 255, 255});
|
||||||
|
};
|
||||||
|
|
||||||
|
float yesX = inner.x + horizontalPad;
|
||||||
|
float noX = yesX + buttonW + buttonGap;
|
||||||
|
drawButton(yesX, "YES", SDL_Color{185, 70, 70, 255});
|
||||||
|
drawButton(noX, "NO", SDL_Color{60, 95, 150, 255});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include "../ui/Font.h"
|
#include "../ui/Font.h"
|
||||||
#include "../../gameplay/effects/LineEffect.h"
|
#include "../../gameplay/effects/LineEffect.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
@ -422,68 +423,96 @@ void GameRenderer::renderPlayingState(
|
|||||||
pixelFont->draw(renderer, logicalW * 0.5f - 120, logicalH * 0.5f + 30, "Press P to resume", 0.8f, {200, 200, 220, 255});
|
pixelFont->draw(renderer, logicalW * 0.5f - 120, logicalH * 0.5f + 30, "Press P to resume", 0.8f, {200, 200, 220, 255});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit confirmation popup
|
// Exit confirmation popup styled like other retro panels
|
||||||
if (showExitConfirmPopup) {
|
if (showExitConfirmPopup) {
|
||||||
float popupW = 420.0f, popupH = 180.0f;
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||||
float popupX = (logicalW - popupW) * 0.5f;
|
|
||||||
float popupY = (logicalH - popupH) * 0.5f;
|
|
||||||
|
|
||||||
// Dim entire window (do not change viewport/scales here)
|
|
||||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 200);
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 200);
|
||||||
SDL_FRect fullWin{0.f, 0.f, winW, winH};
|
SDL_FRect fullWin{0.f, 0.f, winW, winH};
|
||||||
SDL_RenderFillRect(renderer, &fullWin);
|
SDL_RenderFillRect(renderer, &fullWin);
|
||||||
|
|
||||||
// Draw popup box in logical coords with content offsets
|
const float panelW = 640.0f;
|
||||||
drawRectWithOffset(popupX - 4.0f, popupY - 4.0f, popupW + 8.0f, popupH + 8.0f, {60, 70, 90, 255});
|
const float panelH = 320.0f;
|
||||||
drawRectWithOffset(popupX, popupY, popupW, popupH, {20, 22, 28, 240});
|
SDL_FRect panel{
|
||||||
|
(logicalW - panelW) * 0.5f + contentOffsetX,
|
||||||
|
(logicalH - panelH) * 0.5f + contentOffsetY,
|
||||||
|
panelW,
|
||||||
|
panelH
|
||||||
|
};
|
||||||
|
|
||||||
// Text content (measure to perfectly center)
|
SDL_FRect shadow{panel.x + 6.0f, panel.y + 10.0f, panel.w, panel.h};
|
||||||
const std::string title = "Exit game?";
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 140);
|
||||||
const std::string line1 = "Are you sure you want to";
|
SDL_RenderFillRect(renderer, &shadow);
|
||||||
const std::string line2 = "leave the current game?";
|
|
||||||
|
|
||||||
int wTitle=0,hTitle=0; pixelFont->measure(title, 1.6f, wTitle, hTitle);
|
for (int i = 0; i < 5; ++i) {
|
||||||
int wL1=0,hL1=0; pixelFont->measure(line1, 0.9f, wL1, hL1);
|
SDL_FRect glow{panel.x - float(i * 2), panel.y - float(i * 2), panel.w + float(i * 4), panel.h + float(i * 4)};
|
||||||
int wL2=0,hL2=0; pixelFont->measure(line2, 0.9f, wL2, hL2);
|
SDL_SetRenderDrawColor(renderer, 0, 180, 255, Uint8(44 - i * 7));
|
||||||
|
SDL_RenderRect(renderer, &glow);
|
||||||
float titleX = popupX + (popupW - (float)wTitle) * 0.5f + contentOffsetX;
|
|
||||||
float l1X = popupX + (popupW - (float)wL1) * 0.5f + contentOffsetX;
|
|
||||||
float l2X = popupX + (popupW - (float)wL2) * 0.5f + contentOffsetX;
|
|
||||||
|
|
||||||
pixelFont->draw(renderer, titleX, popupY + contentOffsetY + 20.0f, title, 1.6f, {255, 220, 0, 255});
|
|
||||||
pixelFont->draw(renderer, l1X, popupY + contentOffsetY + 60.0f, line1, 0.9f, {220, 220, 230, 255});
|
|
||||||
pixelFont->draw(renderer, l2X, popupY + contentOffsetY + 84.0f, line2, 0.9f, {220, 220, 230, 255});
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
float btnW = 140.0f, btnH = 46.0f;
|
|
||||||
float yesX = popupX + popupW * 0.25f - btnW * 0.5f;
|
|
||||||
float noX = popupX + popupW * 0.75f - btnW * 0.5f;
|
|
||||||
float btnY = popupY + popupH - 60.0f;
|
|
||||||
|
|
||||||
// YES button
|
|
||||||
if (exitPopupSelectedButton == 0) {
|
|
||||||
// Draw glow for selected YES button
|
|
||||||
drawRectWithOffset(yesX - 6.0f, btnY - 6.0f, btnW + 12.0f, btnH + 12.0f, {255, 220, 0, 100});
|
|
||||||
}
|
}
|
||||||
drawRectWithOffset(yesX - 2.0f, btnY - 2.0f, btnW + 4.0f, btnH + 4.0f, {100, 120, 140, 255});
|
|
||||||
drawRectWithOffset(yesX, btnY, btnW, btnH, {200, 60, 60, 255});
|
|
||||||
const std::string yes = "YES";
|
|
||||||
int wYes=0,hYes=0; pixelFont->measure(yes, 1.0f, wYes, hYes);
|
|
||||||
pixelFont->draw(renderer, yesX + (btnW - (float)wYes) * 0.5f + contentOffsetX,
|
|
||||||
btnY + (btnH - (float)hYes) * 0.5f + contentOffsetY,
|
|
||||||
yes, 1.0f, {255, 255, 255, 255});
|
|
||||||
|
|
||||||
// NO button
|
SDL_SetRenderDrawColor(renderer, 18, 30, 52, 255);
|
||||||
if (exitPopupSelectedButton == 1) {
|
SDL_RenderFillRect(renderer, &panel);
|
||||||
// Draw glow for selected NO button
|
SDL_SetRenderDrawColor(renderer, 70, 120, 210, 255);
|
||||||
drawRectWithOffset(noX - 6.0f, btnY - 6.0f, btnW + 12.0f, btnH + 12.0f, {255, 220, 0, 100});
|
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);
|
||||||
|
|
||||||
|
const std::string title = "EXIT GAME?";
|
||||||
|
int titleW = 0, titleH = 0;
|
||||||
|
const float titleScale = 1.8f;
|
||||||
|
pixelFont->measure(title, titleScale, titleW, titleH);
|
||||||
|
pixelFont->draw(renderer, panel.x + (panel.w - titleW) * 0.5f, panel.y + 30.0f, title, titleScale, {255, 230, 140, 255});
|
||||||
|
|
||||||
|
std::array<std::string, 2> lines = {
|
||||||
|
"Are you sure you want to quit?",
|
||||||
|
"Current progress will be lost."
|
||||||
|
};
|
||||||
|
float lineY = inner.y + 22.0f;
|
||||||
|
const float lineScale = 1.05f;
|
||||||
|
for (const auto& line : lines) {
|
||||||
|
int lineW = 0, lineH = 0;
|
||||||
|
pixelFont->measure(line, lineScale, lineW, lineH);
|
||||||
|
float textX = panel.x + (panel.w - lineW) * 0.5f;
|
||||||
|
pixelFont->draw(renderer, textX, lineY, line, lineScale, SDL_Color{210, 220, 240, 255});
|
||||||
|
lineY += lineH + 10.0f;
|
||||||
}
|
}
|
||||||
drawRectWithOffset(noX - 2.0f, btnY - 2.0f, btnW + 4.0f, btnH + 4.0f, {100, 120, 140, 255});
|
|
||||||
drawRectWithOffset(noX, btnY, btnW, btnH, {80, 140, 80, 255});
|
const float horizontalPad = 28.0f;
|
||||||
const std::string no = "NO";
|
const float buttonGap = 32.0f;
|
||||||
int wNo=0,hNo=0; pixelFont->measure(no, 1.0f, wNo, hNo);
|
const float buttonH = 66.0f;
|
||||||
pixelFont->draw(renderer, noX + (btnW - (float)wNo) * 0.5f + contentOffsetX,
|
float buttonW = (inner.w - horizontalPad * 2.0f - buttonGap) * 0.5f;
|
||||||
btnY + (btnH - (float)hNo) * 0.5f + contentOffsetY,
|
float buttonY = inner.y + inner.h - buttonH - 24.0f;
|
||||||
no, 1.0f, {255, 255, 255, 255});
|
|
||||||
|
auto drawButton = [&](int idx, float x, const char* label) {
|
||||||
|
bool selected = (exitPopupSelectedButton == 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_FRect btn{x, buttonY, buttonW, buttonH};
|
||||||
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 120);
|
||||||
|
SDL_FRect btnShadow{btn.x + 4.0f, btn.y + 6.0f, btn.w, btn.h};
|
||||||
|
SDL_RenderFillRect(renderer, &btnShadow);
|
||||||
|
SDL_SetRenderDrawColor(renderer, body.r, body.g, body.b, body.a);
|
||||||
|
SDL_RenderFillRect(renderer, &btn);
|
||||||
|
SDL_SetRenderDrawColor(renderer, border.r, border.g, border.b, border.a);
|
||||||
|
SDL_RenderRect(renderer, &btn);
|
||||||
|
|
||||||
|
int textW = 0, textH = 0;
|
||||||
|
const float labelScale = 1.4f;
|
||||||
|
pixelFont->measure(label, labelScale, textW, textH);
|
||||||
|
float textX = btn.x + (btn.w - textW) * 0.5f;
|
||||||
|
float textY = btn.y + (btn.h - textH) * 0.5f;
|
||||||
|
SDL_Color textColor = selected ? SDL_Color{255, 255, 255, 255} : SDL_Color{230, 235, 250, 255};
|
||||||
|
pixelFont->draw(renderer, textX, textY, label, labelScale, textColor);
|
||||||
|
};
|
||||||
|
|
||||||
|
float yesX = inner.x + horizontalPad;
|
||||||
|
float noX = yesX + buttonW + buttonGap;
|
||||||
|
drawButton(0, yesX, "YES");
|
||||||
|
drawButton(1, noX, "NO");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1167,6 +1167,9 @@ int main(int, char **)
|
|||||||
case AppState::Menu:
|
case AppState::Menu:
|
||||||
menuState->update(frameMs);
|
menuState->update(frameMs);
|
||||||
break;
|
break;
|
||||||
|
case AppState::Options:
|
||||||
|
optionsState->update(frameMs);
|
||||||
|
break;
|
||||||
case AppState::LevelSelector:
|
case AppState::LevelSelector:
|
||||||
levelSelectorState->update(frameMs);
|
levelSelectorState->update(frameMs);
|
||||||
break;
|
break;
|
||||||
@ -1240,7 +1243,7 @@ int main(int, char **)
|
|||||||
} else if (state == AppState::Loading) {
|
} else if (state == AppState::Loading) {
|
||||||
// Use 3D starfield for loading screen (full screen)
|
// Use 3D starfield for loading screen (full screen)
|
||||||
starfield3D.draw(renderer);
|
starfield3D.draw(renderer);
|
||||||
} else if (state == AppState::Menu || state == AppState::LevelSelector) {
|
} else if (state == AppState::Menu || state == AppState::LevelSelector || state == AppState::Options) {
|
||||||
// Use static background for menu, stretched to window; no starfield on sides
|
// Use static background for menu, stretched to window; no starfield on sides
|
||||||
if (backgroundTex) {
|
if (backgroundTex) {
|
||||||
SDL_FRect fullRect = { 0, 0, (float)winW, (float)winH };
|
SDL_FRect fullRect = { 0, 0, (float)winW, (float)winH };
|
||||||
@ -1350,6 +1353,9 @@ int main(int, char **)
|
|||||||
// Delegate full menu rendering to MenuState object now
|
// Delegate full menu rendering to MenuState object now
|
||||||
menuState->render(renderer, logicalScale, logicalVP);
|
menuState->render(renderer, logicalScale, logicalVP);
|
||||||
break;
|
break;
|
||||||
|
case AppState::Options:
|
||||||
|
optionsState->render(renderer, logicalScale, logicalVP);
|
||||||
|
break;
|
||||||
case AppState::LevelSelector:
|
case AppState::LevelSelector:
|
||||||
// Delegate level selector rendering to LevelSelectorState
|
// Delegate level selector rendering to LevelSelectorState
|
||||||
levelSelectorState->render(renderer, logicalScale, logicalVP);
|
levelSelectorState->render(renderer, logicalScale, logicalVP);
|
||||||
|
|||||||
@ -1181,7 +1181,7 @@ int main(int, char **)
|
|||||||
} else if (state == AppState::Loading) {
|
} else if (state == AppState::Loading) {
|
||||||
// Use 3D starfield for loading screen (full screen)
|
// Use 3D starfield for loading screen (full screen)
|
||||||
starfield3D.draw(renderer);
|
starfield3D.draw(renderer);
|
||||||
} else if (state == AppState::Menu || state == AppState::LevelSelector) {
|
} else if (state == AppState::Menu || state == AppState::LevelSelector || state == AppState::Options) {
|
||||||
// Use static background for menu, stretched to window; no starfield on sides
|
// Use static background for menu, stretched to window; no starfield on sides
|
||||||
if (backgroundTex) {
|
if (backgroundTex) {
|
||||||
SDL_FRect fullRect = { 0, 0, (float)winW, (float)winH };
|
SDL_FRect fullRect = { 0, 0, (float)winW, (float)winH };
|
||||||
|
|||||||
@ -268,8 +268,9 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
|||||||
// Draw bottom action buttons with responsive sizing (reduced to match main mouse hit-test)
|
// Draw bottom action buttons with responsive sizing (reduced to match main mouse hit-test)
|
||||||
// Use the contentW calculated at the top with content offsets
|
// Use the contentW calculated at the top with content offsets
|
||||||
bool isSmall = (contentW < 700.0f);
|
bool isSmall = (contentW < 700.0f);
|
||||||
float btnW = isSmall ? (LOGICAL_W * 0.4f) : 300.0f;
|
float btnW = isSmall ? (LOGICAL_W * 0.32f) : (LOGICAL_W * 0.18f);
|
||||||
float btnH = isSmall ? 60.0f : 70.0f;
|
btnW = std::clamp(btnW, 180.0f, 260.0f); // keep buttons from consuming entire row
|
||||||
|
float btnH = isSmall ? 56.0f : 64.0f;
|
||||||
float btnX = LOGICAL_W * 0.5f + contentOffsetX;
|
float btnX = LOGICAL_W * 0.5f + contentOffsetX;
|
||||||
// Move buttons down by 40px to match original layout (user requested 30-50px)
|
// Move buttons down by 40px to match original layout (user requested 30-50px)
|
||||||
const float btnYOffset = 40.0f;
|
const float btnYOffset = 40.0f;
|
||||||
@ -294,9 +295,13 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
|||||||
SDL_FRect br{ x-6, y-6, w+12, h+12 }; SDL_RenderFillRect(r, &br);
|
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, 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);
|
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; float approxCharW = 12.0f * textScale; float textW = label.length() * approxCharW; float tx = x + (w - textW) / 2.0f; float ty = y + (h - 20.0f * textScale) / 2.0f;
|
float textScale = 1.5f;
|
||||||
font.draw(r, tx+2, ty+2, label, textScale, SDL_Color{0,0,0,200});
|
int textW = 0, textH = 0;
|
||||||
font.draw(r, tx, ty, label, textScale, SDL_Color{255,255,255,255});
|
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 {
|
struct MenuButtonDef {
|
||||||
@ -312,7 +317,7 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
|||||||
MenuButtonDef{ SDL_Color{200,70,70,255}, SDL_Color{150,40,40,255}, "EXIT" }
|
MenuButtonDef{ SDL_Color{200,70,70,255}, SDL_Color{150,40,40,255}, "EXIT" }
|
||||||
};
|
};
|
||||||
|
|
||||||
float spacing = isSmall ? btnW * 1.15f : btnW * 1.05f;
|
float spacing = isSmall ? btnW * 1.2f : btnW * 1.15f;
|
||||||
for (size_t i = 0; i < buttons.size(); ++i) {
|
for (size_t i = 0; i < buttons.size(); ++i) {
|
||||||
float offset = (static_cast<float>(i) - 1.5f) * spacing;
|
float offset = (static_cast<float>(i) - 1.5f) * spacing;
|
||||||
float cx = btnX + offset;
|
float cx = btnX + offset;
|
||||||
@ -327,43 +332,89 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
|||||||
SDL_FRect overlay{contentOffsetX, contentOffsetY, LOGICAL_W, LOGICAL_H};
|
SDL_FRect overlay{contentOffsetX, contentOffsetY, LOGICAL_W, LOGICAL_H};
|
||||||
SDL_RenderFillRect(renderer, &overlay);
|
SDL_RenderFillRect(renderer, &overlay);
|
||||||
|
|
||||||
float popupW = 420.0f;
|
const float panelW = 640.0f;
|
||||||
float popupH = 230.0f;
|
const float panelH = 320.0f;
|
||||||
float popupX = (LOGICAL_W - popupW) * 0.5f + contentOffsetX;
|
SDL_FRect panel{
|
||||||
float popupY = (LOGICAL_H - popupH) * 0.5f + contentOffsetY;
|
(LOGICAL_W - panelW) * 0.5f + contentOffsetX,
|
||||||
|
(LOGICAL_H - panelH) * 0.5f + contentOffsetY,
|
||||||
|
panelW,
|
||||||
|
panelH
|
||||||
|
};
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(renderer, 20, 30, 50, 240);
|
SDL_FRect shadow{panel.x + 6.0f, panel.y + 10.0f, panel.w, panel.h};
|
||||||
SDL_FRect popup{popupX, popupY, popupW, popupH};
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 140);
|
||||||
SDL_RenderFillRect(renderer, &popup);
|
SDL_RenderFillRect(renderer, &shadow);
|
||||||
SDL_SetRenderDrawColor(renderer, 90, 140, 220, 255);
|
for (int i = 0; i < 5; ++i) {
|
||||||
SDL_RenderRect(renderer, &popup);
|
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));
|
||||||
FontAtlas* titleFont = ctx.font ? ctx.font : ctx.pixelFont;
|
SDL_RenderRect(renderer, &glow);
|
||||||
if (titleFont) {
|
|
||||||
titleFont->draw(renderer, popupX + 40.0f, popupY + 30.0f, "EXIT GAME?", 1.8f, SDL_Color{255, 230, 140, 255});
|
|
||||||
titleFont->draw(renderer, popupX + 40.0f, popupY + 80.0f, "Are you sure you want to quit?", 1.1f, SDL_Color{200, 210, 230, 255});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto drawChoice = [&](const char* label, float cx, int idx) {
|
SDL_SetRenderDrawColor(renderer, 18, 30, 52, 255);
|
||||||
float btnW2 = 140.0f;
|
SDL_RenderFillRect(renderer, &panel);
|
||||||
float btnH2 = 50.0f;
|
SDL_SetRenderDrawColor(renderer, 70, 120, 210, 255);
|
||||||
float x = cx - btnW2 / 2.0f;
|
SDL_RenderRect(renderer, &panel);
|
||||||
float y = popupY + popupH - btnH2 - 30.0f;
|
|
||||||
|
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);
|
bool selected = (selection == idx);
|
||||||
SDL_Color bg = selected ? SDL_Color{220, 180, 60, 255} : SDL_Color{80, 110, 160, 255};
|
SDL_Color base = (idx == 0) ? SDL_Color{185, 70, 70, 255} : SDL_Color{60, 95, 150, 255};
|
||||||
SDL_Color border = selected ? SDL_Color{255, 220, 120, 255} : SDL_Color{40, 60, 100, 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_SetRenderDrawColor(renderer, border.r, border.g, border.b, border.a);
|
||||||
SDL_FRect br{ x-4, y-4, btnW2+8, btnH2+8 };
|
SDL_RenderRect(renderer, &bodyRect);
|
||||||
SDL_RenderFillRect(renderer, &br);
|
|
||||||
SDL_SetRenderDrawColor(renderer, bg.r, bg.g, bg.b, bg.a);
|
if (retroFont) {
|
||||||
SDL_FRect body{ x, y, btnW2, btnH2 };
|
const float labelScale = 1.4f;
|
||||||
SDL_RenderFillRect(renderer, &body);
|
int textW = 0, textH = 0;
|
||||||
if (titleFont) {
|
retroFont->measure(label, labelScale, textW, textH);
|
||||||
titleFont->draw(renderer, x + 20.0f, y + 10.0f, label, 1.2f, SDL_Color{15, 20, 35, 255});
|
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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
drawChoice("YES", popupX + popupW * 0.3f, 0);
|
|
||||||
drawChoice("NO", popupX + popupW * 0.7f, 1);
|
float yesX = inner.x + horizontalPad;
|
||||||
|
float noX = yesX + buttonW + buttonGap;
|
||||||
|
drawChoice(0, yesX, "YES");
|
||||||
|
drawChoice(1, noX, "NO");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Popups (settings only - level popup is now a separate state)
|
// Popups (settings only - level popup is now a separate state)
|
||||||
|
|||||||
@ -1,13 +1,16 @@
|
|||||||
#include "OptionsState.h"
|
#include "OptionsState.h"
|
||||||
#include "../core/state/StateManager.h"
|
#include "../core/state/StateManager.h"
|
||||||
#include "../graphics/ui/Font.h"
|
#include "../graphics/ui/Font.h"
|
||||||
|
#include "../audio/Audio.h"
|
||||||
|
#include "../audio/SoundEffect.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
|
||||||
OptionsState::OptionsState(StateContext& ctx) : State(ctx) {}
|
OptionsState::OptionsState(StateContext& ctx) : State(ctx) {}
|
||||||
|
|
||||||
void OptionsState::onEnter() {
|
void OptionsState::onEnter() {
|
||||||
m_selectedField = Field::PlayerName;
|
m_selectedField = Field::Fullscreen;
|
||||||
m_cursorTimer = 0.0;
|
m_cursorTimer = 0.0;
|
||||||
m_cursorVisible = true;
|
m_cursorVisible = true;
|
||||||
if (SDL_Window* focusWin = SDL_GetKeyboardFocus()) {
|
if (SDL_Window* focusWin = SDL_GetKeyboardFocus()) {
|
||||||
@ -40,22 +43,28 @@ void OptionsState::handleEvent(const SDL_Event& e) {
|
|||||||
case SDL_SCANCODE_SPACE:
|
case SDL_SCANCODE_SPACE:
|
||||||
activateSelection();
|
activateSelection();
|
||||||
return;
|
return;
|
||||||
|
case SDL_SCANCODE_F:
|
||||||
|
toggleFullscreen();
|
||||||
|
return;
|
||||||
case SDL_SCANCODE_LEFT:
|
case SDL_SCANCODE_LEFT:
|
||||||
case SDL_SCANCODE_RIGHT:
|
case SDL_SCANCODE_RIGHT:
|
||||||
if (m_selectedField == Field::Fullscreen) {
|
if (m_selectedField == Field::Fullscreen) {
|
||||||
toggleFullscreen();
|
toggleFullscreen();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (m_selectedField == Field::Music) {
|
||||||
|
toggleMusic();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_selectedField == Field::SoundFx) {
|
||||||
|
toggleSoundFx();
|
||||||
|
return;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_selectedField == Field::PlayerName) {
|
|
||||||
handleNameInput(e);
|
|
||||||
}
|
|
||||||
} else if (e.type == SDL_EVENT_TEXT_INPUT && m_selectedField == Field::PlayerName) {
|
|
||||||
handleNameInput(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,12 +89,29 @@ void OptionsState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect l
|
|||||||
float contentOffsetX = (winW - contentW) * 0.5f / logicalScale;
|
float contentOffsetX = (winW - contentW) * 0.5f / logicalScale;
|
||||||
float contentOffsetY = (winH - contentH) * 0.5f / logicalScale;
|
float contentOffsetY = (winH - contentH) * 0.5f / logicalScale;
|
||||||
|
|
||||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
SDL_Texture* logoTexture = ctx.logoSmallTex ? ctx.logoSmallTex : ctx.logoTex;
|
||||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 140);
|
if (logoTexture) {
|
||||||
SDL_FRect dim{contentOffsetX, contentOffsetY, LOGICAL_W, LOGICAL_H};
|
float texW = 0.0f;
|
||||||
SDL_RenderFillRect(renderer, &dim);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const float panelW = 560.0f;
|
const float panelW = 520.0f;
|
||||||
const float panelH = 420.0f;
|
const float panelH = 420.0f;
|
||||||
SDL_FRect panel{
|
SDL_FRect panel{
|
||||||
(LOGICAL_W - panelW) * 0.5f + contentOffsetX,
|
(LOGICAL_W - panelW) * 0.5f + contentOffsetX,
|
||||||
@ -94,121 +120,111 @@ void OptionsState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect l
|
|||||||
panelH
|
panelH
|
||||||
};
|
};
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(renderer, 15, 20, 34, 230);
|
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_RenderFillRect(renderer, &panel);
|
||||||
SDL_SetRenderDrawColor(renderer, 70, 110, 190, 255);
|
SDL_SetRenderDrawColor(renderer, 70, 120, 210, 255);
|
||||||
SDL_RenderRect(renderer, &panel);
|
SDL_RenderRect(renderer, &panel);
|
||||||
|
|
||||||
FontAtlas* titleFont = ctx.pixelFont ? ctx.pixelFont : ctx.font;
|
FontAtlas* retroFont = ctx.pixelFont ? ctx.pixelFont : ctx.font;
|
||||||
FontAtlas* bodyFont = ctx.font ? ctx.font : ctx.pixelFont;
|
|
||||||
|
|
||||||
auto drawText = [&](FontAtlas* font, float x, float y, const std::string& text, float scale, SDL_Color color) {
|
if (!logoTexture && retroFont) {
|
||||||
if (!font) return;
|
retroFont->draw(renderer, panel.x + 24.0f, panel.y + 24.0f, "OPTIONS", 2.0f, {255, 230, 120, 255});
|
||||||
font->draw(renderer, x, y, text, scale, color);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
drawText(titleFont, panel.x + 24.0f, panel.y + 24.0f, "OPTIONS", 2.0f, {255, 230, 120, 255});
|
SDL_FRect inner{panel.x + 20.0f, panel.y + 80.0f, panel.w - 40.0f, panel.h - 120.0f};
|
||||||
|
SDL_SetRenderDrawColor(renderer, 16, 24, 40, 235);
|
||||||
|
SDL_RenderFillRect(renderer, &inner);
|
||||||
|
SDL_SetRenderDrawColor(renderer, 40, 80, 140, 240);
|
||||||
|
SDL_RenderRect(renderer, &inner);
|
||||||
|
|
||||||
|
constexpr int rowCount = 4;
|
||||||
|
const float rowHeight = 60.0f;
|
||||||
|
const float spacing = std::max(0.0f, (inner.h - rowHeight * rowCount) / (rowCount + 1));
|
||||||
|
|
||||||
auto drawField = [&](Field field, float y, const std::string& label, const std::string& value) {
|
auto drawField = [&](Field field, float y, const std::string& label, const std::string& value) {
|
||||||
bool selected = (field == m_selectedField);
|
bool selected = (field == m_selectedField);
|
||||||
SDL_FRect row{panel.x + 20.0f, y - 10.0f, panel.w - 40.0f, 70.0f};
|
SDL_FRect row{inner.x + 12.0f, y, inner.w - 24.0f, rowHeight};
|
||||||
SDL_SetRenderDrawColor(renderer, selected ? 40 : 24, selected ? 80 : 36, selected ? 120 : 48, 220);
|
|
||||||
|
SDL_SetRenderDrawColor(renderer, selected ? 55 : 28, selected ? 90 : 40, selected ? 140 : 60, 235);
|
||||||
SDL_RenderFillRect(renderer, &row);
|
SDL_RenderFillRect(renderer, &row);
|
||||||
SDL_SetRenderDrawColor(renderer, 80, 120, 200, 255);
|
SDL_SetRenderDrawColor(renderer, selected ? 180 : 90, selected ? 210 : 120, 255, 255);
|
||||||
SDL_RenderRect(renderer, &row);
|
SDL_RenderRect(renderer, &row);
|
||||||
|
|
||||||
drawText(bodyFont, row.x + 18.0f, row.y + 12.0f, label, 1.4f, {200, 220, 255, 255});
|
if (retroFont) {
|
||||||
drawText(bodyFont, row.x + 18.0f, row.y + 36.0f, value, 1.6f, {255, 255, 255, 255});
|
SDL_Color labelColor = selected ? SDL_Color{255, 220, 120, 255} : SDL_Color{210, 200, 190, 255};
|
||||||
|
SDL_Color valueColor = selected ? SDL_Color{255, 255, 255, 255} : SDL_Color{215, 230, 250, 255};
|
||||||
|
|
||||||
|
if (!label.empty()) {
|
||||||
|
float labelScale = 1.2f;
|
||||||
|
int labelW = 0;
|
||||||
|
int labelH = 0;
|
||||||
|
retroFont->measure(label, labelScale, labelW, labelH);
|
||||||
|
float labelY = row.y + (row.h - static_cast<float>(labelH)) * 0.5f;
|
||||||
|
retroFont->draw(renderer, row.x + 16.0f, labelY, label, labelScale, labelColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
int valueW = 0, valueH = 0;
|
||||||
|
float valueScale = (field == Field::Back) ? 1.3f : 1.5f;
|
||||||
|
retroFont->measure(value, valueScale, valueW, valueH);
|
||||||
|
bool rightAlign = (field == Field::Fullscreen || field == Field::Music || field == Field::SoundFx);
|
||||||
|
float valX = rightAlign
|
||||||
|
? (row.x + row.w - static_cast<float>(valueW) - 16.0f)
|
||||||
|
: (row.x + (row.w - static_cast<float>(valueW)) * 0.5f);
|
||||||
|
float valY = row.y + (row.h - valueH) * 0.5f;
|
||||||
|
retroFont->draw(renderer, valX, valY, value, valueScale, valueColor);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string nameDisplay = playerName();
|
float rowY = inner.y + spacing;
|
||||||
if (nameDisplay.empty()) {
|
|
||||||
nameDisplay = "<ENTER NAME>";
|
|
||||||
}
|
|
||||||
if (m_selectedField == Field::PlayerName && m_cursorVisible) {
|
|
||||||
nameDisplay.push_back('_');
|
|
||||||
}
|
|
||||||
|
|
||||||
drawField(Field::PlayerName, panel.y + 90.0f, "PLAYER NAME", nameDisplay);
|
drawField(Field::Fullscreen, rowY, "FULLSCREEN", isFullscreen() ? "ON" : "OFF");
|
||||||
|
rowY += rowHeight + spacing;
|
||||||
|
drawField(Field::Music, rowY, "MUSIC", isMusicEnabled() ? "ON" : "OFF");
|
||||||
|
rowY += rowHeight + spacing;
|
||||||
|
drawField(Field::SoundFx, rowY, "SOUND FX", isSoundFxEnabled() ? "ON" : "OFF");
|
||||||
|
rowY += rowHeight + spacing;
|
||||||
|
drawField(Field::Back, rowY, "", "RETURN TO MENU");
|
||||||
|
|
||||||
std::string fullscreenValue = isFullscreen() ? "ON" : "OFF";
|
(void)retroFont; // footer removed for cleaner layout
|
||||||
drawField(Field::Fullscreen, panel.y + 180.0f, "FULLSCREEN", fullscreenValue);
|
|
||||||
|
|
||||||
drawField(Field::Back, panel.y + 270.0f, "BACK", "RETURN TO MENU");
|
|
||||||
|
|
||||||
drawText(bodyFont, panel.x + 24.0f, panel.y + panel.h - 50.0f,
|
|
||||||
"ARROWS = NAV ENTER = SELECT ESC = MENU", 1.1f, {190, 200, 215, 255});
|
|
||||||
drawText(bodyFont, panel.x + 24.0f, panel.y + panel.h - 26.0f,
|
|
||||||
"LETTERS/NUMBERS TYPE INTO NAME FIELD", 1.0f, {150, 160, 180, 255});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OptionsState::moveSelection(int delta) {
|
void OptionsState::moveSelection(int delta) {
|
||||||
int idx = static_cast<int>(m_selectedField);
|
int idx = static_cast<int>(m_selectedField);
|
||||||
int total = 3;
|
int total = static_cast<int>(Field::Back) + 1;
|
||||||
idx = (idx + delta + total) % total;
|
idx = (idx + delta + total) % total;
|
||||||
m_selectedField = static_cast<Field>(idx);
|
m_selectedField = static_cast<Field>(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OptionsState::activateSelection() {
|
void OptionsState::activateSelection() {
|
||||||
switch (m_selectedField) {
|
switch (m_selectedField) {
|
||||||
case Field::PlayerName:
|
|
||||||
// Nothing to do; typing is always enabled
|
|
||||||
break;
|
|
||||||
case Field::Fullscreen:
|
case Field::Fullscreen:
|
||||||
toggleFullscreen();
|
toggleFullscreen();
|
||||||
break;
|
break;
|
||||||
|
case Field::Music:
|
||||||
|
toggleMusic();
|
||||||
|
break;
|
||||||
|
case Field::SoundFx:
|
||||||
|
toggleSoundFx();
|
||||||
|
break;
|
||||||
case Field::Back:
|
case Field::Back:
|
||||||
exitToMenu();
|
exitToMenu();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OptionsState::handleNameInput(const SDL_Event& e) {
|
|
||||||
if (!ctx.playerName) return;
|
|
||||||
|
|
||||||
if (e.type == SDL_EVENT_KEY_DOWN) {
|
|
||||||
if (e.key.scancode == SDL_SCANCODE_BACKSPACE) {
|
|
||||||
removeCharacter();
|
|
||||||
} else if (e.key.scancode == SDL_SCANCODE_SPACE) {
|
|
||||||
addCharacter(' ');
|
|
||||||
} else {
|
|
||||||
SDL_Keymod mods = SDL_GetModState();
|
|
||||||
SDL_Keycode keycode = SDL_GetKeyFromScancode(e.key.scancode, mods, true);
|
|
||||||
bool shift = (mods & SDL_KMOD_SHIFT) != 0;
|
|
||||||
char c = static_cast<char>(keycode);
|
|
||||||
if (keycode >= 'a' && keycode <= 'z') {
|
|
||||||
c = shift ? static_cast<char>(std::toupper(c)) : static_cast<char>(std::toupper(c));
|
|
||||||
addCharacter(c);
|
|
||||||
} else if (keycode >= '0' && keycode <= '9') {
|
|
||||||
addCharacter(static_cast<char>(keycode));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (e.type == SDL_EVENT_TEXT_INPUT) {
|
|
||||||
const char* text = e.text.text;
|
|
||||||
while (*text) {
|
|
||||||
unsigned char c = static_cast<unsigned char>(*text);
|
|
||||||
if (std::isalnum(c) || c == ' ') {
|
|
||||||
addCharacter(static_cast<char>(std::toupper(c)));
|
|
||||||
}
|
|
||||||
++text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OptionsState::addCharacter(char c) {
|
|
||||||
if (!ctx.playerName) return;
|
|
||||||
if (c == '\0') return;
|
|
||||||
if (c == ' ' && ctx.playerName->empty()) return;
|
|
||||||
if (ctx.playerName->size() >= MAX_NAME_LENGTH) return;
|
|
||||||
|
|
||||||
ctx.playerName->push_back(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OptionsState::removeCharacter() {
|
|
||||||
if (!ctx.playerName || ctx.playerName->empty()) return;
|
|
||||||
ctx.playerName->pop_back();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OptionsState::toggleFullscreen() {
|
void OptionsState::toggleFullscreen() {
|
||||||
bool nextState = !isFullscreen();
|
bool nextState = !isFullscreen();
|
||||||
if (ctx.applyFullscreen) {
|
if (ctx.applyFullscreen) {
|
||||||
@ -219,20 +235,38 @@ void OptionsState::toggleFullscreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OptionsState::toggleMusic() {
|
||||||
|
Audio::instance().toggleMute();
|
||||||
|
if (ctx.musicEnabled) {
|
||||||
|
*ctx.musicEnabled = !*ctx.musicEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OptionsState::toggleSoundFx() {
|
||||||
|
bool next = !SoundEffectManager::instance().isEnabled();
|
||||||
|
SoundEffectManager::instance().setEnabled(next);
|
||||||
|
}
|
||||||
|
|
||||||
void OptionsState::exitToMenu() {
|
void OptionsState::exitToMenu() {
|
||||||
if (ctx.stateManager) {
|
if (ctx.stateManager) {
|
||||||
ctx.stateManager->setState(AppState::Menu);
|
ctx.stateManager->setState(AppState::Menu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& OptionsState::playerName() const {
|
|
||||||
static std::string empty;
|
|
||||||
return ctx.playerName ? *ctx.playerName : empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OptionsState::isFullscreen() const {
|
bool OptionsState::isFullscreen() const {
|
||||||
if (ctx.queryFullscreen) {
|
if (ctx.queryFullscreen) {
|
||||||
return ctx.queryFullscreen();
|
return ctx.queryFullscreen();
|
||||||
}
|
}
|
||||||
return ctx.fullscreenFlag ? *ctx.fullscreenFlag : false;
|
return ctx.fullscreenFlag ? *ctx.fullscreenFlag : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool OptionsState::isMusicEnabled() const {
|
||||||
|
if (ctx.musicEnabled) {
|
||||||
|
return *ctx.musicEnabled;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OptionsState::isSoundFxEnabled() const {
|
||||||
|
return SoundEffectManager::instance().isEnabled();
|
||||||
|
}
|
||||||
|
|||||||
@ -13,23 +13,24 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
enum class Field : int {
|
enum class Field : int {
|
||||||
PlayerName = 0,
|
Fullscreen = 0,
|
||||||
Fullscreen = 1,
|
Music = 1,
|
||||||
Back = 2
|
SoundFx = 2,
|
||||||
|
Back = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr int MAX_NAME_LENGTH = 12;
|
static constexpr int MAX_NAME_LENGTH = 12;
|
||||||
Field m_selectedField = Field::PlayerName;
|
Field m_selectedField = Field::Fullscreen;
|
||||||
double m_cursorTimer = 0.0;
|
double m_cursorTimer = 0.0;
|
||||||
bool m_cursorVisible = true;
|
bool m_cursorVisible = true;
|
||||||
|
|
||||||
void moveSelection(int delta);
|
void moveSelection(int delta);
|
||||||
void activateSelection();
|
void activateSelection();
|
||||||
void handleNameInput(const SDL_Event& e);
|
|
||||||
void addCharacter(char c);
|
|
||||||
void removeCharacter();
|
|
||||||
void toggleFullscreen();
|
void toggleFullscreen();
|
||||||
|
void toggleMusic();
|
||||||
|
void toggleSoundFx();
|
||||||
void exitToMenu();
|
void exitToMenu();
|
||||||
const std::string& playerName() const;
|
|
||||||
bool isFullscreen() const;
|
bool isFullscreen() const;
|
||||||
|
bool isMusicEnabled() const;
|
||||||
|
bool isSoundFxEnabled() const;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user