From bc3f69e9d6d36a5fafeda7e0ff07b834426b7bcb Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Sun, 17 Aug 2025 09:44:23 +0200 Subject: [PATCH] Fixed selected level state --- src/main.cpp | 7 +- src/states/LevelSelectorState.cpp | 434 +++++++++++++++++------------- src/states/LevelSelectorState.h | 3 + 3 files changed, 252 insertions(+), 192 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 2992cf2..66099ec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1096,7 +1096,8 @@ int main(int, char **) // Draw blended backgrounds if needed if (levelBackgroundTex || nextLevelBackgroundTex) { - SDL_FRect fullRect = { 0, 0, (float)logicalVP.w, (float)logicalVP.h }; + // Use actual window pixel size so backgrounds always cover full screen + SDL_FRect fullRect = { 0, 0, (float)winW, (float)winH }; // if fade in progress if (nextLevelBackgroundTex && levelFadeAlpha < 1.0f && levelBackgroundTex) { // draw current with inverse alpha @@ -1123,10 +1124,10 @@ int main(int, char **) } else if (state == AppState::Loading) { // Use 3D starfield for loading screen (full screen) starfield3D.draw(renderer); - } else if (state == AppState::Menu) { + } else if (state == AppState::Menu || state == AppState::LevelSelector) { // Use static background for menu, stretched to window; no starfield on sides if (backgroundTex) { - SDL_FRect fullRect = { 0, 0, (float)logicalVP.w, (float)logicalVP.h }; + SDL_FRect fullRect = { 0, 0, (float)winW, (float)winH }; SDL_RenderTexture(renderer, backgroundTex, nullptr, &fullRect); } } else { diff --git a/src/states/LevelSelectorState.cpp b/src/states/LevelSelectorState.cpp index 450eeb6..739c18a 100644 --- a/src/states/LevelSelectorState.cpp +++ b/src/states/LevelSelectorState.cpp @@ -6,17 +6,152 @@ #include #include #include +#include // Constants from main.cpp static constexpr int LOGICAL_W = 1200; static constexpr int LOGICAL_H = 1000; -// Helper function to draw rectangles (matches main.cpp implementation) -static void drawRect(SDL_Renderer *r, float x, float y, float w, float h, SDL_Color c) -{ +// --- Minimal draw helpers and look-and-feel adapted from the sample --- +static inline SDL_Color RGBA(Uint8 r, Uint8 g, Uint8 b, Uint8 a = 255) { return SDL_Color{r, g, b, a}; } + +// Palette +static const SDL_Color COL_BG = {11, 15, 20, 255}; +static const SDL_Color COL_PANEL = {20, 40, 60, 255}; +static const SDL_Color COL_PANEL_IN = {26, 46, 66, 255}; +static const SDL_Color COL_CYAN = {0, 255, 255, 200}; +static const SDL_Color COL_CYAN_SO = {0, 255, 255, 32}; +static const SDL_Color COL_TILE = {30, 40, 60, 255}; +static const SDL_Color COL_TILE_H = {60, 80, 100, 255}; +static const SDL_Color COL_TILE_B = {74, 94, 118, 220}; +static const SDL_Color COL_NUM = {233, 241, 255, 255}; +static const SDL_Color COL_ACCENT = {255, 140, 40, 255}; +static const SDL_Color COL_TITLE = {255, 200, 50, 255}; +static const SDL_Color COL_FOOTER = {154, 167, 178, 255}; + +static void FillRect(SDL_Renderer* r, SDL_FRect rc, SDL_Color c) { + SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(r, c.r, c.g, c.b, c.a); - SDL_FRect fr{x, y, w, h}; - SDL_RenderFillRect(r, &fr); + SDL_RenderFillRect(r, &rc); +} +static void StrokeRect(SDL_Renderer* r, SDL_FRect rc, SDL_Color c) { + SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(r, c.r, c.g, c.b, c.a); + SDL_RenderRect(r, &rc); +} +static void Line(SDL_Renderer* r, float x1, float y1, float x2, float y2, SDL_Color c) { + SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(r, c.r, c.g, c.b, c.a); + SDL_RenderLine(r, x1, y1, x2, y2); +} + +// Bitmap-like digits for cell numbers (keeps consistent look without relying on font inside cells) +static void DrawDigit(SDL_Renderer* r, int num, float cx, float cy) { + SDL_SetRenderDrawColor(r, COL_NUM.r, COL_NUM.g, COL_NUM.b, COL_NUM.a); + auto glyph = [&](int d, float ox) { + static const int map[10][15] = { + {1,1,1, 1,0,1, 1,0,1, 1,0,1, 1,1,1}, + {0,1,0, 1,1,0, 0,1,0, 0,1,0, 1,1,1}, + {1,1,1, 0,0,1, 1,1,1, 1,0,0, 1,1,1}, + {1,1,1, 0,0,1, 0,1,1, 0,0,1, 1,1,1}, + {1,0,1, 1,0,1, 1,1,1, 0,0,1, 0,0,1}, + {1,1,1, 1,0,0, 1,1,1, 0,0,1, 1,1,1}, + {1,1,1, 1,0,0, 1,1,1, 1,0,1, 1,1,1}, + {1,1,1, 0,0,1, 0,1,0, 0,1,0, 0,1,0}, + {1,1,1, 1,0,1, 1,1,1, 1,0,1, 1,1,1}, + {1,1,1, 1,0,1, 1,1,1, 0,0,1, 1,1,1} + }; + float s = 5.f; float gx = cx + ox - 6.f, gy = cy - 12.f; + for (int y = 0; y < 5; y++) for (int x = 0; x < 3; x++) + if (map[d][y * 3 + x]) { + SDL_FRect p{gx + x * s, gy + y * s, s - 1.f, s - 1.f}; + SDL_RenderFillRect(r, &p); + } + }; + if (num < 10) glyph(num, 0); + else { glyph(num / 10, -8); glyph(num % 10, 8); } +} + +// Centered text using project Font, with optional shadow +static void DrawText(SDL_Renderer* r, FontAtlas* font, const std::string& s, + float x, float y, float scale, SDL_Color col, + bool center = true, bool shadow = true) { + if (!font) return; + int w = 0, h = 0; font->measure(s, scale, w, h); + float tx = x, ty = y; + if (center) tx -= (float)w / 2.0f; + if (shadow) { + font->draw(r, tx + 2.0f, ty + 2.0f, s, scale, {0, 0, 0, 200}); + } + font->draw(r, tx, ty, s, scale, col); +} + +static void Vignette(SDL_Renderer* r, int w, int h) { + int pad = w / 10; + FillRect(r, SDL_FRect{0, 0, (float)w, (float)pad}, SDL_Color{0, 0, 0, 140}); + FillRect(r, SDL_FRect{0, (float)h - pad, (float)w, (float)pad}, SDL_Color{0, 0, 0, 140}); + FillRect(r, SDL_FRect{0, 0, (float)pad, (float)h}, SDL_Color{0, 0, 0, 140}); + FillRect(r, SDL_FRect{(float)w - pad, 0, (float)pad, (float)h}, SDL_Color{0, 0, 0, 140}); +} + +static SDL_FRect DrawPanel(SDL_Renderer* r, float w, float h, bool draw = true, float offX = 0.f, float offY = 0.f) { + float PW = std::min(520.f, w * 0.65f); + float PH = std::min(360.f, h * 0.7f); + SDL_FRect p{ (w - PW) / 2.f + offX, (h - PH) / 2.f + 10.f + offY, PW, PH }; + + if (!draw) return p; // geometry only + + // drop shadow + FillRect(r, SDL_FRect{p.x + 6, p.y + 10, p.w, p.h}, SDL_Color{0, 0, 0, 120}); + // glow aura + for (int i = 0; i < 6; i++) { + SDL_FRect g{ p.x - (float)(i * 2), p.y - (float)(i * 2), p.w + (float)(i * 4), p.h + (float)(i * 4) }; + SDL_Color c = COL_CYAN_SO; c.a = (Uint8)(36 - i * 6); + StrokeRect(r, g, c); + } + // outer body + border + FillRect(r, p, COL_PANEL); + StrokeRect(r, p, COL_CYAN); + + // inner face + FillRect(r, SDL_FRect{p.x + 12, p.y + 56, p.w - 24, p.h - 68}, COL_PANEL_IN); + StrokeRect(r, SDL_FRect{p.x + 12, p.y + 56, p.w - 24, p.h - 68}, SDL_Color{24, 31, 41, 180}); + return p; +} + +struct Grid { + int cols = 4, rows = 5; + float cellW = 0.f, cellH = 0.f, gapX = 12.f, gapY = 12.f; + SDL_FRect area{0, 0, 0, 0}; + SDL_FRect cell(int i) const { + int r = i / cols, c = i % cols; + return SDL_FRect{ area.x + c * (cellW + gapX), area.y + r * (cellH + gapY), cellW, cellH }; + } +}; + +static Grid MakeGrid(const SDL_FRect& panel) { + Grid g; + float marginX = 34, marginY = 76; + g.area = SDL_FRect{ panel.x + marginX, panel.y + marginY, panel.w - 2 * marginX, panel.h - marginY - 28 }; + g.cellW = (g.area.w - (g.cols - 1) * g.gapX) / g.cols; + g.cellH = (g.area.h - (g.rows - 1) * g.gapY) / g.rows; + return g; +} + +static void DrawCell(SDL_Renderer* r, SDL_FRect rc, int idx, bool hovered, bool selected) { + FillRect(r, rc, selected ? COL_ACCENT : (hovered ? COL_TILE_H : COL_TILE)); + StrokeRect(r, rc, COL_TILE_B); + Line(r, rc.x + 2, rc.y + 2, rc.x + rc.w - 2, rc.y + 2, SDL_Color{255, 255, 255, 18}); + Line(r, rc.x + 2, rc.y + rc.h - 2, rc.x + rc.w - 2, rc.y + rc.h - 2, SDL_Color{0, 0, 0, 40}); + DrawDigit(r, idx, rc.x + rc.w / 2.f, rc.y + rc.h / 2.f); +} + +static int HitTest(const Grid& g, int mx, int my) { + for (int i = 0; i < 20; i++) { + SDL_FRect rc = g.cell(i); + if (mx >= rc.x && mx < rc.x + rc.w && my >= rc.y && my < rc.y + rc.h) return i; + } + return -1; } LevelSelectorState::LevelSelectorState(StateContext& ctx) : State(ctx) { @@ -32,42 +167,54 @@ void LevelSelectorState::onExit() { void LevelSelectorState::handleEvent(const SDL_Event& e) { if (e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) { - // Arrow key navigation - if (e.key.scancode == SDL_SCANCODE_LEFT) { - hoveredLevel = (hoveredLevel - 1 + 20) % 20; - if (ctx.startLevelSelection) *ctx.startLevelSelection = hoveredLevel; - } else if (e.key.scancode == SDL_SCANCODE_RIGHT) { - hoveredLevel = (hoveredLevel + 1) % 20; - if (ctx.startLevelSelection) *ctx.startLevelSelection = hoveredLevel; - } else if (e.key.scancode == SDL_SCANCODE_UP) { - hoveredLevel = (hoveredLevel - 4 + 20) % 20; - if (ctx.startLevelSelection) *ctx.startLevelSelection = hoveredLevel; - } else if (e.key.scancode == SDL_SCANCODE_DOWN) { - hoveredLevel = (hoveredLevel + 4) % 20; - if (ctx.startLevelSelection) *ctx.startLevelSelection = hoveredLevel; - } else if (e.key.scancode == SDL_SCANCODE_RETURN || e.key.scancode == SDL_SCANCODE_KP_ENTER) { - selectLevel(hoveredLevel); - } else if (e.key.scancode == SDL_SCANCODE_ESCAPE) { - closePopup(); + // Arrow key navigation (clamped within grid like the sample) + int c = hoveredLevel < 0 ? 0 : hoveredLevel; + switch (e.key.scancode) { + case SDL_SCANCODE_LEFT: if (c % 4 > 0) c--; break; + case SDL_SCANCODE_RIGHT: if (c % 4 < 3) c++; break; + case SDL_SCANCODE_UP: if (c / 4 > 0) c -= 4; break; + case SDL_SCANCODE_DOWN: if (c / 4 < 4) c += 4; break; + case SDL_SCANCODE_RETURN: + case SDL_SCANCODE_KP_ENTER: + selectLevel(hoveredLevel < 0 ? 0 : hoveredLevel); + return; + case SDL_SCANCODE_ESCAPE: + closePopup(); + return; + default: break; } + hoveredLevel = c; + if (ctx.startLevelSelection) *ctx.startLevelSelection = hoveredLevel; } else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { - float mx = (float)e.button.x, my = (float)e.button.y; - - // Convert to logical coordinates - // Note: This is simplified - in practice we'd need the same coordinate conversion as main.cpp - float popupX, popupY, popupW, popupH; - if (isMouseInPopup(mx, my, popupX, popupY, popupW, popupH)) { - int clickedLevel = getLevelFromMouse(mx, my, popupX, popupY, popupW, popupH); - if (clickedLevel >= 0 && clickedLevel < 20) { - selectLevel(clickedLevel); + if (e.button.button == SDL_BUTTON_LEFT) { + // compute visible logical viewport + float vw = (lastLogicalScale > 0.f) ? float(lastLogicalVP.w) / lastLogicalScale : float(LOGICAL_W); + float vh = (lastLogicalScale > 0.f) ? float(lastLogicalVP.h) / lastLogicalScale : float(LOGICAL_H); + float offX = 0.f; + if (lastLogicalScale > 0.f) offX = (vw / 2.f) - (float(LOGICAL_W) / 2.f); + // convert mouse to logical coords + float lx = (float(e.button.x) - float(lastLogicalVP.x)) / (lastLogicalScale > 0.f ? lastLogicalScale : 1.f); + float ly = (float(e.button.y) - float(lastLogicalVP.y)) / (lastLogicalScale > 0.f ? lastLogicalScale : 1.f); + SDL_FRect panel = DrawPanel(nullptr, vw, vh, /*draw=*/false, offX, 0.f); + Grid g = MakeGrid(panel); + int hit = HitTest(g, int(lx), int(ly)); + if (hit != -1) { + selectLevel(hit); + } else { + closePopup(); } - } else { - // Click outside popup - close it - closePopup(); } } else if (e.type == SDL_EVENT_MOUSE_MOTION) { - float mx = (float)e.motion.x, my = (float)e.motion.y; - updateHoverFromMouse(mx, my); + // compute visible logical viewport and convert mouse coords once + float vw = (lastLogicalScale > 0.f) ? float(lastLogicalVP.w) / lastLogicalScale : float(LOGICAL_W); + float vh = (lastLogicalScale > 0.f) ? float(lastLogicalVP.h) / lastLogicalScale : float(LOGICAL_H); + float offX = 0.f; + if (lastLogicalScale > 0.f) offX = (vw / 2.f) - (float(LOGICAL_W) / 2.f); + float lx = (float(e.motion.x) - float(lastLogicalVP.x)) / (lastLogicalScale > 0.f ? lastLogicalScale : 1.f); + float ly = (float(e.motion.y) - float(lastLogicalVP.y)) / (lastLogicalScale > 0.f ? lastLogicalScale : 1.f); + SDL_FRect panel = DrawPanel(nullptr, vw, vh, /*draw=*/false, offX, 0.f); + Grid g = MakeGrid(panel); + hoveredLevel = HitTest(g, int(lx), int(ly)); } } @@ -77,182 +224,91 @@ void LevelSelectorState::update(double frameMs) { } void LevelSelectorState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) { - (void)logicalScale; - (void)logicalVP; - + // Cache for input conversion + lastLogicalScale = logicalScale; + lastLogicalVP = logicalVP; drawLevelSelectionPopup(renderer); } void LevelSelectorState::drawLevelSelectionPopup(SDL_Renderer* renderer) { - if (!ctx.font) return; - - // Popup dims scale with logical size for responsiveness - float popupW = 400, popupH = 300; - float popupX = (LOGICAL_W - popupW) / 2; - float popupY = (LOGICAL_H - popupH) / 2; + if (!renderer) return; - // Compute content offset so popup centers the same way as other UI code - if (SDL_Window* win = SDL_GetRenderWindow(renderer)) { - int vw = 0, vh = 0; - SDL_GetWindowSize(win, &vw, &vh); - if (vw > 0 && vh > 0) { - float contentScale = std::min((float)vw / (float)LOGICAL_W, (float)vh / (float)LOGICAL_H); - float contentOffsetX = (vw - LOGICAL_W * contentScale) / (2.0f * contentScale); - float contentOffsetY = (vh - LOGICAL_H * contentScale) / (2.0f * contentScale); - popupX += contentOffsetX; - popupY += contentOffsetY; - } + // Important: main sets viewport + logical scale before calling us. + // Draw in logical coordinates to avoid artifacts in fullscreen. + // Use the actual visible logical size (viewport in logical coords) instead of hardcoded constants + float vw = (lastLogicalScale > 0.f) ? float(lastLogicalVP.w) / lastLogicalScale : float(LOGICAL_W); + float vh = (lastLogicalScale > 0.f) ? float(lastLogicalVP.h) / lastLogicalScale : float(LOGICAL_H); + + // Dim overlay over whatever background main already drew. + //FillRect(renderer, SDL_FRect{0, 0, (float)vw, (float)vh}, SDL_Color{0, 0, 0, 120}); + //Vignette(renderer, vw, vh); + + // compute horizontal offset so content centers within the visible logical viewport + float offX = 0.f; + if (lastLogicalScale > 0.f) { + float visibleLogicalW = float(lastLogicalVP.w) / lastLogicalScale; + offX = (visibleLogicalW / 2.f) - (vw / 2.f); } - // Draw the background picture stretched to full renderer viewport if available - SDL_Rect vp{0,0,LOGICAL_W,LOGICAL_H}; - if (SDL_Window* win = SDL_GetRenderWindow(renderer)) { - int vw = 0, vh = 0; - SDL_GetWindowSize(win, &vw, &vh); - if (vw > 0 && vh > 0) { vp.w = vw; vp.h = vh; } - } - - if (ctx.backgroundTex) { - // Dim the background by rendering it then overlaying a semi-transparent black rect - SDL_FRect dst{0, 0, (float)vp.w, (float)vp.h}; - SDL_RenderTexture(renderer, ctx.backgroundTex, nullptr, &dst); - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 160); - SDL_FRect dim{0,0,(float)vp.w,(float)vp.h}; - SDL_RenderFillRect(renderer, &dim); - } else { - // Fallback to semi-transparent overlay - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180); - SDL_FRect overlay{0, 0, (float)vp.w, (float)vp.h}; - SDL_RenderFillRect(renderer, &overlay); - } + // Panel and title strip (in logical space) + SDL_FRect panel = DrawPanel(renderer, vw, vh, /*draw=*/true, offX, 0.f); - // Popup panel with border and subtle background - drawRect(renderer, popupX-6, popupY-6, popupW+12, popupH+12, {90, 110, 140, 200}); // outer border - drawRect(renderer, popupX-3, popupY-3, popupW+6, popupH+6, {30, 38, 60, 220}); // inner border - drawRect(renderer, popupX, popupY, popupW, popupH, {18, 22, 34, 235}); // panel + // Glow strip behind title (raised higher) + //SDL_FRect strip{ (float)vw / 2.f + offX - panel.w / 2.f, panel.y - 80.f, panel.w, 30.f }; + //FillRect(renderer, strip, SDL_Color{255, 204, 51, 28}); - // Title (centered above the popup) - drawn above the box for clarity - { - const std::string title = "SELECT STARTING LEVEL"; - int titleW = 0, titleH = 0; - ctx.font->measure(title, 2.4f, titleW, titleH); - float titleX = popupX + (popupW - (float)titleW) / 2.0f; - float titleY = popupY - (float)titleH - 28.0f; - ctx.font->draw(renderer, titleX, titleY, title, 2.4f, {255, 220, 0, 255}); - } - - // Grid layout for levels: 4 columns x 5 rows - int cols = 4, rows = 5; - float padding = 32.0f; - float gridW = popupW - padding * 2; - float gridH = popupH - 80.0f; // leave less top space since title is above - float cellW = gridW / cols; - float cellH = std::min(96.0f, gridH / rows - 12.0f); - - // Add margin between cells - float cellMargin = 8.0f; - float actualCellW = cellW - cellMargin; - float actualCellH = cellH - cellMargin; - - // Center the grid horizontally inside the popup and position slightly higher inside the box - float gridStartX = popupX + padding; - float gridStartY = popupY + padding/2.0f; + // Title text + DrawText(renderer, ctx.font, "SELECT STARTING LEVEL", vw / 2.f + offX, panel.y - 90.f, 2.4f, COL_TITLE, true, true); + // Grid of levels + Grid g = MakeGrid(panel); int selectedLevel = ctx.startLevelSelection ? *ctx.startLevelSelection : 0; - - for (int level = 0; level < 20; level++) { - int row = level / cols; - int col = level % cols; - float cx = gridStartX + col * cellW + cellMargin/2.0f; - float cy = gridStartY + row * (cellH + 12.0f) + cellMargin/2.0f; - - bool isSelected = (level == selectedLevel); - bool isHovered = (level == hoveredLevel); - - // Button background with thicker border and inner box for a box-style look - const float btnPadX = 24.0f; // increased padding inside each button horizontally - const float btnPadY = 16.0f; // increased padding vertically - - // Outer border - SDL_Color borderCol = isHovered && !isSelected ? SDL_Color{120,140,190,255} : SDL_Color{40,50,70,200}; - drawRect(renderer, cx, cy, actualCellW, actualCellH, borderCol); - - // Inner background - make selected background match the button size - SDL_Color inner = isSelected ? SDL_Color{255, 220, 0, 255} : SDL_Color{50,60,84,255}; - if (!isSelected && isHovered) inner = SDL_Color{90,110,150,255}; - drawRect(renderer, cx + 2, cy + 2, actualCellW - 4, actualCellH - 4, inner); - - // Level label centered using font.measure for accurate centering - char levelStr[8]; - snprintf(levelStr, sizeof(levelStr), "%d", level); - int txtW = 0, txtH = 0; - ctx.font->measure(levelStr, 1.8f, txtW, txtH); - float tx = cx + (actualCellW - (float)txtW) / 2.0f; - float ty = cy + (actualCellH - (float)txtH) / 2.0f; - - // Text color: black on selected (yellow), white on others - SDL_Color fg = isSelected ? SDL_Color{0, 0, 0, 255} : SDL_Color{240, 240, 245, 255}; - ctx.font->draw(renderer, tx, ty, levelStr, 1.8f, fg); + for (int i = 0; i < 20; i++) { + SDL_FRect rc = g.cell(i); + DrawCell(renderer, rc, i, hoveredLevel == i, selectedLevel == i); } - // Instructions below the popup box (centered) - { - const std::string instr = "CLICK A LEVEL TO SELECT • ESC = CANCEL"; - int instrW = 0, instrH = 0; - ctx.font->measure(instr, 1.0f, instrW, instrH); - float instrX = popupX + (popupW - (float)instrW) / 2.0f; - float instrY = popupY + popupH + 40.0f; // below the box with larger padding - ctx.font->draw(renderer, instrX, instrY, instr, 1.0f, {200,200,220,255}); - } + // Footer/instructions + DrawText(renderer, ctx.font, "CLICK A LEVEL TO SELECT \u2022 ESC = CANCEL", + vw / 2.f + offX, vh - 56.f, 1.0f, COL_FOOTER, true, true); } bool LevelSelectorState::isMouseInPopup(float mouseX, float mouseY, float& popupX, float& popupY, float& popupW, float& popupH) { - popupW = 400; - popupH = 300; - popupX = (LOGICAL_W - popupW) / 2; - popupY = (LOGICAL_H - popupH) / 2; - - // Note: This is simplified coordinate conversion - in practice we'd need the same logic as main.cpp - return mouseX >= popupX && mouseX <= popupX + popupW && mouseY >= popupY && mouseY <= popupY + popupH; + // Re-implement using new panel geometry in window coordinates + // Convert from window pixels to logical coords using cached viewport/scale + (void)mouseX; (void)mouseY; + float lx = 0.f, ly = 0.f; + if (lastLogicalScale > 0.0f) { + lx = (float(mouseX) - float(lastLogicalVP.x)) / lastLogicalScale; + ly = (float(mouseY) - float(lastLogicalVP.y)) / lastLogicalScale; + } + float vw = (lastLogicalScale > 0.f) ? float(lastLogicalVP.w) / lastLogicalScale : float(LOGICAL_W); + float vh = (lastLogicalScale > 0.f) ? float(lastLogicalVP.h) / lastLogicalScale : float(LOGICAL_H); + float offX = 0.f; + if (lastLogicalScale > 0.f) offX = (vw / 2.f) - (float(LOGICAL_W) / 2.f); + SDL_FRect p = DrawPanel(nullptr, vw, vh, /*draw=*/false, offX, 0.f); + popupX = p.x; popupY = p.y; popupW = p.w; popupH = p.h; + return lx >= popupX && lx <= popupX + popupW && ly >= popupY && ly <= popupY + popupH; } int LevelSelectorState::getLevelFromMouse(float mouseX, float mouseY, float popupX, float popupY, float popupW, float popupH) { - int cols = 4, rows = 5; - float padding = 32.0f; - float gridW = popupW - padding * 2; - float gridH = popupH - 80.0f; - float cellW = gridW / cols; - float cellH = std::min(96.0f, gridH / rows - 12.0f); - float cellMargin = 8.0f; - float actualCellW = cellW - cellMargin; - float actualCellH = cellH - cellMargin; - float gridStartX = popupX + padding; - float gridStartY = popupY + padding/2.0f; - - if (mouseX >= gridStartX && mouseY >= gridStartY) { - int col = int((mouseX - gridStartX) / cellW); - int row = int((mouseY - gridStartY) / (cellH + 12.0f)); - if (col >= 0 && col < cols && row >= 0 && row < rows) { - // Check if mouse is within the actual cell area (accounting for margins) - float cellX = gridStartX + col * cellW + cellMargin/2.0f; - float cellY = gridStartY + row * (cellH + 12.0f) + cellMargin/2.0f; - if (mouseX >= cellX && mouseX <= cellX + actualCellW && - mouseY >= cellY && mouseY <= cellY + actualCellH) { - int level = row * cols + col; - if (level < 20) return level; - } - } + (void)popupX; (void)popupY; (void)popupW; (void)popupH; + float lx = 0.f, ly = 0.f; + if (lastLogicalScale > 0.0f) { + lx = (float(mouseX) - float(lastLogicalVP.x)) / lastLogicalScale; + ly = (float(mouseY) - float(lastLogicalVP.y)) / lastLogicalScale; } - return -1; + float vw = (lastLogicalScale > 0.f) ? float(lastLogicalVP.w) / lastLogicalScale : float(LOGICAL_W); + float vh = (lastLogicalScale > 0.f) ? float(lastLogicalVP.h) / lastLogicalScale : float(LOGICAL_H); + float offX = 0.f; + if (lastLogicalScale > 0.f) offX = (vw / 2.f) - (float(LOGICAL_W) / 2.f); + SDL_FRect p = DrawPanel(nullptr, vw, vh, /*draw=*/false, offX, 0.f); + Grid g = MakeGrid(p); + return HitTest(g, (int)lx, (int)ly); } void LevelSelectorState::updateHoverFromMouse(float mouseX, float mouseY) { - float popupX, popupY, popupW, popupH; - if (isMouseInPopup(mouseX, mouseY, popupX, popupY, popupW, popupH)) { - hoveredLevel = getLevelFromMouse(mouseX, mouseY, popupX, popupY, popupW, popupH); - } else { - hoveredLevel = -1; - } + hoveredLevel = getLevelFromMouse(mouseX, mouseY, 0, 0, 0, 0); } void LevelSelectorState::selectLevel(int level) { diff --git a/src/states/LevelSelectorState.h b/src/states/LevelSelectorState.h index ee1f1f7..270ef68 100644 --- a/src/states/LevelSelectorState.h +++ b/src/states/LevelSelectorState.h @@ -14,6 +14,9 @@ public: private: int hoveredLevel = -1; // -1 = none, 0..19 = hovered level + // Cache the latest logical scale/viewport from render() for input conversion + float lastLogicalScale = 1.0f; + SDL_Rect lastLogicalVP{0,0,0,0}; // Helper functions void drawLevelSelectionPopup(SDL_Renderer* renderer);