Compare commits
8 Commits
f71f7cd5ed
...
9ae851d5b6
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ae851d5b6 | |||
| e1f6fd0f39 | |||
| 093b982e01 | |||
| 74d954df1e | |||
| 46b24bf075 | |||
| 33cb995872 | |||
| c40eb69fc1 | |||
| 1d2e9c5035 |
Binary file not shown.
BIN
data/fonts/Exo2-Regular.ttf
Normal file
BIN
data/fonts/Exo2-Regular.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,7 +6,7 @@ layout(set=3, binding=0) uniform PostFXUniforms {
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float scanline_strength;
|
||||
float time;
|
||||
float screen_height;
|
||||
} u;
|
||||
void main() {
|
||||
float ca = u.chroma_strength * 0.005;
|
||||
@@ -15,7 +15,7 @@ void main() {
|
||||
color.g = texture(scene, v_uv).g;
|
||||
color.b = texture(scene, v_uv - vec2( ca, 0.0)).b;
|
||||
color.a = texture(scene, v_uv).a;
|
||||
float scan = 0.85 + 0.15 * sin(v_uv.y * 3.14159265 * 720.0);
|
||||
float scan = 0.85 + 0.15 * sin(v_uv.y * 3.14159265 * u.screen_height);
|
||||
color.rgb *= mix(1.0, scan, u.scanline_strength);
|
||||
vec2 d = v_uv - vec2(0.5, 0.5);
|
||||
float vignette = 1.0 - dot(d, d) * u.vignette_strength;
|
||||
|
||||
@@ -5,22 +5,29 @@
|
||||
#include <vector> // for std::vector in DynamicThemeKeyframe/DynamicTheme
|
||||
|
||||
// Configuración de ventana y pantalla
|
||||
constexpr char WINDOW_CAPTION[] = "ViBe3 Physics (JailDesigner 2025)";
|
||||
constexpr char WINDOW_CAPTION[] = "© 2025 ViBe3 Physics — JailDesigner";
|
||||
|
||||
// Resolución por defecto (usada si no se especifica en CLI)
|
||||
constexpr int DEFAULT_SCREEN_WIDTH = 1280; // Ancho lógico por defecto (si no hay -w)
|
||||
constexpr int DEFAULT_SCREEN_HEIGHT = 720; // Alto lógico por defecto (si no hay -h)
|
||||
constexpr int DEFAULT_WINDOW_ZOOM = 1; // Zoom inicial de ventana (1x = sin zoom)
|
||||
|
||||
// Configuración de zoom dinámico de ventana
|
||||
constexpr int WINDOW_ZOOM_MIN = 1; // Zoom mínimo (320x240)
|
||||
constexpr int WINDOW_ZOOM_MAX = 10; // Zoom máximo teórico (3200x2400)
|
||||
// Configuración de zoom dinámico de ventana (legacy, solo usado en initialize())
|
||||
constexpr int WINDOW_ZOOM_MIN = 1; // Zoom mínimo (legacy, para validación inicial)
|
||||
constexpr int WINDOW_ZOOM_MAX = 10; // Zoom máximo teórico (legacy)
|
||||
constexpr int WINDOW_DESKTOP_MARGIN = 10; // Margen mínimo con bordes del escritorio
|
||||
constexpr int WINDOW_DECORATION_HEIGHT = 30; // Altura estimada de decoraciones del SO
|
||||
|
||||
// Configuración de escala de ventana por pasos (F1/F2)
|
||||
constexpr float WINDOW_SCALE_STEP = 0.1f; // Incremento/decremento por pulsación (10%)
|
||||
constexpr float WINDOW_SCALE_MIN = 0.5f; // Escala mínima (50% de la resolución base)
|
||||
|
||||
// Configuración de física
|
||||
constexpr float GRAVITY_FORCE = 0.2f; // Fuerza de gravedad (píxeles/frame²)
|
||||
|
||||
// Fuente de la interfaz
|
||||
#define APP_FONT "data/fonts/Exo2-Regular.ttf"
|
||||
|
||||
// Configuración de interfaz
|
||||
constexpr float THEME_TRANSITION_DURATION = 0.5f; // Duración de transiciones LERP entre temas (segundos)
|
||||
|
||||
@@ -52,10 +59,24 @@ constexpr float BALL_SPAWN_MARGIN = 0.15f; // Margen lateral para spawn (0.25 =
|
||||
|
||||
// Escenarios de número de pelotas (teclas 1-8)
|
||||
// Fase 1 (instanced rendering): límit pràctic ~100K a 60fps (physics bound)
|
||||
constexpr int BALL_COUNT_SCENARIOS[8] = {10, 50, 100, 500, 1000, 5000, 10000, 100000};
|
||||
constexpr int SCENE_BALLS_1 = 10;
|
||||
constexpr int SCENE_BALLS_2 = 50;
|
||||
constexpr int SCENE_BALLS_3 = 100;
|
||||
constexpr int SCENE_BALLS_4 = 500;
|
||||
constexpr int SCENE_BALLS_5 = 1000;
|
||||
constexpr int SCENE_BALLS_6 = 5000;
|
||||
constexpr int SCENE_BALLS_7 = 10000;
|
||||
constexpr int SCENE_BALLS_8 = 50000; // Máximo escenario estándar (tecla 8)
|
||||
|
||||
constexpr int SCENARIO_COUNT = 8;
|
||||
constexpr int BALL_COUNT_SCENARIOS[SCENARIO_COUNT] = {
|
||||
SCENE_BALLS_1, SCENE_BALLS_2, SCENE_BALLS_3, SCENE_BALLS_4,
|
||||
SCENE_BALLS_5, SCENE_BALLS_6, SCENE_BALLS_7, SCENE_BALLS_8
|
||||
};
|
||||
|
||||
constexpr int BOIDS_MAX_BALLS = SCENE_BALLS_5; // 1 000 bolas máximo en modo BOIDS
|
||||
|
||||
// Límites de escenario para modos automáticos (índices en BALL_COUNT_SCENARIOS)
|
||||
// BALL_COUNT_SCENARIOS = {10, 50, 100, 500, 1000, 5000, 10000, 50000}
|
||||
// 0 1 2 3 4 5 6 7
|
||||
constexpr int DEMO_AUTO_MIN_SCENARIO = 2; // mínimo 100 bolas
|
||||
constexpr int DEMO_AUTO_MAX_SCENARIO = 7; // máximo sin restricción hardware (ajustado por benchmark)
|
||||
|
||||
@@ -84,8 +84,8 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
|
||||
window_zoom = 1;
|
||||
}
|
||||
|
||||
// Guardar zoom calculado ANTES de crear la ventana (para F1/F2/F3/F4)
|
||||
current_window_zoom_ = window_zoom;
|
||||
// Guardar escala inicial (siempre 1.0 salvo que CLI haya pedido zoom > 1)
|
||||
current_window_scale_ = static_cast<float>(window_zoom);
|
||||
|
||||
// Calcular tamaño de ventana
|
||||
int window_width = logical_width * window_zoom;
|
||||
@@ -231,8 +231,15 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
|
||||
success = false;
|
||||
}
|
||||
|
||||
// Capacidad = número de bolas del escenario máximo (o custom si es mayor).
|
||||
// addBackground() no usa el guard de pushQuad(), así que no consume slots aquí.
|
||||
// init() reserva internamente +1 quad extra garantizado para el overlay.
|
||||
int sprite_capacity = BALL_COUNT_SCENARIOS[DEMO_AUTO_MAX_SCENARIO];
|
||||
if (custom_scenario_enabled_ && custom_scenario_balls_ > sprite_capacity)
|
||||
sprite_capacity = custom_scenario_balls_;
|
||||
|
||||
sprite_batch_ = std::make_unique<GpuSpriteBatch>();
|
||||
if (!sprite_batch_->init(gpu_ctx_->device())) {
|
||||
if (!sprite_batch_->init(gpu_ctx_->device(), sprite_capacity)) {
|
||||
std::cerr << "ERROR: No se pudo crear el sprite batch GPU" << std::endl;
|
||||
success = false;
|
||||
}
|
||||
@@ -275,6 +282,12 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
|
||||
// Inicializar ThemeManager PRIMERO (requerido por Notifier y SceneManager)
|
||||
theme_manager_ = std::make_unique<ThemeManager>();
|
||||
theme_manager_->initialize();
|
||||
{
|
||||
int max_balls = BALL_COUNT_SCENARIOS[DEMO_AUTO_MAX_SCENARIO];
|
||||
if (custom_scenario_enabled_ && custom_scenario_balls_ > max_balls)
|
||||
max_balls = custom_scenario_balls_;
|
||||
theme_manager_->setMaxBallCount(max_balls);
|
||||
}
|
||||
|
||||
// Inicializar SceneManager (gestión de bolas y física)
|
||||
scene_manager_ = std::make_unique<SceneManager>(current_screen_width_, current_screen_height_);
|
||||
@@ -427,7 +440,7 @@ void Engine::calculateDeltaTime() {
|
||||
|
||||
void Engine::update() {
|
||||
// Accumulate time for PostFX uniforms
|
||||
postfx_uniforms_.time += delta_time_;
|
||||
postfx_uniforms_.screen_height = static_cast<float>(current_screen_height_);
|
||||
|
||||
// Actualizar visibilidad del cursor (auto-ocultar tras inactividad)
|
||||
Mouse::updateCursorVisibility();
|
||||
@@ -551,7 +564,21 @@ void Engine::toggleDepthZoom() {
|
||||
}
|
||||
|
||||
// Boids (comportamiento de enjambre)
|
||||
bool Engine::isScenarioAllowedForBoids(int scenario_id) const {
|
||||
int ball_count = (scenario_id == CUSTOM_SCENARIO_IDX)
|
||||
? custom_scenario_balls_
|
||||
: BALL_COUNT_SCENARIOS[scenario_id];
|
||||
return ball_count <= BOIDS_MAX_BALLS;
|
||||
}
|
||||
|
||||
void Engine::toggleBoidsMode(bool force_gravity_on) {
|
||||
if (current_mode_ != SimulationMode::BOIDS) {
|
||||
// Intentando activar BOIDS — verificar escenario actual
|
||||
if (!isScenarioAllowedForBoids(scene_manager_->getCurrentScenario())) {
|
||||
showNotificationForAction("Boids: máximo 1.000 pelotas");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (current_mode_ == SimulationMode::BOIDS) {
|
||||
// Salir del modo boids
|
||||
current_mode_ = SimulationMode::PHYSICS;
|
||||
@@ -641,6 +668,12 @@ void Engine::setCustomScenario(int balls) {
|
||||
|
||||
// Escenarios (número de pelotas)
|
||||
void Engine::changeScenario(int scenario_id, const char* notification_text) {
|
||||
if (current_mode_ == SimulationMode::BOIDS) {
|
||||
if (!isScenarioAllowedForBoids(scenario_id)) {
|
||||
showNotificationForAction("Boids: máximo 1.000 pelotas");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Pasar el modo actual al SceneManager para inicialización correcta
|
||||
scene_manager_->changeScenario(scenario_id, current_mode_);
|
||||
|
||||
@@ -953,7 +986,9 @@ void Engine::toggleFullscreen() {
|
||||
|
||||
// Si acabamos de salir de fullscreen, restaurar tamaño de ventana
|
||||
if (!fullscreen_enabled_) {
|
||||
SDL_SetWindowSize(window_, base_screen_width_ * current_window_zoom_, base_screen_height_ * current_window_zoom_);
|
||||
int restore_w = static_cast<int>(std::round(base_screen_width_ * current_window_scale_));
|
||||
int restore_h = static_cast<int>(std::round(base_screen_height_ * current_window_scale_));
|
||||
SDL_SetWindowSize(window_, restore_w, restore_h);
|
||||
SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
}
|
||||
|
||||
@@ -1018,10 +1053,13 @@ void Engine::toggleRealFullscreen() {
|
||||
// Volver a resolución base (configurada por CLI o default)
|
||||
current_screen_width_ = base_screen_width_;
|
||||
current_screen_height_ = base_screen_height_;
|
||||
current_field_scale_ = 1.0f; // Resetear escala de campo al salir de fullscreen real
|
||||
|
||||
// Restaurar ventana normal con el zoom actual (no hardcoded)
|
||||
// Restaurar ventana normal con la escala actual
|
||||
SDL_SetWindowFullscreen(window_, false);
|
||||
SDL_SetWindowSize(window_, base_screen_width_ * current_window_zoom_, base_screen_height_ * current_window_zoom_);
|
||||
int restore_w = static_cast<int>(std::round(base_screen_width_ * current_window_scale_));
|
||||
int restore_h = static_cast<int>(std::round(base_screen_height_ * current_window_scale_));
|
||||
SDL_SetWindowSize(window_, restore_w, restore_h);
|
||||
SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
|
||||
// Recrear render target offscreen con resolución base
|
||||
@@ -1148,85 +1186,112 @@ void Engine::addSpriteToBatch(float x, float y, float w, float h, int r, int g,
|
||||
static_cast<float>(current_screen_height_));
|
||||
}
|
||||
|
||||
// Sistema de zoom dinámico
|
||||
int Engine::calculateMaxWindowZoom() const {
|
||||
// Obtener información del display usando el método de Coffee Crisis
|
||||
int num_displays = 0;
|
||||
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
|
||||
if (displays == nullptr || num_displays == 0) {
|
||||
return WINDOW_ZOOM_MIN; // Fallback si no se puede obtener
|
||||
// Sistema de escala de ventana (pasos del 10%)
|
||||
float Engine::calculateMaxWindowScale() const {
|
||||
SDL_Rect bounds;
|
||||
if (!SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &bounds)) { // bool: false = error
|
||||
return WINDOW_SCALE_MIN; // Fallback solo si falla de verdad
|
||||
}
|
||||
|
||||
// Obtener el modo de display actual
|
||||
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
|
||||
if (dm == nullptr) {
|
||||
SDL_free(displays);
|
||||
return WINDOW_ZOOM_MIN;
|
||||
}
|
||||
|
||||
// Calcular zoom máximo usando la fórmula de Coffee Crisis
|
||||
const int MAX_ZOOM = std::min(dm->w / base_screen_width_, (dm->h - WINDOW_DECORATION_HEIGHT) / base_screen_height_);
|
||||
|
||||
SDL_free(displays);
|
||||
|
||||
// Aplicar límites
|
||||
return std::max(WINDOW_ZOOM_MIN, std::min(MAX_ZOOM, WINDOW_ZOOM_MAX));
|
||||
float max_by_w = static_cast<float>(bounds.w - 2 * WINDOW_DESKTOP_MARGIN) / base_screen_width_;
|
||||
float max_by_h = static_cast<float>(bounds.h - 2 * WINDOW_DESKTOP_MARGIN - WINDOW_DECORATION_HEIGHT) / base_screen_height_;
|
||||
float result = std::max(WINDOW_SCALE_MIN, std::min(max_by_w, max_by_h));
|
||||
return result;
|
||||
}
|
||||
|
||||
void Engine::setWindowZoom(int new_zoom) {
|
||||
// Validar zoom
|
||||
int max_zoom = calculateMaxWindowZoom();
|
||||
new_zoom = std::max(WINDOW_ZOOM_MIN, std::min(new_zoom, max_zoom));
|
||||
// Redimensiona la ventana física manteniéndo su centro, con clamping a pantalla.
|
||||
static void resizeWindowCentered(SDL_Window* window, int new_w, int new_h) {
|
||||
int cur_x, cur_y, cur_w, cur_h;
|
||||
SDL_GetWindowPosition(window, &cur_x, &cur_y);
|
||||
SDL_GetWindowSize(window, &cur_w, &cur_h);
|
||||
|
||||
if (new_zoom == current_window_zoom_) {
|
||||
return; // No hay cambio
|
||||
int new_x = cur_x + (cur_w - new_w) / 2;
|
||||
int new_y = cur_y + (cur_h - new_h) / 2;
|
||||
|
||||
SDL_Rect bounds;
|
||||
if (SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &bounds)) {
|
||||
new_x = std::max(WINDOW_DESKTOP_MARGIN,
|
||||
std::min(new_x, bounds.w - new_w - WINDOW_DESKTOP_MARGIN));
|
||||
new_y = std::max(WINDOW_DESKTOP_MARGIN,
|
||||
std::min(new_y, bounds.h - new_h - WINDOW_DESKTOP_MARGIN - WINDOW_DECORATION_HEIGHT));
|
||||
}
|
||||
|
||||
// Obtener posición actual del centro de la ventana
|
||||
int current_x, current_y;
|
||||
SDL_GetWindowPosition(window_, ¤t_x, ¤t_y);
|
||||
int current_center_x = current_x + (base_screen_width_ * current_window_zoom_) / 2;
|
||||
int current_center_y = current_y + (base_screen_height_ * current_window_zoom_) / 2;
|
||||
SDL_SetWindowSize(window, new_w, new_h);
|
||||
SDL_SetWindowPosition(window, new_x, new_y);
|
||||
}
|
||||
|
||||
// Calcular nuevo tamaño
|
||||
int new_width = base_screen_width_ * new_zoom;
|
||||
int new_height = base_screen_height_ * new_zoom;
|
||||
void Engine::setWindowScale(float new_scale) {
|
||||
float max_scale = calculateMaxWindowScale();
|
||||
new_scale = std::max(WINDOW_SCALE_MIN, std::min(new_scale, max_scale));
|
||||
new_scale = std::round(new_scale * 10.0f) / 10.0f;
|
||||
|
||||
// Calcular nueva posición (centrada en el punto actual)
|
||||
int new_x = current_center_x - new_width / 2;
|
||||
int new_y = current_center_y - new_height / 2;
|
||||
if (new_scale == current_window_scale_) return;
|
||||
|
||||
// Obtener límites del escritorio para no salirse
|
||||
SDL_Rect display_bounds;
|
||||
if (SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &display_bounds) == 0) {
|
||||
// Aplicar márgenes
|
||||
int min_x = WINDOW_DESKTOP_MARGIN;
|
||||
int min_y = WINDOW_DESKTOP_MARGIN;
|
||||
int max_x = display_bounds.w - new_width - WINDOW_DESKTOP_MARGIN;
|
||||
int max_y = display_bounds.h - new_height - WINDOW_DESKTOP_MARGIN - WINDOW_DECORATION_HEIGHT;
|
||||
int new_width = static_cast<int>(std::round(current_screen_width_ * new_scale));
|
||||
int new_height = static_cast<int>(std::round(current_screen_height_ * new_scale));
|
||||
|
||||
// Limitar posición
|
||||
new_x = std::max(min_x, std::min(new_x, max_x));
|
||||
new_y = std::max(min_y, std::min(new_y, max_y));
|
||||
}
|
||||
resizeWindowCentered(window_, new_width, new_height);
|
||||
current_window_scale_ = new_scale;
|
||||
|
||||
// Aplicar cambios
|
||||
SDL_SetWindowSize(window_, new_width, new_height);
|
||||
SDL_SetWindowPosition(window_, new_x, new_y);
|
||||
current_window_zoom_ = new_zoom;
|
||||
|
||||
// Actualizar tamaño físico de ventana y fuentes
|
||||
updatePhysicalWindowSize();
|
||||
}
|
||||
|
||||
void Engine::zoomIn() {
|
||||
setWindowZoom(current_window_zoom_ + 1);
|
||||
float prev = current_window_scale_;
|
||||
setWindowScale(current_window_scale_ + WINDOW_SCALE_STEP);
|
||||
if (current_window_scale_ != prev) {
|
||||
char buf[32];
|
||||
std::snprintf(buf, sizeof(buf), "Zoom %.0f%%", current_window_scale_ * 100.0f);
|
||||
showNotificationForAction(buf);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::zoomOut() {
|
||||
setWindowZoom(current_window_zoom_ - 1);
|
||||
float prev = current_window_scale_;
|
||||
setWindowScale(current_window_scale_ - WINDOW_SCALE_STEP);
|
||||
if (current_window_scale_ != prev) {
|
||||
char buf[32];
|
||||
std::snprintf(buf, sizeof(buf), "Zoom %.0f%%", current_window_scale_ * 100.0f);
|
||||
showNotificationForAction(buf);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::setFieldScale(float new_scale) {
|
||||
float max_scale = calculateMaxWindowScale();
|
||||
new_scale = std::max(WINDOW_SCALE_MIN, std::min(new_scale, max_scale));
|
||||
new_scale = std::round(new_scale * 10.0f) / 10.0f;
|
||||
if (new_scale == current_field_scale_) return;
|
||||
|
||||
current_field_scale_ = new_scale;
|
||||
current_screen_width_ = static_cast<int>(std::round(base_screen_width_ * new_scale));
|
||||
current_screen_height_ = static_cast<int>(std::round(base_screen_height_ * new_scale));
|
||||
|
||||
// Ajustar ventana física: campo lógico × zoom actual, manteniendo centro
|
||||
int phys_w = static_cast<int>(std::round(current_screen_width_ * current_window_scale_));
|
||||
int phys_h = static_cast<int>(std::round(current_screen_height_ * current_window_scale_));
|
||||
resizeWindowCentered(window_, phys_w, phys_h);
|
||||
|
||||
// Recrear render target con nueva resolución lógica
|
||||
recreateOffscreenTexture();
|
||||
updatePhysicalWindowSize();
|
||||
|
||||
// Reiniciar escena (igual que F4)
|
||||
scene_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||
scene_manager_->changeScenario(scene_manager_->getCurrentScenario(), current_mode_);
|
||||
boid_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||
shape_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||
if (app_logo_) app_logo_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
generateShape();
|
||||
scene_manager_->enableShapeAttractionAll(true);
|
||||
}
|
||||
|
||||
showNotificationForAction("Campo " + std::to_string(current_screen_width_) +
|
||||
" x " + std::to_string(current_screen_height_));
|
||||
}
|
||||
|
||||
void Engine::fieldSizeUp() { setFieldScale(current_field_scale_ + WINDOW_SCALE_STEP); }
|
||||
void Engine::fieldSizeDown() { setFieldScale(current_field_scale_ - WINDOW_SCALE_STEP); }
|
||||
|
||||
void Engine::updatePhysicalWindowSize() {
|
||||
if (real_fullscreen_enabled_) {
|
||||
// En fullscreen real (F4), usar resolución del display
|
||||
@@ -1253,7 +1318,11 @@ void Engine::updatePhysicalWindowSize() {
|
||||
}
|
||||
|
||||
// Notificar a UIManager del cambio de tamaño (delegado)
|
||||
ui_manager_->updatePhysicalWindowSize(physical_window_width_, physical_window_height_);
|
||||
// Pasar current_screen_height_ para que UIManager actualice la altura lógica
|
||||
// (necesario en F4 donde la resolución lógica cambia a la del display)
|
||||
ui_manager_->updatePhysicalWindowSize(physical_window_width_, physical_window_height_,
|
||||
current_screen_height_);
|
||||
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -76,6 +76,11 @@ class Engine {
|
||||
void toggleRealFullscreen();
|
||||
void toggleIntegerScaling();
|
||||
|
||||
// Campo de juego (tamaño lógico + físico)
|
||||
void fieldSizeUp();
|
||||
void fieldSizeDown();
|
||||
void setFieldScale(float new_scale);
|
||||
|
||||
// PostFX presets
|
||||
void handlePostFXCycle();
|
||||
void handlePostFXToggle();
|
||||
@@ -187,8 +192,11 @@ class Engine {
|
||||
float postfx_override_vignette_ = -1.f; // -1 = sin override
|
||||
float postfx_override_chroma_ = -1.f;
|
||||
|
||||
// Sistema de zoom dinámico
|
||||
int current_window_zoom_ = DEFAULT_WINDOW_ZOOM;
|
||||
// Sistema de escala de ventana
|
||||
float current_window_scale_ = 1.0f;
|
||||
|
||||
// Escala del campo de juego lógico (F7/F8)
|
||||
float current_field_scale_ = 1.0f;
|
||||
|
||||
// V-Sync y fullscreen
|
||||
bool vsync_enabled_ = true;
|
||||
@@ -242,9 +250,9 @@ class Engine {
|
||||
// Sistema de cambio de sprites dinámico
|
||||
void switchTextureInternal(bool show_notification);
|
||||
|
||||
// Sistema de zoom dinámico
|
||||
int calculateMaxWindowZoom() const;
|
||||
void setWindowZoom(int new_zoom);
|
||||
// Sistema de escala de ventana
|
||||
float calculateMaxWindowScale() const;
|
||||
void setWindowScale(float new_scale);
|
||||
void zoomIn();
|
||||
void zoomOut();
|
||||
void updatePhysicalWindowSize();
|
||||
@@ -261,6 +269,9 @@ class Engine {
|
||||
// PostFX helper
|
||||
void applyPostFXPreset(int mode);
|
||||
|
||||
// Boids: comprueba si un escenario tiene ≤ BOIDS_MAX_BALLS bolas
|
||||
bool isScenarioAllowedForBoids(int scenario_id) const;
|
||||
|
||||
// GPU helpers
|
||||
bool loadGpuSpriteTexture(size_t index); // Upload one sprite texture to GPU
|
||||
void recreateOffscreenTexture(); // Recreate when resolution changes
|
||||
|
||||
6
source/external/texture.cpp
vendored
6
source/external/texture.cpp
vendored
@@ -50,11 +50,7 @@ bool Texture::loadFromFile(const std::string &file_path) {
|
||||
delete[] resourceData; // Liberar buffer temporal
|
||||
|
||||
if (data != nullptr) {
|
||||
if (ResourceManager::isPackLoaded()) {
|
||||
std::cout << "Imagen cargada desde pack: " << filename.c_str() << std::endl;
|
||||
} else {
|
||||
std::cout << "Imagen cargada desde disco: " << filename.c_str() << std::endl;
|
||||
}
|
||||
std::cout << "[Textura] " << filename << " (" << (ResourceManager::isPackLoaded() ? "pack" : "disco") << ")\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ struct PostFXUniforms {
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float scanline_strength;
|
||||
float time;
|
||||
float screen_height;
|
||||
};
|
||||
|
||||
fragment float4 postfx_fs(PostVOut in [[stage_in]],
|
||||
@@ -133,7 +133,7 @@ fragment float4 postfx_fs(PostVOut in [[stage_in]],
|
||||
color.a = scene.sample(samp, in.uv ).a;
|
||||
|
||||
// Scanlines: horizontal sine-wave at ~360 lines (one dark band per 2 px at 720p)
|
||||
float scan = 0.85 + 0.15 * sin(in.uv.y * 3.14159265 * 720.0);
|
||||
float scan = 0.85 + 0.15 * sin(in.uv.y * 3.14159265 * u.screen_height);
|
||||
color.rgb *= mix(1.0, scan, u.scanline_strength);
|
||||
|
||||
// Vignette: radial edge darkening
|
||||
|
||||
@@ -11,7 +11,7 @@ struct PostFXUniforms {
|
||||
float vignette_strength; // 0 = none, 0.8 = default subtle
|
||||
float chroma_strength; // 0 = off, 0.2 = default chromatic aberration
|
||||
float scanline_strength; // 0 = off, 1 = full scanlines
|
||||
float time; // accumulated seconds (for future animations)
|
||||
float screen_height; // logical render target height (px), for resolution-independent scanlines
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
// Public interface
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool GpuSpriteBatch::init(SDL_GPUDevice* device) {
|
||||
// Pre-allocate GPU buffers large enough for MAX_SPRITES quads.
|
||||
Uint32 max_verts = static_cast<Uint32>(MAX_SPRITES) * 4;
|
||||
Uint32 max_indices = static_cast<Uint32>(MAX_SPRITES) * 6;
|
||||
bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
|
||||
max_sprites_ = max_sprites;
|
||||
// Pre-allocate GPU buffers large enough for (max_sprites_ + 2) quads.
|
||||
// The +2 reserves one slot for the background quad and one for the fullscreen overlay.
|
||||
Uint32 max_verts = static_cast<Uint32>(max_sprites_ + 2) * 4;
|
||||
Uint32 max_indices = static_cast<Uint32>(max_sprites_ + 2) * 6;
|
||||
|
||||
Uint32 vb_size = max_verts * sizeof(GpuVertex);
|
||||
Uint32 ib_size = max_indices * sizeof(uint32_t);
|
||||
@@ -53,8 +55,8 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vertices_.reserve(MAX_SPRITES * 4);
|
||||
indices_.reserve(MAX_SPRITES * 6);
|
||||
vertices_.reserve(static_cast<size_t>(max_sprites_ + 2) * 4);
|
||||
indices_.reserve(static_cast<size_t>(max_sprites_ + 2) * 6);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -128,9 +130,17 @@ void GpuSpriteBatch::addSprite(float x, float y, float w, float h,
|
||||
}
|
||||
|
||||
void GpuSpriteBatch::addFullscreenOverlay() {
|
||||
// El overlay es un slot reservado fuera del espacio de max_sprites_, igual que el background.
|
||||
// Escribe directamente sin pasar por el guard de pushQuad().
|
||||
overlay_index_offset_ = static_cast<int>(indices_.size());
|
||||
pushQuad(-1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
overlay_index_count_ = 6;
|
||||
uint32_t vi = static_cast<uint32_t>(vertices_.size());
|
||||
vertices_.push_back({ -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
vertices_.push_back({ 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
vertices_.push_back({ 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
vertices_.push_back({ -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
indices_.push_back(vi + 0); indices_.push_back(vi + 1); indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 2); indices_.push_back(vi + 3); indices_.push_back(vi + 0);
|
||||
overlay_index_count_ = 6;
|
||||
}
|
||||
|
||||
bool GpuSpriteBatch::uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf) {
|
||||
@@ -179,7 +189,8 @@ void GpuSpriteBatch::toNDC(float px, float py,
|
||||
void GpuSpriteBatch::pushQuad(float ndx0, float ndy0, float ndx1, float ndy1,
|
||||
float u0, float v0, float u1, float v1,
|
||||
float r, float g, float b, float a) {
|
||||
if (vertices_.size() + 4 > static_cast<size_t>(MAX_SPRITES) * 4) return;
|
||||
// +1 reserva el slot del background que ya entró sin pasar por este guard.
|
||||
if (vertices_.size() + 4 > static_cast<size_t>(max_sprites_ + 1) * 4) return;
|
||||
uint32_t vi = static_cast<uint32_t>(vertices_.size());
|
||||
|
||||
// TL, TR, BR, BL
|
||||
|
||||
@@ -26,10 +26,10 @@ struct GpuVertex {
|
||||
// ============================================================================
|
||||
class GpuSpriteBatch {
|
||||
public:
|
||||
// Maximum sprites (background + UI overlay each count as one sprite)
|
||||
static constexpr int MAX_SPRITES = 200000;
|
||||
// Default maximum sprites (background + UI overlay each count as one sprite)
|
||||
static constexpr int DEFAULT_MAX_SPRITES = 200000;
|
||||
|
||||
bool init(SDL_GPUDevice* device);
|
||||
bool init(SDL_GPUDevice* device, int max_sprites = DEFAULT_MAX_SPRITES);
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
|
||||
void beginFrame();
|
||||
@@ -83,4 +83,6 @@ private:
|
||||
int sprite_index_count_ = 0;
|
||||
int overlay_index_offset_ = 0;
|
||||
int overlay_index_count_ = 0;
|
||||
|
||||
int max_sprites_ = DEFAULT_MAX_SPRITES;
|
||||
};
|
||||
|
||||
@@ -264,6 +264,17 @@ bool InputHandler::processEvents(Engine& engine) {
|
||||
engine.toggleIntegerScaling();
|
||||
break;
|
||||
|
||||
// Redimensionar campo de juego (tamaño lógico + físico)
|
||||
case SDLK_F7:
|
||||
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||
else engine.fieldSizeDown();
|
||||
break;
|
||||
|
||||
case SDLK_F8:
|
||||
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||
else engine.fieldSizeUp();
|
||||
break;
|
||||
|
||||
// Toggle Modo DEMO COMPLETO (auto-play) o Pausar tema dinámico (Shift+D)
|
||||
case SDLK_D:
|
||||
// Shift+D = Pausar tema dinámico
|
||||
|
||||
@@ -11,9 +11,9 @@ void printHelp() {
|
||||
std::cout << "ViBe3 Physics - Simulador de físicas avanzadas\n";
|
||||
std::cout << "\nUso: vibe3_physics [opciones]\n\n";
|
||||
std::cout << "Opciones:\n";
|
||||
std::cout << " -w, --width <px> Ancho de resolución (default: 320)\n";
|
||||
std::cout << " -h, --height <px> Alto de resolución (default: 240)\n";
|
||||
std::cout << " -z, --zoom <n> Zoom de ventana (default: 3)\n";
|
||||
std::cout << " -w, --width <px> Ancho de resolución (default: " << DEFAULT_SCREEN_WIDTH << ")\n";
|
||||
std::cout << " -h, --height <px> Alto de resolución (default: " << DEFAULT_SCREEN_HEIGHT << ")\n";
|
||||
std::cout << " -z, --zoom <n> Zoom de ventana (default: " << DEFAULT_WINDOW_ZOOM << ")\n";
|
||||
std::cout << " -f, --fullscreen Modo pantalla completa (F3 - letterbox)\n";
|
||||
std::cout << " -F, --real-fullscreen Modo pantalla completa real (F4 - nativo)\n";
|
||||
std::cout << " -k, --kiosk Modo kiosko (F4 fijo, sin ESC, sin zoom)\n";
|
||||
@@ -26,7 +26,7 @@ void printHelp() {
|
||||
std::cout << " --chroma <float> Sobreescribir chroma_strength (activa PostFX si no hay --postfx)\n";
|
||||
std::cout << " --help Mostrar esta ayuda\n\n";
|
||||
std::cout << "Ejemplos:\n";
|
||||
std::cout << " vibe3_physics # 320x240 zoom 3 (ventana 960x720)\n";
|
||||
std::cout << " vibe3_physics # " << DEFAULT_SCREEN_WIDTH << "x" << DEFAULT_SCREEN_HEIGHT << " zoom " << DEFAULT_WINDOW_ZOOM << " (default)\n";
|
||||
std::cout << " vibe3_physics -w 1920 -h 1080 # 1920x1080 zoom 1 (auto)\n";
|
||||
std::cout << " vibe3_physics -w 640 -h 480 -z 2 # 640x480 zoom 2 (ventana 1280x960)\n";
|
||||
std::cout << " vibe3_physics -f # Fullscreen letterbox (F3)\n";
|
||||
|
||||
@@ -22,7 +22,11 @@ PNGShape::PNGShape(const char* png_path) {
|
||||
}
|
||||
|
||||
bool PNGShape::loadPNG(const char* resource_key) {
|
||||
std::cout << "[PNGShape] Cargando recurso: " << resource_key << std::endl;
|
||||
{
|
||||
std::string fn = std::string(resource_key);
|
||||
fn = fn.substr(fn.find_last_of("\\/") + 1);
|
||||
std::cout << "[PNGShape] " << fn << " (" << (ResourceManager::isPackLoaded() ? "pack" : "disco") << ")\n";
|
||||
}
|
||||
unsigned char* file_data = nullptr;
|
||||
size_t file_size = 0;
|
||||
if (!ResourceManager::loadResource(resource_key, file_data, file_size)) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "textrenderer.hpp"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
#include <iostream>
|
||||
#include "resource_manager.hpp"
|
||||
|
||||
TextRenderer::TextRenderer() : renderer_(nullptr), font_(nullptr), font_size_(0), use_antialiasing_(true), font_data_buffer_(nullptr) {
|
||||
@@ -44,7 +45,11 @@ bool TextRenderer::init(SDL_Renderer* renderer, const char* font_path, int font_
|
||||
// CRÍTICO: NO eliminar fontData aquí - SDL_ttf necesita estos datos en memoria
|
||||
// mientras la fuente esté abierta. Se liberará en cleanup()
|
||||
font_data_buffer_ = fontData;
|
||||
SDL_Log("Fuente cargada desde ResourceManager: %s (%lu bytes)", font_path, (unsigned long)fontDataSize);
|
||||
{
|
||||
std::string fn = std::string(font_path);
|
||||
fn = fn.substr(fn.find_last_of("\\/") + 1);
|
||||
std::cout << "[Fuente] " << fn << " (" << (ResourceManager::isPackLoaded() ? "pack" : "disco") << ")\n";
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
delete[] fontData;
|
||||
@@ -102,7 +107,10 @@ bool TextRenderer::reinitialize(int new_font_size) {
|
||||
// Mantener buffer en memoria (NO eliminar)
|
||||
font_data_buffer_ = fontData;
|
||||
font_size_ = new_font_size;
|
||||
SDL_Log("Fuente recargada desde ResourceManager: %s (tamaño %d)", font_path_.c_str(), new_font_size);
|
||||
{
|
||||
std::string fn = font_path_.substr(font_path_.find_last_of("\\/") + 1);
|
||||
std::cout << "[Fuente] " << fn << " (" << (ResourceManager::isPackLoaded() ? "pack" : "disco") << ")\n";
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
delete[] fontData;
|
||||
|
||||
@@ -640,10 +640,10 @@ std::unique_ptr<ThemeSnapshot> ThemeManager::captureCurrentSnapshot() const {
|
||||
snapshot->name_en = themes_[current_theme_index_]->getNameEN();
|
||||
snapshot->name_es = themes_[current_theme_index_]->getNameES();
|
||||
|
||||
// Capturar colores de pelotas (suficientes para escenario máximo: 50,000)
|
||||
// Esto asegura LERP correcto incluso en escenarios grandes
|
||||
snapshot->ball_colors.reserve(50000);
|
||||
for (size_t i = 0; i < 50000; i++) {
|
||||
// Capturar colores de pelotas para el máximo real de esta sesión
|
||||
// (SCENE_BALLS_8 o más si hay escenario custom)
|
||||
snapshot->ball_colors.reserve(max_ball_count_);
|
||||
for (int i = 0; i < max_ball_count_; i++) {
|
||||
snapshot->ball_colors.push_back(
|
||||
themes_[current_theme_index_]->getBallColor(i, 0.0f)
|
||||
);
|
||||
|
||||
@@ -43,6 +43,7 @@ class ThemeManager {
|
||||
|
||||
// Inicialización
|
||||
void initialize(); // Inicializa 15 temas unificados (9 estáticos + 6 dinámicos)
|
||||
void setMaxBallCount(int n) { max_ball_count_ = n; } // Máximo real (escenario 8 o custom si mayor)
|
||||
|
||||
// Interfaz unificada (PHASE 2 + PHASE 3)
|
||||
void switchToTheme(int theme_index); // Cambia a tema 0-14 con transición LERP suave (PHASE 3)
|
||||
@@ -99,6 +100,9 @@ class ThemeManager {
|
||||
// Snapshot del tema origen (capturado al iniciar transición)
|
||||
std::unique_ptr<ThemeSnapshot> source_snapshot_; // nullptr si no hay transición
|
||||
|
||||
// Máximo de bolas posible en esta sesión (max(SCENE_BALLS_8, custom_balls))
|
||||
int max_ball_count_ = SCENE_BALLS_8;
|
||||
|
||||
// ========================================
|
||||
// MÉTODOS PRIVADOS
|
||||
// ========================================
|
||||
|
||||
@@ -65,12 +65,6 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he
|
||||
int logo_base_target_height = static_cast<int>(base_screen_height_ * APPLOGO_HEIGHT_PERCENT);
|
||||
int logo_native_target_height = static_cast<int>(native_screen_height_ * APPLOGO_HEIGHT_PERCENT);
|
||||
|
||||
std::cout << "Pre-escalando logos:" << std::endl;
|
||||
std::cout << " Base: " << base_screen_width_ << "x" << base_screen_height_
|
||||
<< " (altura logo: " << logo_base_target_height << "px)" << std::endl;
|
||||
std::cout << " Nativa: " << native_screen_width_ << "x" << native_screen_height_
|
||||
<< " (altura logo: " << logo_native_target_height << "px)" << std::endl;
|
||||
|
||||
// ========================================================================
|
||||
// 3. Cargar y escalar LOGO1 (data/logo/logo.png) a 2 versiones
|
||||
// ========================================================================
|
||||
@@ -193,7 +187,7 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he
|
||||
logo2_current_width_ = logo2_base_width_;
|
||||
logo2_current_height_ = logo2_base_height_;
|
||||
|
||||
std::cout << "Logos pre-escalados exitosamente (4 texturas creadas)" << std::endl;
|
||||
std::cout << "[Logo] logo.png + logo2.png (base " << logo_base_target_height << "px, nativa " << logo_native_target_height << "px)\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <algorithm> // for std::min
|
||||
|
||||
#include "defines.hpp"
|
||||
#include "text/textrenderer.hpp"
|
||||
#include "theme_manager.hpp"
|
||||
|
||||
@@ -101,7 +102,7 @@ void HelpOverlay::initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, in
|
||||
|
||||
// Crear renderer de texto con tamaño dinámico
|
||||
text_renderer_ = new TextRenderer();
|
||||
text_renderer_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", font_size, true);
|
||||
text_renderer_->init(renderer, APP_FONT, font_size, true);
|
||||
|
||||
calculateBoxDimensions();
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ bool LogoScaler::detectNativeResolution(int& native_width, int& native_height) {
|
||||
|
||||
SDL_free(displays);
|
||||
|
||||
std::cout << "Resolución nativa detectada: " << native_width << "x" << native_height << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -73,8 +72,6 @@ unsigned char* LogoScaler::loadAndScale(const std::string& path,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::cout << "Imagen cargada: " << path << " (" << orig_width << "x" << orig_height << ")" << std::endl;
|
||||
|
||||
// 2. Calcular tamaño final manteniendo aspect ratio
|
||||
// El alto está fijado por target_height (APPLOGO_HEIGHT_PERCENT)
|
||||
// Calcular ancho proporcional al aspect ratio original
|
||||
@@ -82,8 +79,6 @@ unsigned char* LogoScaler::loadAndScale(const std::string& path,
|
||||
out_width = static_cast<int>(target_height * aspect_ratio);
|
||||
out_height = target_height;
|
||||
|
||||
std::cout << " Escalando a: " << out_width << "x" << out_height << std::endl;
|
||||
|
||||
// 3. Alocar buffer para imagen escalada (RGBA = 4 bytes por píxel)
|
||||
unsigned char* scaled_data = static_cast<unsigned char*>(malloc(out_width * out_height * 4));
|
||||
if (scaled_data == nullptr) {
|
||||
@@ -109,7 +104,6 @@ unsigned char* LogoScaler::loadAndScale(const std::string& path,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::cout << " Escalado completado correctamente" << std::endl;
|
||||
return scaled_data;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,8 +90,8 @@ void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
|
||||
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);
|
||||
text_renderer_debug_->init(renderer, APP_FONT, std::max(9, current_font_size_ - 2), true);
|
||||
text_renderer_notifier_->init(renderer, APP_FONT, current_font_size_, true);
|
||||
|
||||
// Crear y configurar sistema de notificaciones
|
||||
notifier_ = new Notifier();
|
||||
@@ -172,10 +172,15 @@ void UIManager::updateVSyncText(bool enabled) {
|
||||
vsync_text_ = enabled ? "V-Sync: On" : "V-Sync: Off";
|
||||
}
|
||||
|
||||
void UIManager::updatePhysicalWindowSize(int width, int height) {
|
||||
void UIManager::updatePhysicalWindowSize(int width, int height, int logical_height) {
|
||||
physical_window_width_ = width;
|
||||
physical_window_height_ = height;
|
||||
|
||||
// Actualizar altura lógica si se proporciona (ej. al entrar/salir de F4)
|
||||
if (logical_height > 0) {
|
||||
logical_window_height_ = logical_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_);
|
||||
@@ -418,9 +423,15 @@ int UIManager::calculateFontSize(int logical_height) const {
|
||||
} else if (logical_height < 900) {
|
||||
// Rango medio-alto (700-899px) → 18px
|
||||
font_size = 18;
|
||||
} else if (logical_height < 1200) {
|
||||
// Rango alto (900-1199px): 900→22, 1080→27, 1199→29
|
||||
font_size = logical_height / 40;
|
||||
} else if (logical_height < 1600) {
|
||||
// Rango muy alto (1200-1599px): 1200→25, 1440→30
|
||||
font_size = logical_height / 48;
|
||||
} else {
|
||||
// Rango alto: proporcional (1080px→42, 1440px→55, 2160px→72)
|
||||
font_size = logical_height / 26;
|
||||
// Rango ultra (>=1600px): 1600→26, 2000→33, 2160→36
|
||||
font_size = logical_height / 60;
|
||||
}
|
||||
|
||||
// Aplicar límites: mínimo 9px, máximo 72px
|
||||
|
||||
@@ -111,8 +111,9 @@ class UIManager {
|
||||
* @brief Actualiza tamaño físico de ventana (cambios de fullscreen)
|
||||
* @param width Nuevo ancho físico
|
||||
* @param height Nuevo alto físico
|
||||
* @param logical_height Nuevo alto lógico (0 = sin cambio)
|
||||
*/
|
||||
void updatePhysicalWindowSize(int width, int height);
|
||||
void updatePhysicalWindowSize(int width, int height, int logical_height = 0);
|
||||
|
||||
// === Getters ===
|
||||
|
||||
|
||||
Reference in New Issue
Block a user