#include "settings.h" #include "app_state.h" #include "home.h" #include "license_blocked_page.h" #include "wifi_setup_page.h" #include "audio_settings_page.h" #include "timezone_page.h" #include "log_viewer_page.h" #include "proclamation_options_page.h" #include "email_address_page.h" #include "license_key_page.h" #include "date_format_page.h" #include #include #include namespace { constexpr int MENU_ITEM_COUNT = 9; // 0 = WiFi Setup, 1 = Audio Options, 2 = Time Zone Setup, 3 = Proclamations, // 4 = Email Address, 5 = License Key, 6 = Date Format, // 7 = View System Logs, 8 = Back constexpr const char *MENU_LABELS[MENU_ITEM_COUNT] = { "WiFi Setup", "Audio Options", "Time Zone Setup", "Proclamations", "Email Address", "License Key", "Date Format", "View System Logs", "Back to Home", }; } // anonymous namespace void Settings::onPush(AppState &state) { state_ = &state; TTF_Init(); createTextures(state); state.keybinds.bind(SDLK_UP, "menu_up", [this]() { selectedItem_ = (selectedItem_ - 1 + MENU_ITEM_COUNT) % MENU_ITEM_COUNT; }); state.keybinds.bind(SDLK_DOWN, "menu_down", [this]() { selectedItem_ = (selectedItem_ + 1) % MENU_ITEM_COUNT; }); } void Settings::onResume(AppState &state) { state_ = &state; createTextures(state); state.keybinds.bind(SDLK_UP, "menu_up", [this]() { selectedItem_ = (selectedItem_ - 1 + MENU_ITEM_COUNT) % MENU_ITEM_COUNT; }); state.keybinds.bind(SDLK_DOWN, "menu_down", [this]() { selectedItem_ = (selectedItem_ + 1) % MENU_ITEM_COUNT; }); } void Settings::onPause(AppState &state) { state.keybinds.unbindAll(); destroyTextures(); } void Settings::onPop(AppState &state) { destroyTextures(); TTF_Quit(); } void Settings::handleEvent(const SDL_Event &event) { if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_RETURN) { switch (selectedItem_) { case 0: state_->pages.push(*state_, std::make_unique()); break; case 1: state_->pages.push(*state_, std::make_unique()); break; case 2: state_->pages.push(*state_, std::make_unique()); break; case 3: // The Proclamation list is license-gated like Play. if (state_->licenseTracker.isValid()) { state_->pages.push(*state_, std::make_unique()); } else { state_->pages.push(*state_, std::make_unique()); } break; case 4: state_->pages.push(*state_, std::make_unique()); break; case 5: state_->pages.push(*state_, std::make_unique()); break; case 6: state_->pages.push(*state_, std::make_unique()); break; case 7: state_->pages.push(*state_, std::make_unique()); break; case 8: state_->pages.replace(*state_, std::make_unique()); break; default: break; } } else if (event.type == SDL_EVENT_KEY_DOWN && (event.key.key == SDLK_ESCAPE || event.key.key == SDLK_TAB)) { state_->pages.replace(*state_, std::make_unique()); } } void Settings::update(float dt) { } void Settings::render(SDL_Renderer *renderer) { int winW, winH; SDL_GetWindowSize(SDL_GetRenderWindow(renderer), &winW, &winH); float fw = static_cast(winW); float fh = static_cast(winH); // 1. Logo Background Image if (bgTexture_) { float scaleX = fw / bgWidth_; float scaleY = fh / bgHeight_; float scale = std::min(scaleX, scaleY); float dw = bgWidth_ * scale; float dh = bgHeight_ * scale; float dx = (fw - dw) / 2.0f; float dy = (fh - dh) / 2.0f; SDL_FRect bgDst{dx, dy, dw, dh}; SDL_RenderTexture(renderer, bgTexture_, nullptr, &bgDst); } // 2. Dark Overlay SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 160); SDL_FRect overlayRect{0, 0, fw, fh}; SDL_RenderFillRect(renderer, &overlayRect); // 3. Title Text float titleY = 60.0f; if (titleTexture_) { float x = (fw - titleWidth_) / 2.0f; SDL_FRect dst{x, titleY, titleWidth_, titleHeight_}; SDL_RenderTexture(renderer, titleTexture_, nullptr, &dst); } // Compute bounding box for all menu items float maxWidth = 0.0f; for (const auto &item : items_) { maxWidth = std::max(maxWidth, item.width); } float margin = 12.0f; // 12px vertical margin rule // Shared left edge for left-aligned items with centered bounding box float sharedX = (fw - maxWidth) / 2.0f; // Start menu list exactly 24 pixels below title text float currentY = titleY + titleHeight_ + 24.0f; for (auto &item : items_) { item.y = currentY; if (item.texture) { SDL_FRect dst{sharedX, item.y, item.width, item.height}; SDL_RenderTexture(renderer, item.texture, nullptr, &dst); } currentY += item.height + margin; } // 6px Underline for selected item occupying half of the 12px margin space if (selectedItem_ >= 0 && selectedItem_ < static_cast(items_.size())) { const auto &item = items_[selectedItem_]; if (item.texture) { SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); SDL_FRect line{sharedX, item.y + item.height + 2.0f, item.width, 6.0f}; SDL_RenderFillRect(renderer, &line); } } } void Settings::createTextures(AppState &state) { destroyTextures(); // Background Image SDL_Surface *bgSurface = IMG_Load("assets/images/RWT-Circular-Color-ForOnDark-NoText.png"); if (bgSurface) { bgTexture_ = SDL_CreateTextureFromSurface(state.renderer, bgSurface); bgWidth_ = static_cast(bgSurface->w); bgHeight_ = static_cast(bgSurface->h); SDL_DestroySurface(bgSurface); SDL_SetTextureAlphaMod(bgTexture_, 204); } SDL_Color white = {255, 255, 255, 255}; // Title Font TTF_Font *fontLarge = TTF_OpenFont("assets/fonts/Roboto-Regular.ttf", 64); if (fontLarge) { SDL_Surface *surface = TTF_RenderText_Blended(fontLarge, "Settings", 0, white); if (surface) { titleTexture_ = SDL_CreateTextureFromSurface(state.renderer, surface); titleWidth_ = static_cast(surface->w); titleHeight_ = static_cast(surface->h); SDL_DestroySurface(surface); } TTF_CloseFont(fontLarge); } // Item Font TTF_Font *fontItem = TTF_OpenFont("assets/fonts/Roboto-Regular.ttf", 36); if (!fontItem) return; items_.reserve(MENU_ITEM_COUNT); for (int i = 0; i < MENU_ITEM_COUNT; ++i) { MenuItem item; item.label = MENU_LABELS[i]; SDL_Surface *surface = TTF_RenderText_Blended(fontItem, MENU_LABELS[i], 0, white); if (surface) { item.texture = SDL_CreateTextureFromSurface(state.renderer, surface); item.width = static_cast(surface->w); item.height = static_cast(surface->h); SDL_DestroySurface(surface); } items_.push_back(std::move(item)); } TTF_CloseFont(fontItem); } void Settings::destroyTextures() { if (bgTexture_) { SDL_DestroyTexture(bgTexture_); bgTexture_ = nullptr; } if (titleTexture_) { SDL_DestroyTexture(titleTexture_); titleTexture_ = nullptr; } for (auto &item : items_) { if (item.texture) { SDL_DestroyTexture(item.texture); item.texture = nullptr; } } items_.clear(); }