From a671825502cbefe1b98330d305aaf08996b01596 Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Wed, 17 Dec 2025 18:55:55 +0100 Subject: [PATCH] fixed menu --- src/graphics/renderers/UIRenderer.cpp | 44 +++++++-- src/main.cpp | 14 +-- src/states/MenuState.cpp | 128 ++++++++++++-------------- src/ui/MenuLayout.cpp | 24 +++++ src/ui/UIConstants.h | 2 +- 5 files changed, 124 insertions(+), 88 deletions(-) diff --git a/src/graphics/renderers/UIRenderer.cpp b/src/graphics/renderers/UIRenderer.cpp index e86e754..d30a36f 100644 --- a/src/graphics/renderers/UIRenderer.cpp +++ b/src/graphics/renderers/UIRenderer.cpp @@ -39,6 +39,30 @@ void UIRenderer::drawButton(SDL_Renderer* renderer, FontAtlas* font, float cx, f float x = cx - w * 0.5f; float y = cy - h * 0.5f; + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + + // In "textOnly" mode we don't draw a full button body (the art may be in the background image), + // but we still add a subtle highlight so hover/selection feels intentional. + if (textOnly && (isHovered || isSelected)) { + Uint8 outlineA = isSelected ? 170 : 110; + Uint8 fillA = isSelected ? 60 : 32; + + SDL_Color hl = borderColor; + hl.a = outlineA; + SDL_SetRenderDrawColor(renderer, hl.r, hl.g, hl.b, hl.a); + SDL_FRect o1{x - 3.0f, y - 3.0f, w + 6.0f, h + 6.0f}; + SDL_RenderRect(renderer, &o1); + SDL_FRect o2{x - 6.0f, y - 6.0f, w + 12.0f, h + 12.0f}; + SDL_SetRenderDrawColor(renderer, hl.r, hl.g, hl.b, static_cast(std::max(0, (int)hl.a - 60))); + SDL_RenderRect(renderer, &o2); + + SDL_Color fill = bgColor; + fill.a = fillA; + SDL_SetRenderDrawColor(renderer, fill.r, fill.g, fill.b, fill.a); + SDL_FRect f{x, y, w, h}; + SDL_RenderFillRect(renderer, &f); + } + if (!textOnly) { // Adjust colors based on state if (isSelected) { @@ -54,7 +78,6 @@ void UIRenderer::drawButton(SDL_Renderer* renderer, FontAtlas* font, float cx, f } // Neon glow aura around the button to increase visibility (subtle) - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); for (int gi = 0; gi < 3; ++gi) { float grow = 6.0f + gi * 3.0f; Uint8 glowA = static_cast(std::max(0, (int)borderColor.a / (3 - gi))); @@ -89,18 +112,27 @@ void UIRenderer::drawButton(SDL_Renderer* renderer, FontAtlas* font, float cx, f float iconX = cx - scaledW * 0.5f; float iconY = cy - scaledH * 0.5f; - // Apply yellow tint when selected + SDL_FRect iconRect{iconX, iconY, scaledW, scaledH}; + + // Soft icon shadow for readability over busy backgrounds + SDL_SetTextureBlendMode(icon, SDL_BLENDMODE_BLEND); + SDL_SetTextureColorMod(icon, 0, 0, 0); + SDL_SetTextureAlphaMod(icon, 150); + SDL_FRect shadowRect{iconX + 2.0f, iconY + 2.0f, scaledW, scaledH}; + SDL_RenderTexture(renderer, icon, nullptr, &shadowRect); + + // Main icon (yellow tint when selected) if (isSelected) { SDL_SetTextureColorMod(icon, 255, 220, 0); } else { SDL_SetTextureColorMod(icon, 255, 255, 255); } - - SDL_FRect iconRect{iconX, iconY, scaledW, scaledH}; + SDL_SetTextureAlphaMod(icon, 255); SDL_RenderTexture(renderer, icon, nullptr, &iconRect); - - // Reset color mod + + // Reset SDL_SetTextureColorMod(icon, 255, 255, 255); + SDL_SetTextureAlphaMod(icon, 255); } else if (font) { // Draw text (smaller scale for tighter buttons) float textScale = 1.2f; diff --git a/src/main.cpp b/src/main.cpp index 718b40c..3598b13 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -980,18 +980,8 @@ int main(int, char **) // Click anywhere closes settings popup showSettingsPopup = false; } else { - // Responsive Main menu buttons (match MenuState layout) - bool isSmall = ((LOGICAL_W * logicalScale) < MENU_SMALL_THRESHOLD); - float btnW = isSmall ? (LOGICAL_W * MENU_BTN_WIDTH_SMALL_FACTOR) : MENU_BTN_WIDTH_LARGE; - float btnH = isSmall ? MENU_BTN_HEIGHT_SMALL : MENU_BTN_HEIGHT_LARGE; - float btnCX = LOGICAL_W * 0.5f + contentOffsetX; - float btnCY = LOGICAL_H * 0.86f + contentOffsetY + MENU_BTN_Y_OFFSET; - float spacing = isSmall ? btnW * MENU_BTN_SPACING_FACTOR_SMALL : btnW * MENU_BTN_SPACING_FACTOR_LARGE; - std::array buttonRects{}; - for (int i = 0; i < MENU_BTN_COUNT; ++i) { - float center = btnCX + (static_cast(i) - MENU_BTN_CENTER) * spacing; - buttonRects[i] = SDL_FRect{center - btnW / 2.0f, btnCY - btnH / 2.0f, btnW, btnH}; - } + ui::MenuLayoutParams params{ LOGICAL_W, LOGICAL_H, winW, winH, logicalScale }; + auto buttonRects = ui::computeMenuButtonRects(params); auto pointInRect = [&](const SDL_FRect& r) { return lx >= r.x && lx <= r.x + r.w && ly >= r.y && ly <= r.y + r.h; diff --git a/src/states/MenuState.cpp b/src/states/MenuState.cpp index 2f731a7..285b0a9 100644 --- a/src/states/MenuState.cpp +++ b/src/states/MenuState.cpp @@ -25,6 +25,7 @@ #include "../utils/ImagePathResolver.h" #include "../graphics/renderers/UIRenderer.h" #include "../graphics/renderers/GameRenderer.h" +#include "../ui/MenuLayout.h" #include // Frosted tint helper: draw a safe, inexpensive frosted overlay for the panel area. @@ -135,19 +136,54 @@ void MenuState::onEnter() { void MenuState::renderMainButtonTop(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) { const float LOGICAL_W = 1200.f; const float LOGICAL_H = 1000.f; - float contentOffsetX = 0.0f; - float contentOffsetY = 0.0f; - UIRenderer::computeContentOffsets((float)logicalVP.w, (float)logicalVP.h, LOGICAL_W, LOGICAL_H, logicalScale, contentOffsetX, contentOffsetY); - float contentW = LOGICAL_W * logicalScale; - bool isSmall = (contentW < 700.0f); - float btnW = 200.0f; - float btnH = 70.0f; - float btnX = LOGICAL_W * 0.5f + contentOffsetX; - // move buttons a bit lower for better visibility - // small global vertical offset for the whole menu (tweak to move UI down) - float menuYOffset = LOGICAL_H * 0.03f; - float btnY = LOGICAL_H * 0.865f + contentOffsetY + (LOGICAL_H * 0.02f) + menuYOffset + 4.5f; + // Use the same layout code as mouse hit-testing so each button is the same size. + ui::MenuLayoutParams params{ + static_cast(LOGICAL_W), + static_cast(LOGICAL_H), + logicalVP.w, + logicalVP.h, + logicalScale + }; + auto rects = ui::computeMenuButtonRects(params); + + // Draw a compact translucent panel behind the button row for readability. + // Keep it tight so the main_screen art stays visible. + { + float left = rects[0].x; + float right = rects[0].x + rects[0].w; + float top = rects[0].y; + float bottom = rects[0].y + rects[0].h; + for (int i = 1; i < MENU_BTN_COUNT; ++i) { + left = std::min(left, rects[i].x); + right = std::max(right, rects[i].x + rects[i].w); + top = std::min(top, rects[i].y); + bottom = std::max(bottom, rects[i].y + rects[i].h); + } + + const float padX = 16.0f; + const float padY = 12.0f; + SDL_FRect panel{ left - padX, top - padY, (right - left) + padX * 2.0f, (bottom - top) + padY * 2.0f }; + + SDL_BlendMode prevBlend = SDL_BLENDMODE_NONE; + SDL_GetRenderDrawBlendMode(renderer, &prevBlend); + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + + // Soft shadow (dark, low alpha) + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 80); + SDL_FRect shadow{ panel.x + 3.0f, panel.y + 5.0f, panel.w, panel.h }; + SDL_RenderFillRect(renderer, &shadow); + + // Bright translucent fill (use existing cyan family used elsewhere in UI) + SDL_SetRenderDrawColor(renderer, 180, 235, 255, 46); + SDL_RenderFillRect(renderer, &panel); + + // Border + SDL_SetRenderDrawColor(renderer, 120, 220, 255, 120); + SDL_RenderRect(renderer, &panel); + + SDL_SetRenderDrawBlendMode(renderer, prevBlend); + } // Compose same button definition used in render() char levelBtnText[32]; @@ -165,60 +201,17 @@ void MenuState::renderMainButtonTop(SDL_Renderer* renderer, float logicalScale, std::array icons = { playIcon, levelIcon, optionsIcon, helpIcon, exitIcon }; - float spacing = isSmall ? btnW * 1.2f : btnW * 1.15f; - - - - // Draw semi-transparent background panel behind the full button group (draw first so text sits on top) - // `groupCenterY` is declared here so it can be used when drawing the buttons below. - float groupCenterY = 0.0f; - { - float groupCenterX = btnX; - float halfSpan = 1.5f * spacing; // covers from leftmost to rightmost button centers - float panelLeft = groupCenterX - halfSpan - btnW * 0.5f - 14.0f; - float panelRight = groupCenterX + halfSpan + btnW * 0.5f + 14.0f; - // Nudge the panel slightly lower for better visual spacing - float panelTop = btnY - btnH * 0.5f - 12.0f + 18.0f; - float panelH = btnH + 24.0f; - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); - // Backdrop blur pass before tint (use captured scene texture if available) - renderBackdropBlur(renderer, logicalVP, logicalScale, panelTop, panelH, ctx.sceneTex, ctx.sceneW, ctx.sceneH); - // Brighter, more transparent background to increase contrast but keep scene visible - // More transparent background so underlying scene shows through - SDL_SetRenderDrawColor(renderer, 28, 36, 46, 110); - // Fill full-width background so edges are covered in fullscreen - float viewportLogicalW = (float)logicalVP.w / logicalScale; - SDL_FRect fullPanel{ 0.0f, panelTop, viewportLogicalW, panelH }; - SDL_RenderFillRect(renderer, &fullPanel); - // Also draw the central strip to keep visual center emphasis - SDL_FRect panelRect{ panelLeft, panelTop, panelRight - panelLeft, panelH }; - SDL_RenderFillRect(renderer, &panelRect); - // brighter full-width border (slightly more transparent) - SDL_SetRenderDrawColor(renderer, 120, 140, 160, 120); - // Expand border to cover full window width (use actual viewport) - SDL_FRect borderFull{ 0.0f, panelTop, viewportLogicalW, panelH }; - SDL_RenderRect(renderer, &borderFull); - // Compute a vertical center for the group so labels/icons can be centered - groupCenterY = panelTop + panelH * 0.5f; - } - - // Draw all five buttons on top + // Draw all five buttons on top of the main_screen art (text/icon only). for (int i = 0; i < 5; ++i) { - float cxCenter = 0.0f; - // Use the group's center Y so text/icons sit visually centered in the panel - float cyCenter = groupCenterY; - if (ctx.menuButtonsExplicit) { - cxCenter = ctx.menuButtonCX[i] + contentOffsetX; - cyCenter = ctx.menuButtonCY[i] + contentOffsetY; - } else { - float offset = (static_cast(i) - 2.0f) * spacing; - // small per-button offsets to better match original art placement - float extra = 0.0f; - if (i == 0) extra = 15.0f; - if (i == 2) extra = -18.0f; - if (i == 4) extra = -24.0f; - cxCenter = btnX + offset + extra; - } + const SDL_FRect& r = rects[i]; + float cxCenter = r.x + r.w * 0.5f; + float cyCenter = r.y + r.h * 0.5f; + float btnW = r.w; + float btnH = r.h; + + const bool isHovered = (ctx.hoveredButton && *ctx.hoveredButton == i); + const bool isSelected = (selectedButton == i); + // Apply group alpha and transient flash to button colors double aMul = std::clamp(buttonGroupAlpha + buttonFlash * buttonFlashAmount, 0.0, 1.0); SDL_Color bgCol = buttons[i].bg; @@ -226,12 +219,9 @@ void MenuState::renderMainButtonTop(SDL_Renderer* renderer, float logicalScale, bgCol.a = static_cast(std::round(aMul * static_cast(bgCol.a))); bdCol.a = static_cast(std::round(aMul * static_cast(bdCol.a))); UIRenderer::drawButton(renderer, ctx.pixelFont, cxCenter, cyCenter, btnW, btnH, - buttons[i].label, false, selectedButton == i, + buttons[i].label, isHovered, isSelected, bgCol, bdCol, true, icons[i]); - // no per-button neon outline here; draw group background below instead } - - // (panel for the top-button draw path is drawn before the buttons so text is on top) } void MenuState::onExit() { diff --git a/src/ui/MenuLayout.cpp b/src/ui/MenuLayout.cpp index 83bc594..c99ef95 100644 --- a/src/ui/MenuLayout.cpp +++ b/src/ui/MenuLayout.cpp @@ -16,6 +16,30 @@ std::array computeMenuButtonRects(const MenuLayoutParams& p) { float btnCX = LOGICAL_W * 0.5f + contentOffsetX; float btnCY = LOGICAL_H * 0.86f + contentOffsetY + MENU_BTN_Y_OFFSET; float spacing = isSmall ? btnW * MENU_BTN_SPACING_FACTOR_SMALL : btnW * MENU_BTN_SPACING_FACTOR_LARGE; + + // Guarantee the full 5-button group fits within the logical width so no options + // disappear in windowed mode. We shrink width + spacing proportionally when needed. + const float margin = std::max(18.0f, LOGICAL_W * 0.02f); + const float availableW = std::max(100.0f, LOGICAL_W - margin * 2.0f); + float totalW = btnW + (MENU_BTN_COUNT - 1) * spacing; + if (totalW > availableW) { + float scale = availableW / totalW; + btnW *= scale; + btnH *= scale; + spacing *= scale; + totalW = btnW + (MENU_BTN_COUNT - 1) * spacing; + } + + // Keep the group centered but ensure left/right edges respect margins. + float groupLeft = btnCX - totalW * 0.5f; + float minLeft = contentOffsetX + margin; + float maxRight = contentOffsetX + LOGICAL_W - margin; + float groupRight = groupLeft + totalW; + if (groupLeft < minLeft) { + btnCX += (minLeft - groupLeft); + } else if (groupRight > maxRight) { + btnCX -= (groupRight - maxRight); + } std::array rects{}; for (int i = 0; i < MENU_BTN_COUNT; ++i) { float center = btnCX + (static_cast(i) - MENU_BTN_CENTER) * spacing; diff --git a/src/ui/UIConstants.h b/src/ui/UIConstants.h index 27c6d8e..cb9c89d 100644 --- a/src/ui/UIConstants.h +++ b/src/ui/UIConstants.h @@ -6,7 +6,7 @@ static constexpr float MENU_BTN_WIDTH_LARGE = 300.0f; static constexpr float MENU_BTN_WIDTH_SMALL_FACTOR = 0.4f; // multiplied by LOGICAL_W static constexpr float MENU_BTN_HEIGHT_LARGE = 70.0f; static constexpr float MENU_BTN_HEIGHT_SMALL = 60.0f; -static constexpr float MENU_BTN_Y_OFFSET = 40.0f; // matches MenuState offset +static constexpr float MENU_BTN_Y_OFFSET = 58.0f; // matches MenuState offset; slightly lower for windowed visibility static constexpr float MENU_BTN_SPACING_FACTOR_SMALL = 1.15f; static constexpr float MENU_BTN_SPACING_FACTOR_LARGE = 1.05f; static constexpr float MENU_BTN_CENTER = (MENU_BTN_COUNT - 1) / 2.0f;