fixed menu
This commit is contained in:
@ -39,6 +39,30 @@ void UIRenderer::drawButton(SDL_Renderer* renderer, FontAtlas* font, float cx, f
|
|||||||
float x = cx - w * 0.5f;
|
float x = cx - w * 0.5f;
|
||||||
float y = cy - h * 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<Uint8>(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) {
|
if (!textOnly) {
|
||||||
// Adjust colors based on state
|
// Adjust colors based on state
|
||||||
if (isSelected) {
|
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)
|
// Neon glow aura around the button to increase visibility (subtle)
|
||||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
|
||||||
for (int gi = 0; gi < 3; ++gi) {
|
for (int gi = 0; gi < 3; ++gi) {
|
||||||
float grow = 6.0f + gi * 3.0f;
|
float grow = 6.0f + gi * 3.0f;
|
||||||
Uint8 glowA = static_cast<Uint8>(std::max(0, (int)borderColor.a / (3 - gi)));
|
Uint8 glowA = static_cast<Uint8>(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 iconX = cx - scaledW * 0.5f;
|
||||||
float iconY = cy - scaledH * 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) {
|
if (isSelected) {
|
||||||
SDL_SetTextureColorMod(icon, 255, 220, 0);
|
SDL_SetTextureColorMod(icon, 255, 220, 0);
|
||||||
} else {
|
} else {
|
||||||
SDL_SetTextureColorMod(icon, 255, 255, 255);
|
SDL_SetTextureColorMod(icon, 255, 255, 255);
|
||||||
}
|
}
|
||||||
|
SDL_SetTextureAlphaMod(icon, 255);
|
||||||
SDL_FRect iconRect{iconX, iconY, scaledW, scaledH};
|
|
||||||
SDL_RenderTexture(renderer, icon, nullptr, &iconRect);
|
SDL_RenderTexture(renderer, icon, nullptr, &iconRect);
|
||||||
|
|
||||||
// Reset color mod
|
// Reset
|
||||||
SDL_SetTextureColorMod(icon, 255, 255, 255);
|
SDL_SetTextureColorMod(icon, 255, 255, 255);
|
||||||
|
SDL_SetTextureAlphaMod(icon, 255);
|
||||||
} else if (font) {
|
} else if (font) {
|
||||||
// Draw text (smaller scale for tighter buttons)
|
// Draw text (smaller scale for tighter buttons)
|
||||||
float textScale = 1.2f;
|
float textScale = 1.2f;
|
||||||
|
|||||||
14
src/main.cpp
14
src/main.cpp
@ -980,18 +980,8 @@ int main(int, char **)
|
|||||||
// Click anywhere closes settings popup
|
// Click anywhere closes settings popup
|
||||||
showSettingsPopup = false;
|
showSettingsPopup = false;
|
||||||
} else {
|
} else {
|
||||||
// Responsive Main menu buttons (match MenuState layout)
|
ui::MenuLayoutParams params{ LOGICAL_W, LOGICAL_H, winW, winH, logicalScale };
|
||||||
bool isSmall = ((LOGICAL_W * logicalScale) < MENU_SMALL_THRESHOLD);
|
auto buttonRects = ui::computeMenuButtonRects(params);
|
||||||
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<SDL_FRect, MENU_BTN_COUNT> buttonRects{};
|
|
||||||
for (int i = 0; i < MENU_BTN_COUNT; ++i) {
|
|
||||||
float center = btnCX + (static_cast<float>(i) - MENU_BTN_CENTER) * spacing;
|
|
||||||
buttonRects[i] = SDL_FRect{center - btnW / 2.0f, btnCY - btnH / 2.0f, btnW, btnH};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto pointInRect = [&](const SDL_FRect& r) {
|
auto pointInRect = [&](const SDL_FRect& r) {
|
||||||
return lx >= r.x && lx <= r.x + r.w && ly >= r.y && ly <= r.y + r.h;
|
return lx >= r.x && lx <= r.x + r.w && ly >= r.y && ly <= r.y + r.h;
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
#include "../utils/ImagePathResolver.h"
|
#include "../utils/ImagePathResolver.h"
|
||||||
#include "../graphics/renderers/UIRenderer.h"
|
#include "../graphics/renderers/UIRenderer.h"
|
||||||
#include "../graphics/renderers/GameRenderer.h"
|
#include "../graphics/renderers/GameRenderer.h"
|
||||||
|
#include "../ui/MenuLayout.h"
|
||||||
#include <SDL3_image/SDL_image.h>
|
#include <SDL3_image/SDL_image.h>
|
||||||
|
|
||||||
// Frosted tint helper: draw a safe, inexpensive frosted overlay for the panel area.
|
// 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) {
|
void MenuState::renderMainButtonTop(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) {
|
||||||
const float LOGICAL_W = 1200.f;
|
const float LOGICAL_W = 1200.f;
|
||||||
const float LOGICAL_H = 1000.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;
|
// Use the same layout code as mouse hit-testing so each button is the same size.
|
||||||
bool isSmall = (contentW < 700.0f);
|
ui::MenuLayoutParams params{
|
||||||
float btnW = 200.0f;
|
static_cast<int>(LOGICAL_W),
|
||||||
float btnH = 70.0f;
|
static_cast<int>(LOGICAL_H),
|
||||||
float btnX = LOGICAL_W * 0.5f + contentOffsetX;
|
logicalVP.w,
|
||||||
// move buttons a bit lower for better visibility
|
logicalVP.h,
|
||||||
// small global vertical offset for the whole menu (tweak to move UI down)
|
logicalScale
|
||||||
float menuYOffset = LOGICAL_H * 0.03f;
|
};
|
||||||
float btnY = LOGICAL_H * 0.865f + contentOffsetY + (LOGICAL_H * 0.02f) + menuYOffset + 4.5f;
|
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()
|
// Compose same button definition used in render()
|
||||||
char levelBtnText[32];
|
char levelBtnText[32];
|
||||||
@ -165,60 +201,17 @@ void MenuState::renderMainButtonTop(SDL_Renderer* renderer, float logicalScale,
|
|||||||
|
|
||||||
std::array<SDL_Texture*,5> icons = { playIcon, levelIcon, optionsIcon, helpIcon, exitIcon };
|
std::array<SDL_Texture*,5> icons = { playIcon, levelIcon, optionsIcon, helpIcon, exitIcon };
|
||||||
|
|
||||||
float spacing = isSmall ? btnW * 1.2f : btnW * 1.15f;
|
// Draw all five buttons on top of the main_screen art (text/icon only).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 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
|
|
||||||
for (int i = 0; i < 5; ++i) {
|
for (int i = 0; i < 5; ++i) {
|
||||||
float cxCenter = 0.0f;
|
const SDL_FRect& r = rects[i];
|
||||||
// Use the group's center Y so text/icons sit visually centered in the panel
|
float cxCenter = r.x + r.w * 0.5f;
|
||||||
float cyCenter = groupCenterY;
|
float cyCenter = r.y + r.h * 0.5f;
|
||||||
if (ctx.menuButtonsExplicit) {
|
float btnW = r.w;
|
||||||
cxCenter = ctx.menuButtonCX[i] + contentOffsetX;
|
float btnH = r.h;
|
||||||
cyCenter = ctx.menuButtonCY[i] + contentOffsetY;
|
|
||||||
} else {
|
const bool isHovered = (ctx.hoveredButton && *ctx.hoveredButton == i);
|
||||||
float offset = (static_cast<float>(i) - 2.0f) * spacing;
|
const bool isSelected = (selectedButton == i);
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
// Apply group alpha and transient flash to button colors
|
// Apply group alpha and transient flash to button colors
|
||||||
double aMul = std::clamp(buttonGroupAlpha + buttonFlash * buttonFlashAmount, 0.0, 1.0);
|
double aMul = std::clamp(buttonGroupAlpha + buttonFlash * buttonFlashAmount, 0.0, 1.0);
|
||||||
SDL_Color bgCol = buttons[i].bg;
|
SDL_Color bgCol = buttons[i].bg;
|
||||||
@ -226,12 +219,9 @@ void MenuState::renderMainButtonTop(SDL_Renderer* renderer, float logicalScale,
|
|||||||
bgCol.a = static_cast<Uint8>(std::round(aMul * static_cast<double>(bgCol.a)));
|
bgCol.a = static_cast<Uint8>(std::round(aMul * static_cast<double>(bgCol.a)));
|
||||||
bdCol.a = static_cast<Uint8>(std::round(aMul * static_cast<double>(bdCol.a)));
|
bdCol.a = static_cast<Uint8>(std::round(aMul * static_cast<double>(bdCol.a)));
|
||||||
UIRenderer::drawButton(renderer, ctx.pixelFont, cxCenter, cyCenter, btnW, btnH,
|
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]);
|
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() {
|
void MenuState::onExit() {
|
||||||
|
|||||||
@ -16,6 +16,30 @@ std::array<SDL_FRect, 5> computeMenuButtonRects(const MenuLayoutParams& p) {
|
|||||||
float btnCX = LOGICAL_W * 0.5f + contentOffsetX;
|
float btnCX = LOGICAL_W * 0.5f + contentOffsetX;
|
||||||
float btnCY = LOGICAL_H * 0.86f + contentOffsetY + MENU_BTN_Y_OFFSET;
|
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;
|
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<SDL_FRect, MENU_BTN_COUNT> rects{};
|
std::array<SDL_FRect, MENU_BTN_COUNT> rects{};
|
||||||
for (int i = 0; i < MENU_BTN_COUNT; ++i) {
|
for (int i = 0; i < MENU_BTN_COUNT; ++i) {
|
||||||
float center = btnCX + (static_cast<float>(i) - MENU_BTN_CENTER) * spacing;
|
float center = btnCX + (static_cast<float>(i) - MENU_BTN_CENTER) * spacing;
|
||||||
|
|||||||
@ -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_WIDTH_SMALL_FACTOR = 0.4f; // multiplied by LOGICAL_W
|
||||||
static constexpr float MENU_BTN_HEIGHT_LARGE = 70.0f;
|
static constexpr float MENU_BTN_HEIGHT_LARGE = 70.0f;
|
||||||
static constexpr float MENU_BTN_HEIGHT_SMALL = 60.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_SMALL = 1.15f;
|
||||||
static constexpr float MENU_BTN_SPACING_FACTOR_LARGE = 1.05f;
|
static constexpr float MENU_BTN_SPACING_FACTOR_LARGE = 1.05f;
|
||||||
static constexpr float MENU_BTN_CENTER = (MENU_BTN_COUNT - 1) / 2.0f;
|
static constexpr float MENU_BTN_CENTER = (MENU_BTN_COUNT - 1) / 2.0f;
|
||||||
|
|||||||
Reference in New Issue
Block a user