Pages computed layout from SDL_GetWindowSize() (real window coordinates) while SDL_SetRenderLogicalPresentation() independently remapped every draw call into a 1920x1080 space. The two coordinate systems only agreed when the window happened to be exactly 1920x1080; at any other size, layout coordinates were transformed a second time, so content shrank/grew quadratically toward the top-left on resize or on non-1080p monitors. Commenting the call out masked the bug by making rendering 1:1 with the window, but then nothing scaled: fonts and margins stayed fixed-pixel while backgrounds cover-fit, and HiDPI displays drew at point resolution and relied on blurry compositor upsampling. Restore SDL_SetRenderLogicalPresentation(1920x1080, LETTERBOX) as the single source of scaling, and route all twelve layout sites through a new radiantwave::getLayoutSize() helper (src/logical_size.h) that returns the renderer's logical size, so pages always lay out against the design resolution regardless of window or monitor geometry. Add SDL_WINDOW_HIGH_PIXEL_DENSITY so HiDPI displays render at native pixel density instead of being upscaled by the OS. Cached per-page textures become resolution-independent since layout no longer depends on window size.
223 lines
7.6 KiB
C++
223 lines
7.6 KiB
C++
#include "date_format_page.h"
|
|
#include "app_state.h"
|
|
#include "date_format.h"
|
|
#include "logical_size.h"
|
|
#include <SDL3/SDL.h>
|
|
#include <algorithm>
|
|
#include <ctime>
|
|
|
|
namespace {
|
|
|
|
constexpr const char* DB_KEY = "date_format";
|
|
|
|
} // anonymous namespace
|
|
|
|
void DateFormatPage::onPush(AppState& state)
|
|
{
|
|
state_ = &state;
|
|
TTF_Init();
|
|
createFonts();
|
|
|
|
currentFormat_ = state.db.getKV(DB_KEY, radiantwave::datetime::DEFAULT_DATE_FORMAT);
|
|
|
|
const auto options = radiantwave::datetime::getDateFormatOptions();
|
|
selectedIndex_ = 0;
|
|
for (size_t i = 0; i < options.size(); ++i) {
|
|
if (options[i].pattern == currentFormat_) {
|
|
selectedIndex_ = static_cast<int>(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
statusMessage_ = "Pick how the date should look";
|
|
|
|
state.keybinds.bind(SDLK_UP, "date_up", [this]() {
|
|
const auto options = radiantwave::datetime::getDateFormatOptions();
|
|
selectedIndex_ = (selectedIndex_ - 1 + static_cast<int>(options.size())) % static_cast<int>(options.size());
|
|
});
|
|
state.keybinds.bind(SDLK_DOWN, "date_down", [this]() {
|
|
const auto options = radiantwave::datetime::getDateFormatOptions();
|
|
selectedIndex_ = (selectedIndex_ + 1) % static_cast<int>(options.size());
|
|
});
|
|
}
|
|
|
|
void DateFormatPage::onResume(AppState& state)
|
|
{
|
|
state_ = &state;
|
|
createFonts();
|
|
currentFormat_ = state.db.getKV(DB_KEY, radiantwave::datetime::DEFAULT_DATE_FORMAT);
|
|
|
|
state.keybinds.bind(SDLK_UP, "date_up", [this]() {
|
|
const auto options = radiantwave::datetime::getDateFormatOptions();
|
|
selectedIndex_ = (selectedIndex_ - 1 + static_cast<int>(options.size())) % static_cast<int>(options.size());
|
|
});
|
|
state.keybinds.bind(SDLK_DOWN, "date_down", [this]() {
|
|
const auto options = radiantwave::datetime::getDateFormatOptions();
|
|
selectedIndex_ = (selectedIndex_ + 1) % static_cast<int>(options.size());
|
|
});
|
|
}
|
|
|
|
void DateFormatPage::onPause(AppState& state)
|
|
{
|
|
state.keybinds.unbindAll();
|
|
destroyFonts();
|
|
destroyTextures();
|
|
}
|
|
|
|
void DateFormatPage::onPop(AppState& state)
|
|
{
|
|
destroyFonts();
|
|
destroyTextures();
|
|
TTF_Quit();
|
|
}
|
|
|
|
void DateFormatPage::createFonts()
|
|
{
|
|
destroyFonts();
|
|
fontTitle_ = TTF_OpenFont("assets/fonts/Roboto-Regular.ttf", 52);
|
|
fontHeader_ = TTF_OpenFont("assets/fonts/Roboto-Regular.ttf", 28);
|
|
fontBody_ = TTF_OpenFont("assets/fonts/Roboto-Regular.ttf", 32);
|
|
fontSmall_ = TTF_OpenFont("assets/fonts/Roboto-Regular.ttf", 22);
|
|
}
|
|
|
|
void DateFormatPage::destroyFonts()
|
|
{
|
|
if (fontTitle_) { TTF_CloseFont(fontTitle_); fontTitle_ = nullptr; }
|
|
if (fontHeader_) { TTF_CloseFont(fontHeader_); fontHeader_ = nullptr; }
|
|
if (fontBody_) { TTF_CloseFont(fontBody_); fontBody_ = nullptr; }
|
|
if (fontSmall_) { TTF_CloseFont(fontSmall_); fontSmall_ = nullptr; }
|
|
}
|
|
|
|
void DateFormatPage::destroyTextures()
|
|
{
|
|
titleText_.destroy();
|
|
currentText_.destroy();
|
|
statusText_.destroy();
|
|
hintText_.destroy();
|
|
for (auto& t : nameRows_) t.destroy();
|
|
nameRows_.clear();
|
|
for (auto& t : sampleRows_) t.destroy();
|
|
sampleRows_.clear();
|
|
}
|
|
|
|
void DateFormatPage::handleEvent(const SDL_Event& event)
|
|
{
|
|
if (!state_) return;
|
|
|
|
if (event.type == SDL_EVENT_KEY_DOWN) {
|
|
SDL_Keycode key = event.key.key;
|
|
const auto options = radiantwave::datetime::getDateFormatOptions();
|
|
|
|
if (key == SDLK_RETURN || key == SDLK_KP_ENTER) {
|
|
if (selectedIndex_ >= 0 && selectedIndex_ < static_cast<int>(options.size())) {
|
|
std::string pattern = options[selectedIndex_].pattern;
|
|
if (state_->db.setKV(DB_KEY, pattern)) {
|
|
currentFormat_ = pattern;
|
|
statusMessage_ = "Date format saved";
|
|
state_->pages.pop(*state_);
|
|
} else {
|
|
statusMessage_ = "Failed to save date format";
|
|
}
|
|
}
|
|
} else if (key == SDLK_ESCAPE) {
|
|
state_->pages.pop(*state_);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DateFormatPage::update(float dt)
|
|
{
|
|
}
|
|
|
|
void DateFormatPage::render(SDL_Renderer* renderer)
|
|
{
|
|
int winW, winH;
|
|
radiantwave::getLayoutSize(renderer, &winW, &winH);
|
|
float fw = static_cast<float>(winW);
|
|
float fh = static_cast<float>(winH);
|
|
|
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
|
SDL_SetRenderDrawColor(renderer, 20, 20, 30, 255);
|
|
SDL_FRect bgRect{0, 0, fw, fh};
|
|
SDL_RenderFillRect(renderer, &bgRect);
|
|
|
|
if (!fontTitle_ || !fontHeader_ || !fontBody_ || !fontSmall_) return;
|
|
|
|
SDL_Color white = {255, 255, 255, 255};
|
|
SDL_Color yellow = {255, 255, 0, 255};
|
|
SDL_Color cyan = {100, 220, 255, 255};
|
|
SDL_Color gray = {180, 180, 180, 255};
|
|
|
|
const float sharedX = 100.0f;
|
|
float currentY = 30.0f;
|
|
|
|
titleText_.ensure(renderer, fontTitle_, "Date Format", white);
|
|
if (titleText_.texture) {
|
|
float x = (fw - titleText_.width) / 2.0f;
|
|
SDL_FRect dst{x, currentY, titleText_.width, titleText_.height};
|
|
SDL_RenderTexture(renderer, titleText_.texture, nullptr, &dst);
|
|
currentY += titleText_.height + 30.0f;
|
|
}
|
|
|
|
{
|
|
std::string saved = radiantwave::datetime::formatDate(std::time(nullptr), currentFormat_);
|
|
currentText_.ensure(renderer, fontHeader_, "Today looks like: " + saved, yellow);
|
|
if (currentText_.texture) {
|
|
SDL_FRect dst{sharedX, currentY, currentText_.width, currentText_.height};
|
|
SDL_RenderTexture(renderer, currentText_.texture, nullptr, &dst);
|
|
}
|
|
currentY += 50.0f;
|
|
}
|
|
|
|
const auto options = radiantwave::datetime::getDateFormatOptions();
|
|
const float margin = 12.0f;
|
|
const float rowPitch = 50.0f;
|
|
const time_t now = std::time(nullptr);
|
|
|
|
if (nameRows_.size() != options.size()) nameRows_.resize(options.size());
|
|
if (sampleRows_.size() != options.size()) sampleRows_.resize(options.size());
|
|
|
|
for (int i = 0; i < static_cast<int>(options.size()); ++i) {
|
|
std::string label = options[i].name;
|
|
std::string example = radiantwave::datetime::formatDate(now, options[i].pattern);
|
|
|
|
SDL_Color color = (options[i].pattern == currentFormat_) ? yellow : white;
|
|
nameRows_[i].ensure(renderer, fontBody_, label, color);
|
|
if (nameRows_[i].texture) {
|
|
SDL_FRect dst{sharedX, currentY, nameRows_[i].width, nameRows_[i].height};
|
|
SDL_RenderTexture(renderer, nameRows_[i].texture, nullptr, &dst);
|
|
}
|
|
|
|
sampleRows_[i].ensure(renderer, fontSmall_, example, gray);
|
|
if (sampleRows_[i].texture) {
|
|
SDL_FRect dst{sharedX + 340.0f, currentY + 8.0f, sampleRows_[i].width, sampleRows_[i].height};
|
|
SDL_RenderTexture(renderer, sampleRows_[i].texture, nullptr, &dst);
|
|
}
|
|
|
|
if (i == selectedIndex_) {
|
|
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
|
|
SDL_FRect line{sharedX, currentY + nameRows_[i].height + 2.0f, nameRows_[i].width, 6.0f};
|
|
SDL_RenderFillRect(renderer, &line);
|
|
}
|
|
currentY += rowPitch;
|
|
}
|
|
|
|
currentY += 20.0f;
|
|
|
|
if (!statusMessage_.empty()) {
|
|
statusText_.ensure(renderer, fontHeader_, statusMessage_, cyan);
|
|
if (statusText_.texture) {
|
|
SDL_FRect dst{sharedX, currentY, statusText_.width, statusText_.height};
|
|
SDL_RenderTexture(renderer, statusText_.texture, nullptr, &dst);
|
|
}
|
|
}
|
|
|
|
hintText_.ensure(renderer, fontSmall_,
|
|
"[UP/DOWN] Choose [ENTER] Apply & Back [ESC] Back", gray);
|
|
if (hintText_.texture) {
|
|
float x = (fw - hintText_.width) / 2.0f;
|
|
SDL_FRect dst{x, fh - 40.0f, hintText_.width, hintText_.height};
|
|
SDL_RenderTexture(renderer, hintText_.texture, nullptr, &dst);
|
|
}
|
|
}
|