- HUD (F1) afegeix línia PostFX: OFF o PostFX: <preset> [V:x.xx C:x.xx S:x.xx] - applyPostFXPreset reaaplica overrides de CLI per preservar-los en ciclar amb X - setPostFXParamOverrides guarda els valors en membres privats per persistència - --postfx sense valor ja no dona error i utilitza complet (preset 3) per defecte Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
432 lines
16 KiB
C++
432 lines
16 KiB
C++
#include "ui_manager.hpp"
|
|
|
|
#include <SDL3/SDL.h>
|
|
#include <algorithm>
|
|
#include <string>
|
|
|
|
#include "ball.hpp" // for Ball
|
|
#include "defines.hpp" // for TEXT_DURATION, NOTIFICATION_DURATION, AppMode, SimulationMode
|
|
#include "engine.hpp" // for Engine (info de sistema)
|
|
#include "scene/scene_manager.hpp" // for SceneManager
|
|
#include "shapes/shape.hpp" // for Shape
|
|
#include "text/textrenderer.hpp" // for TextRenderer
|
|
#include "theme_manager.hpp" // for ThemeManager
|
|
#include "notifier.hpp" // for Notifier
|
|
#include "help_overlay.hpp" // for HelpOverlay
|
|
|
|
// ============================================================================
|
|
// HELPER: Obtener viewport en coordenadas físicas (no lógicas)
|
|
// ============================================================================
|
|
// SDL_GetRenderViewport() devuelve coordenadas LÓGICAS cuando hay presentación
|
|
// lógica activa. Para obtener coordenadas FÍSICAS, necesitamos deshabilitar
|
|
// temporalmente la presentación lógica.
|
|
static SDL_Rect getPhysicalViewport(SDL_Renderer* renderer) {
|
|
// Guardar estado actual de presentación lógica
|
|
int logical_w = 0, logical_h = 0;
|
|
SDL_RendererLogicalPresentation presentation_mode;
|
|
SDL_GetRenderLogicalPresentation(renderer, &logical_w, &logical_h, &presentation_mode);
|
|
|
|
// Deshabilitar presentación lógica temporalmente
|
|
SDL_SetRenderLogicalPresentation(renderer, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED);
|
|
|
|
// Obtener viewport en coordenadas físicas (píxeles reales)
|
|
SDL_Rect physical_viewport;
|
|
SDL_GetRenderViewport(renderer, &physical_viewport);
|
|
|
|
// Restaurar presentación lógica
|
|
SDL_SetRenderLogicalPresentation(renderer, logical_w, logical_h, presentation_mode);
|
|
|
|
return physical_viewport;
|
|
}
|
|
|
|
UIManager::UIManager()
|
|
: text_renderer_debug_(nullptr)
|
|
, text_renderer_notifier_(nullptr)
|
|
, notifier_(nullptr)
|
|
, help_overlay_(nullptr)
|
|
, show_debug_(false)
|
|
, fps_last_time_(0)
|
|
, fps_frame_count_(0)
|
|
, fps_current_(0)
|
|
, fps_text_("FPS: 0")
|
|
, vsync_text_("VSYNC ON")
|
|
, renderer_(nullptr)
|
|
, theme_manager_(nullptr)
|
|
, physical_window_width_(0)
|
|
, physical_window_height_(0)
|
|
, logical_window_width_(0)
|
|
, logical_window_height_(0)
|
|
, current_font_size_(18) { // Tamaño por defecto (medium)
|
|
}
|
|
|
|
UIManager::~UIManager() {
|
|
// Limpieza: Los objetos creados con new deben ser eliminados
|
|
delete text_renderer_debug_;
|
|
delete text_renderer_notifier_;
|
|
delete notifier_;
|
|
delete help_overlay_;
|
|
}
|
|
|
|
void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
|
|
int physical_width, int physical_height,
|
|
int logical_width, int logical_height) {
|
|
delete text_renderer_debug_; text_renderer_debug_ = nullptr;
|
|
delete text_renderer_notifier_; text_renderer_notifier_ = nullptr;
|
|
delete notifier_; notifier_ = nullptr;
|
|
delete help_overlay_; help_overlay_ = nullptr;
|
|
|
|
renderer_ = renderer;
|
|
theme_manager_ = theme_manager;
|
|
physical_window_width_ = physical_width;
|
|
physical_window_height_ = physical_height;
|
|
logical_window_width_ = logical_width;
|
|
logical_window_height_ = logical_height;
|
|
|
|
// Calcular tamaño de fuente apropiado según dimensiones LÓGICAS (sin zoom)
|
|
current_font_size_ = calculateFontSize(logical_height);
|
|
|
|
// Crear renderers de texto
|
|
text_renderer_debug_ = new TextRenderer();
|
|
text_renderer_notifier_ = new TextRenderer();
|
|
|
|
// Inicializar renderers con tamaño dinámico
|
|
text_renderer_debug_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", std::max(9, current_font_size_ - 2), true);
|
|
text_renderer_notifier_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", current_font_size_, true);
|
|
|
|
// Crear y configurar sistema de notificaciones
|
|
notifier_ = new Notifier();
|
|
notifier_->init(renderer, text_renderer_notifier_, theme_manager_,
|
|
physical_width, physical_height);
|
|
|
|
// Crear y configurar sistema de ayuda (overlay)
|
|
help_overlay_ = new HelpOverlay();
|
|
help_overlay_->initialize(renderer, theme_manager_, physical_width, physical_height, current_font_size_);
|
|
|
|
// Inicializar FPS counter
|
|
fps_last_time_ = SDL_GetTicks();
|
|
fps_frame_count_ = 0;
|
|
fps_current_ = 0;
|
|
}
|
|
|
|
void UIManager::update(Uint64 current_time, float delta_time) {
|
|
// Calcular FPS
|
|
fps_frame_count_++;
|
|
if (current_time - fps_last_time_ >= 1000) { // Actualizar cada segundo
|
|
fps_current_ = fps_frame_count_;
|
|
fps_frame_count_ = 0;
|
|
fps_last_time_ = current_time;
|
|
fps_text_ = "FPS: " + std::to_string(fps_current_);
|
|
}
|
|
|
|
// Actualizar sistema de notificaciones
|
|
notifier_->update(current_time);
|
|
}
|
|
|
|
void UIManager::render(SDL_Renderer* renderer,
|
|
const Engine* engine,
|
|
const SceneManager* scene_manager,
|
|
SimulationMode current_mode,
|
|
AppMode current_app_mode,
|
|
const Shape* active_shape,
|
|
float shape_convergence,
|
|
int physical_width,
|
|
int physical_height,
|
|
int current_screen_width) {
|
|
// Actualizar dimensiones físicas (puede cambiar en fullscreen)
|
|
physical_window_width_ = physical_width;
|
|
physical_window_height_ = physical_height;
|
|
|
|
// Renderizar debug HUD si está activo
|
|
if (show_debug_) {
|
|
renderDebugHUD(engine, scene_manager, current_mode, current_app_mode,
|
|
active_shape, shape_convergence);
|
|
}
|
|
|
|
// Renderizar notificaciones (siempre al final, sobre todo lo demás)
|
|
notifier_->render();
|
|
|
|
// Renderizar ayuda (siempre última, sobre todo incluso notificaciones)
|
|
if (help_overlay_) {
|
|
help_overlay_->render(renderer);
|
|
}
|
|
}
|
|
|
|
void UIManager::toggleDebug() {
|
|
show_debug_ = !show_debug_;
|
|
}
|
|
|
|
void UIManager::toggleHelp() {
|
|
if (help_overlay_) {
|
|
help_overlay_->toggle();
|
|
}
|
|
}
|
|
|
|
void UIManager::showNotification(const std::string& text, Uint64 duration) {
|
|
if (duration == 0) {
|
|
duration = NOTIFICATION_DURATION;
|
|
}
|
|
notifier_->show(text, duration);
|
|
}
|
|
|
|
void UIManager::updateVSyncText(bool enabled) {
|
|
vsync_text_ = enabled ? "V-Sync: On" : "V-Sync: Off";
|
|
}
|
|
|
|
void UIManager::updatePhysicalWindowSize(int width, int height) {
|
|
physical_window_width_ = width;
|
|
physical_window_height_ = height;
|
|
|
|
// Calcular nuevo tamaño de fuente apropiado basado en altura LÓGICA
|
|
// (las dimensiones lógicas no cambian con zoom, solo con cambios explícitos de resolución)
|
|
int new_font_size = calculateFontSize(logical_window_height_);
|
|
|
|
// Si el tamaño cambió, reinicializar todos los text renderers
|
|
if (new_font_size != current_font_size_) {
|
|
current_font_size_ = new_font_size;
|
|
|
|
// Reinicializar text renderers con nuevo tamaño
|
|
if (text_renderer_debug_) {
|
|
text_renderer_debug_->reinitialize(std::max(9, current_font_size_ - 2));
|
|
}
|
|
if (text_renderer_notifier_) {
|
|
text_renderer_notifier_->reinitialize(current_font_size_);
|
|
}
|
|
}
|
|
|
|
// Actualizar help overlay con font size actual Y nuevas dimensiones (atómicamente)
|
|
if (help_overlay_) {
|
|
help_overlay_->updateAll(current_font_size_, width, height);
|
|
}
|
|
|
|
// Actualizar otros componentes de UI con nuevas dimensiones
|
|
notifier_->updateWindowSize(width, height);
|
|
}
|
|
|
|
// === Métodos privados ===
|
|
|
|
void UIManager::renderDebugHUD(const Engine* engine,
|
|
const SceneManager* scene_manager,
|
|
SimulationMode current_mode,
|
|
AppMode current_app_mode,
|
|
const Shape* active_shape,
|
|
float shape_convergence) {
|
|
int line_height = text_renderer_debug_->getTextHeight();
|
|
int margin = 8;
|
|
SDL_Rect physical_viewport = getPhysicalViewport(renderer_);
|
|
|
|
// --- Construir strings ---
|
|
|
|
std::string appmode_text;
|
|
if (current_app_mode == AppMode::LOGO) {
|
|
appmode_text = "AppMode: LOGO";
|
|
} else if (current_app_mode == AppMode::DEMO) {
|
|
appmode_text = "AppMode: DEMO";
|
|
} else if (current_app_mode == AppMode::DEMO_LITE) {
|
|
appmode_text = "AppMode: DEMO LITE";
|
|
} else {
|
|
appmode_text = "AppMode: SANDBOX";
|
|
}
|
|
|
|
std::string simmode_text;
|
|
if (current_mode == SimulationMode::PHYSICS) {
|
|
simmode_text = "SimMode: PHYSICS";
|
|
} else if (current_mode == SimulationMode::SHAPE) {
|
|
if (active_shape) {
|
|
simmode_text = std::string("SimMode: SHAPE (") + active_shape->getName() + ")";
|
|
} else {
|
|
simmode_text = "SimMode: SHAPE";
|
|
}
|
|
} else if (current_mode == SimulationMode::BOIDS) {
|
|
simmode_text = "SimMode: BOIDS";
|
|
}
|
|
|
|
std::string sprite_name = engine->getCurrentTextureName();
|
|
std::transform(sprite_name.begin(), sprite_name.end(), sprite_name.begin(), ::toupper);
|
|
std::string sprite_text = "Sprite: " + sprite_name;
|
|
|
|
size_t ball_count = scene_manager->getBallCount();
|
|
std::string balls_text;
|
|
if (ball_count >= 1000) {
|
|
std::string count_str = std::to_string(ball_count);
|
|
std::string formatted;
|
|
int digits = static_cast<int>(count_str.length());
|
|
for (int i = 0; i < digits; i++) {
|
|
if (i > 0 && (digits - i) % 3 == 0) formatted += ',';
|
|
formatted += count_str[i];
|
|
}
|
|
balls_text = "Balls: " + formatted;
|
|
} else {
|
|
balls_text = "Balls: " + std::to_string(ball_count);
|
|
}
|
|
|
|
int max_auto_idx = engine->getMaxAutoScenario();
|
|
int max_auto_balls = BALL_COUNT_SCENARIOS[max_auto_idx];
|
|
if (engine->isCustomAutoAvailable() && engine->getCustomScenarioBalls() > max_auto_balls) {
|
|
max_auto_balls = engine->getCustomScenarioBalls();
|
|
}
|
|
std::string max_auto_text;
|
|
if (max_auto_balls >= 1000) {
|
|
std::string count_str = std::to_string(max_auto_balls);
|
|
std::string formatted;
|
|
int digits = static_cast<int>(count_str.length());
|
|
for (int i = 0; i < digits; i++) {
|
|
if (i > 0 && (digits - i) % 3 == 0) formatted += ',';
|
|
formatted += count_str[i];
|
|
}
|
|
max_auto_text = "Auto max: " + formatted;
|
|
} else {
|
|
max_auto_text = "Auto max: " + std::to_string(max_auto_balls);
|
|
}
|
|
|
|
std::string scaling_text;
|
|
if (engine->getFullscreenEnabled() || engine->getRealFullscreenEnabled()) {
|
|
ScalingMode scaling = engine->getCurrentScalingMode();
|
|
if (scaling == ScalingMode::INTEGER) {
|
|
scaling_text = "Scaling: INTEGER";
|
|
} else if (scaling == ScalingMode::LETTERBOX) {
|
|
scaling_text = "Scaling: LETTERBOX";
|
|
} else if (scaling == ScalingMode::STRETCH) {
|
|
scaling_text = "Scaling: STRETCH";
|
|
}
|
|
} else {
|
|
scaling_text = "Scaling: WINDOWED";
|
|
}
|
|
|
|
std::string phys_res_text = "Physical: " + std::to_string(physical_window_width_) + "x" + std::to_string(physical_window_height_);
|
|
std::string logic_res_text = "Logical: " + std::to_string(engine->getCurrentScreenWidth()) + "x" + std::to_string(engine->getCurrentScreenHeight());
|
|
|
|
std::string refresh_text;
|
|
int num_displays = 0;
|
|
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
|
|
if (displays && num_displays > 0) {
|
|
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
|
|
if (dm) {
|
|
refresh_text = "Refresh: " + std::to_string(static_cast<int>(dm->refresh_rate)) + " Hz";
|
|
} else {
|
|
refresh_text = "Refresh: N/A";
|
|
}
|
|
SDL_free(displays);
|
|
} else {
|
|
refresh_text = "Refresh: N/A";
|
|
}
|
|
|
|
std::string theme_text = std::string("Theme: ") + theme_manager_->getCurrentThemeNameEN();
|
|
|
|
Uint64 ticks_ms = SDL_GetTicks();
|
|
Uint64 total_secs = ticks_ms / 1000;
|
|
int hh = static_cast<int>(total_secs / 3600);
|
|
int mm = static_cast<int>((total_secs % 3600) / 60);
|
|
int ss = static_cast<int>(total_secs % 60);
|
|
char elapsed_buf[32];
|
|
SDL_snprintf(elapsed_buf, sizeof(elapsed_buf), "Elapsed: %02d:%02d:%02d", hh, mm, ss);
|
|
std::string elapsed_text(elapsed_buf);
|
|
|
|
// --- Construir vector de líneas en orden ---
|
|
std::vector<std::string> lines;
|
|
lines.push_back(fps_text_);
|
|
lines.push_back(appmode_text);
|
|
lines.push_back(simmode_text);
|
|
lines.push_back(sprite_text);
|
|
lines.push_back(balls_text);
|
|
lines.push_back(max_auto_text);
|
|
lines.push_back(vsync_text_);
|
|
lines.push_back(scaling_text);
|
|
lines.push_back(phys_res_text);
|
|
lines.push_back(logic_res_text);
|
|
lines.push_back(refresh_text);
|
|
lines.push_back(theme_text);
|
|
std::string postfx_text;
|
|
if (!engine->isPostFXEnabled()) {
|
|
postfx_text = "PostFX: OFF";
|
|
} else {
|
|
static constexpr const char* preset_names[4] = {
|
|
"Vinyeta", "Scanlines", "Cromatica", "Complet"
|
|
};
|
|
int mode = engine->getPostFXMode();
|
|
char buf[64];
|
|
SDL_snprintf(buf, sizeof(buf), "PostFX: %s [V:%.2f C:%.2f S:%.2f]",
|
|
preset_names[mode],
|
|
engine->getPostFXVignette(),
|
|
engine->getPostFXChroma(),
|
|
engine->getPostFXScanline());
|
|
postfx_text = buf;
|
|
}
|
|
lines.push_back(postfx_text);
|
|
lines.push_back(elapsed_text);
|
|
|
|
const Ball* first_ball = scene_manager->getFirstBall();
|
|
if (first_ball != nullptr) {
|
|
lines.push_back("VelX: " + std::to_string(static_cast<int>(first_ball->getVelocityX())));
|
|
lines.push_back("VelY: " + std::to_string(static_cast<int>(first_ball->getVelocityY())));
|
|
SDL_FRect pos = first_ball->getPosition();
|
|
lines.push_back("Pos: (" + std::to_string(static_cast<int>(pos.x)) + ", " + std::to_string(static_cast<int>(pos.y)) + ")");
|
|
lines.push_back("Gravity: " + std::to_string(static_cast<int>(first_ball->getGravityForce())));
|
|
lines.push_back(first_ball->isOnSurface() ? "Surface: YES" : "Surface: NO");
|
|
lines.push_back("Loss: " + std::to_string(first_ball->getLossCoefficient()).substr(0, 4));
|
|
lines.push_back("Dir: " + gravityDirectionToString(static_cast<int>(scene_manager->getCurrentGravity())));
|
|
}
|
|
|
|
if (current_app_mode == AppMode::LOGO && current_mode == SimulationMode::SHAPE) {
|
|
int convergence_percent = static_cast<int>(shape_convergence * 100.0f);
|
|
lines.push_back("Convergence: " + std::to_string(convergence_percent) + "%");
|
|
}
|
|
|
|
// --- Render con desbordamiento a segunda columna ---
|
|
int max_lines = (physical_viewport.h - 2 * margin) / line_height;
|
|
if (max_lines < 1) max_lines = 1;
|
|
int col_width = physical_viewport.w / 2;
|
|
|
|
for (int i = 0; i < static_cast<int>(lines.size()); i++) {
|
|
int col = i / max_lines;
|
|
int row = i % max_lines;
|
|
int x = margin + col * col_width;
|
|
int y = margin + row * line_height;
|
|
text_renderer_debug_->printAbsoluteShadowed(x, y, lines[i].c_str());
|
|
}
|
|
}
|
|
|
|
std::string UIManager::gravityDirectionToString(int direction) const {
|
|
switch (direction) {
|
|
case 0: return "Abajo"; // DOWN
|
|
case 1: return "Arriba"; // UP
|
|
case 2: return "Izquierda"; // LEFT
|
|
case 3: return "Derecha"; // RIGHT
|
|
default: return "Desconocida";
|
|
}
|
|
}
|
|
|
|
int UIManager::calculateFontSize(int logical_height) const {
|
|
// Escalado híbrido basado en ALTURA LÓGICA (resolución interna, sin zoom)
|
|
// Esto asegura que el tamaño de fuente sea consistente independientemente del zoom de ventana
|
|
// - Proporcional en extremos (muy bajo/alto)
|
|
// - Escalonado en rango medio (estabilidad)
|
|
|
|
int font_size = 14; // Default fallback
|
|
|
|
if (logical_height < 300) {
|
|
// Rango bajo: proporcional (240px→9.6, 280px→11.2)
|
|
font_size = logical_height / 25;
|
|
} else if (logical_height < 380) {
|
|
// Rango muy bajo (300-379px) → 10px (crítico para 640x360)
|
|
font_size = 10;
|
|
} else if (logical_height < 500) {
|
|
// Rango medio-bajo (380-499px) → 12px
|
|
font_size = 12;
|
|
} else if (logical_height < 700) {
|
|
// Rango medio (500-699px) → 14px
|
|
font_size = 14;
|
|
} else if (logical_height < 900) {
|
|
// Rango medio-alto (700-899px) → 18px
|
|
font_size = 18;
|
|
} else {
|
|
// Rango alto: proporcional (1080px→42, 1440px→55, 2160px→72)
|
|
font_size = logical_height / 26;
|
|
}
|
|
|
|
// Aplicar límites: mínimo 9px, máximo 72px
|
|
if (font_size < 9) font_size = 9;
|
|
if (font_size > 72) font_size = 72;
|
|
|
|
return font_size;
|
|
}
|