added helper menu
This commit is contained in:
133
src/graphics/ui/HelpOverlay.cpp
Normal file
133
src/graphics/ui/HelpOverlay.cpp
Normal file
@ -0,0 +1,133 @@
|
||||
#include "HelpOverlay.h"
|
||||
#include "Font.h"
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
struct ShortcutEntry {
|
||||
const char* combo;
|
||||
const char* description;
|
||||
};
|
||||
|
||||
void drawRect(SDL_Renderer* renderer, float x, float y, float w, float h, SDL_Color color) {
|
||||
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
|
||||
SDL_FRect rect{x, y, w, h};
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
}
|
||||
|
||||
float fitScale(FontAtlas& font, const char* text, float initialScale, float maxWidth) {
|
||||
int textW = 0, textH = 0;
|
||||
float scale = initialScale;
|
||||
font.measure(text, scale, textW, textH);
|
||||
if (textW > maxWidth && textW > 0) {
|
||||
float factor = maxWidth / static_cast<float>(textW);
|
||||
scale = std::max(0.65f, scale * factor);
|
||||
}
|
||||
return scale;
|
||||
}
|
||||
}
|
||||
|
||||
namespace HelpOverlay {
|
||||
|
||||
void Render(
|
||||
SDL_Renderer* renderer,
|
||||
FontAtlas& font,
|
||||
float logicalWidth,
|
||||
float logicalHeight,
|
||||
float offsetX,
|
||||
float offsetY) {
|
||||
if (!renderer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::array<ShortcutEntry, 5> generalShortcuts{{
|
||||
{"H", "Toggle this help overlay"},
|
||||
{"ESC", "Back / cancel current popup"},
|
||||
{"F11 or ALT+ENTER", "Toggle fullscreen"},
|
||||
{"M", "Mute or unmute music"},
|
||||
{"S", "Toggle sound effects"}
|
||||
}};
|
||||
|
||||
const std::array<ShortcutEntry, 2> menuShortcuts{{
|
||||
{"ARROW KEYS", "Navigate menu buttons"},
|
||||
{"ENTER / SPACE", "Activate highlighted action"}
|
||||
}};
|
||||
|
||||
const std::array<ShortcutEntry, 7> gameplayShortcuts{{
|
||||
{"LEFT / RIGHT", "Move active piece"},
|
||||
{"DOWN", "Soft drop (faster fall)"},
|
||||
{"SPACE", "Hard drop / instant lock"},
|
||||
{"UP", "Rotate clockwise"},
|
||||
{"X", "Rotate counter-clockwise"},
|
||||
{"P", "Pause or resume"},
|
||||
{"ESC", "Open exit confirmation"}
|
||||
}};
|
||||
|
||||
const float boxW = logicalWidth * 0.7f;
|
||||
const float boxH = logicalHeight * 0.55f;
|
||||
const float boxX = offsetX + (logicalWidth - boxW) * 0.5f;
|
||||
const float boxY = offsetY + (logicalHeight - boxH) * 0.5f;
|
||||
|
||||
drawRect(renderer, boxX - 6.0f, boxY - 6.0f, boxW + 12.0f, boxH + 12.0f, {70, 90, 150, 255});
|
||||
drawRect(renderer, boxX - 2.0f, boxY - 2.0f, boxW + 4.0f, boxH + 4.0f, {10, 12, 20, 255});
|
||||
drawRect(renderer, boxX, boxY, boxW, boxH, {18, 22, 35, 240});
|
||||
|
||||
const float titleScale = 1.7f;
|
||||
font.draw(renderer, boxX + 28.0f, boxY + 24.0f, "HELP & SHORTCUTS", titleScale, {255, 220, 0, 255});
|
||||
|
||||
const float contentPadding = 32.0f;
|
||||
const float columnGap = 30.0f;
|
||||
const float columnWidth = (boxW - contentPadding * 2.0f - columnGap) * 0.5f;
|
||||
const float leftColumnX = boxX + contentPadding;
|
||||
const float rightColumnX = leftColumnX + columnWidth + columnGap;
|
||||
const float footerHeight = 46.0f;
|
||||
const float footerPadding = 18.0f;
|
||||
|
||||
const float sectionTitleScale = 1.1f;
|
||||
const float comboScale = 0.92f;
|
||||
const float descBaseScale = 0.8f;
|
||||
const float comboSpacing = 22.0f;
|
||||
const float sectionSpacing = 14.0f;
|
||||
|
||||
auto drawSection = [&](float startX, float& cursorY, const char* title, const auto& entries) {
|
||||
font.draw(renderer, startX, cursorY, title, sectionTitleScale, {180, 200, 255, 255});
|
||||
cursorY += 26.0f;
|
||||
for (const auto& entry : entries) {
|
||||
font.draw(renderer, startX, cursorY, entry.combo, comboScale, {255, 255, 255, 255});
|
||||
cursorY += comboSpacing;
|
||||
|
||||
float descScale = fitScale(font, entry.description, descBaseScale, columnWidth - 10.0f);
|
||||
font.draw(renderer, startX, cursorY, entry.description, descScale, {200, 210, 230, 255});
|
||||
int descW = 0, descH = 0;
|
||||
font.measure(entry.description, descScale, descW, descH);
|
||||
cursorY += static_cast<float>(descH) + 10.0f;
|
||||
}
|
||||
cursorY += sectionSpacing;
|
||||
};
|
||||
|
||||
float leftCursorY = boxY + 80.0f;
|
||||
float rightCursorY = boxY + 80.0f;
|
||||
drawSection(leftColumnX, leftCursorY, "GENERAL", generalShortcuts);
|
||||
drawSection(leftColumnX, leftCursorY, "MENUS", menuShortcuts);
|
||||
drawSection(rightColumnX, rightCursorY, "GAMEPLAY", gameplayShortcuts);
|
||||
|
||||
SDL_FRect footerRect{
|
||||
boxX + contentPadding,
|
||||
boxY + boxH - contentPadding - footerHeight,
|
||||
boxW - contentPadding * 2.0f,
|
||||
footerHeight
|
||||
};
|
||||
drawRect(renderer, footerRect.x, footerRect.y, footerRect.w, footerRect.h, {24, 30, 50, 255});
|
||||
SDL_SetRenderDrawColor(renderer, 90, 110, 170, 255);
|
||||
SDL_RenderRect(renderer, &footerRect);
|
||||
|
||||
const char* closeLabel = "PRESS H TO CLOSE";
|
||||
float closeScale = fitScale(font, closeLabel, 1.0f, footerRect.w - footerPadding * 2.0f);
|
||||
int closeW = 0, closeH = 0;
|
||||
font.measure(closeLabel, closeScale, closeW, closeH);
|
||||
float closeX = footerRect.x + (footerRect.w - static_cast<float>(closeW)) * 0.5f;
|
||||
float closeY = footerRect.y + (footerRect.h - static_cast<float>(closeH)) * 0.5f;
|
||||
font.draw(renderer, closeX, closeY, closeLabel, closeScale, {215, 220, 240, 255});
|
||||
}
|
||||
|
||||
} // namespace HelpOverlay
|
||||
16
src/graphics/ui/HelpOverlay.h
Normal file
16
src/graphics/ui/HelpOverlay.h
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
class FontAtlas;
|
||||
|
||||
namespace HelpOverlay {
|
||||
// Draws the help popup contents inside the logical coordinate space.
|
||||
// Optional offsets allow aligning the box when the logical canvas is letterboxed.
|
||||
void Render(
|
||||
SDL_Renderer* renderer,
|
||||
FontAtlas& font,
|
||||
float logicalWidth,
|
||||
float logicalHeight,
|
||||
float offsetX = 0.0f,
|
||||
float offsetY = 0.0f);
|
||||
}
|
||||
Reference in New Issue
Block a user