added buttons to main state
This commit is contained in:
238
src/states/OptionsState.cpp
Normal file
238
src/states/OptionsState.cpp
Normal file
@ -0,0 +1,238 @@
|
||||
#include "OptionsState.h"
|
||||
#include "../core/state/StateManager.h"
|
||||
#include "../graphics/ui/Font.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <cctype>
|
||||
|
||||
OptionsState::OptionsState(StateContext& ctx) : State(ctx) {}
|
||||
|
||||
void OptionsState::onEnter() {
|
||||
m_selectedField = Field::PlayerName;
|
||||
m_cursorTimer = 0.0;
|
||||
m_cursorVisible = true;
|
||||
if (SDL_Window* focusWin = SDL_GetKeyboardFocus()) {
|
||||
SDL_StartTextInput(focusWin);
|
||||
}
|
||||
}
|
||||
|
||||
void OptionsState::onExit() {
|
||||
if (SDL_Window* focusWin = SDL_GetKeyboardFocus()) {
|
||||
SDL_StopTextInput(focusWin);
|
||||
}
|
||||
}
|
||||
|
||||
void OptionsState::handleEvent(const SDL_Event& e) {
|
||||
if (e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
|
||||
switch (e.key.scancode) {
|
||||
case SDL_SCANCODE_ESCAPE:
|
||||
exitToMenu();
|
||||
return;
|
||||
case SDL_SCANCODE_UP:
|
||||
case SDL_SCANCODE_W:
|
||||
moveSelection(-1);
|
||||
return;
|
||||
case SDL_SCANCODE_DOWN:
|
||||
case SDL_SCANCODE_S:
|
||||
moveSelection(1);
|
||||
return;
|
||||
case SDL_SCANCODE_RETURN:
|
||||
case SDL_SCANCODE_KP_ENTER:
|
||||
case SDL_SCANCODE_SPACE:
|
||||
activateSelection();
|
||||
return;
|
||||
case SDL_SCANCODE_LEFT:
|
||||
case SDL_SCANCODE_RIGHT:
|
||||
if (m_selectedField == Field::Fullscreen) {
|
||||
toggleFullscreen();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void OptionsState::update(double frameMs) {
|
||||
m_cursorTimer += frameMs;
|
||||
if (m_cursorTimer >= 450.0) {
|
||||
m_cursorTimer = 0.0;
|
||||
m_cursorVisible = !m_cursorVisible;
|
||||
}
|
||||
}
|
||||
|
||||
void OptionsState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) {
|
||||
if (!renderer) return;
|
||||
|
||||
const float LOGICAL_W = 1200.0f;
|
||||
const float LOGICAL_H = 1000.0f;
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
const float panelW = 560.0f;
|
||||
const float panelH = 420.0f;
|
||||
SDL_FRect panel{
|
||||
(LOGICAL_W - panelW) * 0.5f + contentOffsetX,
|
||||
(LOGICAL_H - panelH) * 0.5f + contentOffsetY,
|
||||
panelW,
|
||||
panelH
|
||||
};
|
||||
|
||||
SDL_SetRenderDrawColor(renderer, 15, 20, 34, 230);
|
||||
SDL_RenderFillRect(renderer, &panel);
|
||||
SDL_SetRenderDrawColor(renderer, 70, 110, 190, 255);
|
||||
SDL_RenderRect(renderer, &panel);
|
||||
|
||||
FontAtlas* titleFont = 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 (!font) return;
|
||||
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});
|
||||
|
||||
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_RenderFillRect(renderer, &row);
|
||||
SDL_SetRenderDrawColor(renderer, 80, 120, 200, 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});
|
||||
};
|
||||
|
||||
std::string nameDisplay = playerName();
|
||||
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);
|
||||
|
||||
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 OptionsState::moveSelection(int delta) {
|
||||
int idx = static_cast<int>(m_selectedField);
|
||||
int total = 3;
|
||||
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::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) {
|
||||
ctx.applyFullscreen(nextState);
|
||||
}
|
||||
if (ctx.fullscreenFlag) {
|
||||
*ctx.fullscreenFlag = nextState;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user