Retro exit modal styling and shortcuts
This commit is contained in:
@ -1,13 +1,16 @@
|
||||
#include "OptionsState.h"
|
||||
#include "../core/state/StateManager.h"
|
||||
#include "../graphics/ui/Font.h"
|
||||
#include "../audio/Audio.h"
|
||||
#include "../audio/SoundEffect.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
OptionsState::OptionsState(StateContext& ctx) : State(ctx) {}
|
||||
|
||||
void OptionsState::onEnter() {
|
||||
m_selectedField = Field::PlayerName;
|
||||
m_selectedField = Field::Fullscreen;
|
||||
m_cursorTimer = 0.0;
|
||||
m_cursorVisible = true;
|
||||
if (SDL_Window* focusWin = SDL_GetKeyboardFocus()) {
|
||||
@ -40,22 +43,28 @@ void OptionsState::handleEvent(const SDL_Event& e) {
|
||||
case SDL_SCANCODE_SPACE:
|
||||
activateSelection();
|
||||
return;
|
||||
case SDL_SCANCODE_F:
|
||||
toggleFullscreen();
|
||||
return;
|
||||
case SDL_SCANCODE_LEFT:
|
||||
case SDL_SCANCODE_RIGHT:
|
||||
if (m_selectedField == Field::Fullscreen) {
|
||||
toggleFullscreen();
|
||||
return;
|
||||
}
|
||||
if (m_selectedField == Field::Music) {
|
||||
toggleMusic();
|
||||
return;
|
||||
}
|
||||
if (m_selectedField == Field::SoundFx) {
|
||||
toggleSoundFx();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
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 contentOffsetY = (winH - contentH) * 0.5f / logicalScale;
|
||||
|
||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 140);
|
||||
SDL_FRect dim{contentOffsetX, contentOffsetY, LOGICAL_W, LOGICAL_H};
|
||||
SDL_RenderFillRect(renderer, &dim);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
const float panelW = 560.0f;
|
||||
const float panelW = 520.0f;
|
||||
const float panelH = 420.0f;
|
||||
SDL_FRect panel{
|
||||
(LOGICAL_W - panelW) * 0.5f + contentOffsetX,
|
||||
@ -94,121 +120,111 @@ void OptionsState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect l
|
||||
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_SetRenderDrawColor(renderer, 70, 110, 190, 255);
|
||||
SDL_SetRenderDrawColor(renderer, 70, 120, 210, 255);
|
||||
SDL_RenderRect(renderer, &panel);
|
||||
|
||||
FontAtlas* titleFont = ctx.pixelFont ? ctx.pixelFont : ctx.font;
|
||||
FontAtlas* bodyFont = ctx.font ? ctx.font : ctx.pixelFont;
|
||||
FontAtlas* retroFont = ctx.pixelFont ? ctx.pixelFont : ctx.font;
|
||||
|
||||
auto drawText = [&](FontAtlas* font, float x, float y, const std::string& text, float scale, SDL_Color color) {
|
||||
if (!font) return;
|
||||
font->draw(renderer, x, y, text, scale, color);
|
||||
};
|
||||
if (!logoTexture && retroFont) {
|
||||
retroFont->draw(renderer, panel.x + 24.0f, panel.y + 24.0f, "OPTIONS", 2.0f, {255, 230, 120, 255});
|
||||
}
|
||||
|
||||
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) {
|
||||
bool selected = (field == m_selectedField);
|
||||
SDL_FRect row{panel.x + 20.0f, y - 10.0f, panel.w - 40.0f, 70.0f};
|
||||
SDL_SetRenderDrawColor(renderer, selected ? 40 : 24, selected ? 80 : 36, selected ? 120 : 48, 220);
|
||||
SDL_FRect row{inner.x + 12.0f, y, inner.w - 24.0f, rowHeight};
|
||||
|
||||
SDL_SetRenderDrawColor(renderer, selected ? 55 : 28, selected ? 90 : 40, selected ? 140 : 60, 235);
|
||||
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);
|
||||
|
||||
drawText(bodyFont, row.x + 18.0f, row.y + 12.0f, label, 1.4f, {200, 220, 255, 255});
|
||||
drawText(bodyFont, row.x + 18.0f, row.y + 36.0f, value, 1.6f, {255, 255, 255, 255});
|
||||
if (retroFont) {
|
||||
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();
|
||||
if (nameDisplay.empty()) {
|
||||
nameDisplay = "<ENTER NAME>";
|
||||
}
|
||||
if (m_selectedField == Field::PlayerName && m_cursorVisible) {
|
||||
nameDisplay.push_back('_');
|
||||
}
|
||||
float rowY = inner.y + spacing;
|
||||
|
||||
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";
|
||||
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)retroFont; // footer removed for cleaner layout
|
||||
}
|
||||
|
||||
void OptionsState::moveSelection(int delta) {
|
||||
int idx = static_cast<int>(m_selectedField);
|
||||
int total = 3;
|
||||
int total = static_cast<int>(Field::Back) + 1;
|
||||
idx = (idx + delta + total) % total;
|
||||
m_selectedField = static_cast<Field>(idx);
|
||||
}
|
||||
|
||||
void OptionsState::activateSelection() {
|
||||
switch (m_selectedField) {
|
||||
case Field::PlayerName:
|
||||
// Nothing to do; typing is always enabled
|
||||
break;
|
||||
case Field::Fullscreen:
|
||||
toggleFullscreen();
|
||||
break;
|
||||
case Field::Music:
|
||||
toggleMusic();
|
||||
break;
|
||||
case Field::SoundFx:
|
||||
toggleSoundFx();
|
||||
break;
|
||||
case Field::Back:
|
||||
exitToMenu();
|
||||
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() {
|
||||
bool nextState = !isFullscreen();
|
||||
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() {
|
||||
if (ctx.stateManager) {
|
||||
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 {
|
||||
if (ctx.queryFullscreen) {
|
||||
return ctx.queryFullscreen();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user