feat: F7/F8 redimensionan campo lógico, F1/F2 muestran notificación de zoom

- F7/F8: nuevo setFieldScale() cambia resolución lógica en pasos del 10%
  (mín 50%, máx limitado por pantalla), reinicia escena como F4
- F1/F2: muestran notificación "Zoom X%" al cambiar escala de ventana
- Ventana física = lógico × zoom en todo momento; resizeWindowCentered()
  unifica el cálculo de posición leyendo el tamaño real con SDL_GetWindowSize
- PostFXUniforms::time renombrado a screen_height; scanlines usan la altura
  lógica actual en lugar del 720 hardcodeado — F1/F2 escalan las scanlines
  visualmente, F7/F8 las mantienen a 1 franja por píxel lógico
- Eliminados logs de debug de calculateMaxWindowScale y setWindowScale

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 22:35:40 +01:00
parent f71f7cd5ed
commit 1d2e9c5035
7 changed files with 135 additions and 79 deletions

View File

@@ -6,7 +6,7 @@ layout(set=3, binding=0) uniform PostFXUniforms {
float vignette_strength; float vignette_strength;
float chroma_strength; float chroma_strength;
float scanline_strength; float scanline_strength;
float time; float screen_height;
} u; } u;
void main() { void main() {
float ca = u.chroma_strength * 0.005; float ca = u.chroma_strength * 0.005;
@@ -15,7 +15,7 @@ void main() {
color.g = texture(scene, v_uv).g; color.g = texture(scene, v_uv).g;
color.b = texture(scene, v_uv - vec2( ca, 0.0)).b; color.b = texture(scene, v_uv - vec2( ca, 0.0)).b;
color.a = texture(scene, v_uv).a; 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); color.rgb *= mix(1.0, scan, u.scanline_strength);
vec2 d = v_uv - vec2(0.5, 0.5); vec2 d = v_uv - vec2(0.5, 0.5);
float vignette = 1.0 - dot(d, d) * u.vignette_strength; float vignette = 1.0 - dot(d, d) * u.vignette_strength;

View File

@@ -5,19 +5,23 @@
#include <vector> // for std::vector in DynamicThemeKeyframe/DynamicTheme #include <vector> // for std::vector in DynamicThemeKeyframe/DynamicTheme
// Configuración de ventana y pantalla // 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) // 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_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_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) constexpr int DEFAULT_WINDOW_ZOOM = 1; // Zoom inicial de ventana (1x = sin zoom)
// Configuración de zoom dinámico de ventana // Configuración de zoom dinámico de ventana (legacy, solo usado en initialize())
constexpr int WINDOW_ZOOM_MIN = 1; // Zoom mínimo (320x240) 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 (3200x2400) 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_DESKTOP_MARGIN = 10; // Margen mínimo con bordes del escritorio
constexpr int WINDOW_DECORATION_HEIGHT = 30; // Altura estimada de decoraciones del SO 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 // Configuración de física
constexpr float GRAVITY_FORCE = 0.2f; // Fuerza de gravedad (píxeles/frame²) constexpr float GRAVITY_FORCE = 0.2f; // Fuerza de gravedad (píxeles/frame²)

View File

@@ -84,8 +84,8 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
window_zoom = 1; window_zoom = 1;
} }
// Guardar zoom calculado ANTES de crear la ventana (para F1/F2/F3/F4) // Guardar escala inicial (siempre 1.0 salvo que CLI haya pedido zoom > 1)
current_window_zoom_ = window_zoom; current_window_scale_ = static_cast<float>(window_zoom);
// Calcular tamaño de ventana // Calcular tamaño de ventana
int window_width = logical_width * window_zoom; int window_width = logical_width * window_zoom;
@@ -427,7 +427,7 @@ void Engine::calculateDeltaTime() {
void Engine::update() { void Engine::update() {
// Accumulate time for PostFX uniforms // 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) // Actualizar visibilidad del cursor (auto-ocultar tras inactividad)
Mouse::updateCursorVisibility(); Mouse::updateCursorVisibility();
@@ -953,7 +953,9 @@ void Engine::toggleFullscreen() {
// Si acabamos de salir de fullscreen, restaurar tamaño de ventana // Si acabamos de salir de fullscreen, restaurar tamaño de ventana
if (!fullscreen_enabled_) { 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); SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
} }
@@ -1018,10 +1020,13 @@ void Engine::toggleRealFullscreen() {
// Volver a resolución base (configurada por CLI o default) // Volver a resolución base (configurada por CLI o default)
current_screen_width_ = base_screen_width_; current_screen_width_ = base_screen_width_;
current_screen_height_ = base_screen_height_; 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_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); SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
// Recrear render target offscreen con resolución base // Recrear render target offscreen con resolución base
@@ -1148,85 +1153,112 @@ void Engine::addSpriteToBatch(float x, float y, float w, float h, int r, int g,
static_cast<float>(current_screen_height_)); static_cast<float>(current_screen_height_));
} }
// Sistema de zoom dinámico // Sistema de escala de ventana (pasos del 10%)
int Engine::calculateMaxWindowZoom() const { float Engine::calculateMaxWindowScale() const {
// Obtener información del display usando el método de Coffee Crisis SDL_Rect bounds;
int num_displays = 0; if (!SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &bounds)) { // bool: false = error
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays); return WINDOW_SCALE_MIN; // Fallback solo si falla de verdad
if (displays == nullptr || num_displays == 0) {
return WINDOW_ZOOM_MIN; // Fallback si no se puede obtener
} }
float max_by_w = static_cast<float>(bounds.w - 2 * WINDOW_DESKTOP_MARGIN) / base_screen_width_;
// Obtener el modo de display actual float max_by_h = static_cast<float>(bounds.h - 2 * WINDOW_DESKTOP_MARGIN - WINDOW_DECORATION_HEIGHT) / base_screen_height_;
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]); float result = std::max(WINDOW_SCALE_MIN, std::min(max_by_w, max_by_h));
if (dm == nullptr) { return result;
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));
} }
void Engine::setWindowZoom(int new_zoom) { // Redimensiona la ventana física manteniéndo su centro, con clamping a pantalla.
// Validar zoom static void resizeWindowCentered(SDL_Window* window, int new_w, int new_h) {
int max_zoom = calculateMaxWindowZoom(); int cur_x, cur_y, cur_w, cur_h;
new_zoom = std::max(WINDOW_ZOOM_MIN, std::min(new_zoom, max_zoom)); SDL_GetWindowPosition(window, &cur_x, &cur_y);
SDL_GetWindowSize(window, &cur_w, &cur_h);
if (new_zoom == current_window_zoom_) { int new_x = cur_x + (cur_w - new_w) / 2;
return; // No hay cambio 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 SDL_SetWindowSize(window, new_w, new_h);
int current_x, current_y; SDL_SetWindowPosition(window, new_x, new_y);
SDL_GetWindowPosition(window_, &current_x, &current_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;
// Calcular nuevo tamaño void Engine::setWindowScale(float new_scale) {
int new_width = base_screen_width_ * new_zoom; float max_scale = calculateMaxWindowScale();
int new_height = base_screen_height_ * new_zoom; 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) if (new_scale == current_window_scale_) return;
int new_x = current_center_x - new_width / 2;
int new_y = current_center_y - new_height / 2;
// Obtener límites del escritorio para no salirse int new_width = static_cast<int>(std::round(current_screen_width_ * new_scale));
SDL_Rect display_bounds; int new_height = static_cast<int>(std::round(current_screen_height_ * new_scale));
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;
// Limitar posición resizeWindowCentered(window_, new_width, new_height);
new_x = std::max(min_x, std::min(new_x, max_x)); current_window_scale_ = new_scale;
new_y = std::max(min_y, std::min(new_y, max_y));
}
// 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(); updatePhysicalWindowSize();
} }
void Engine::zoomIn() { 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() { 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() { void Engine::updatePhysicalWindowSize() {
if (real_fullscreen_enabled_) { if (real_fullscreen_enabled_) {
// En fullscreen real (F4), usar resolución del display // En fullscreen real (F4), usar resolución del display
@@ -1254,6 +1286,7 @@ void Engine::updatePhysicalWindowSize() {
// Notificar a UIManager del cambio de tamaño (delegado) // Notificar a UIManager del cambio de tamaño (delegado)
ui_manager_->updatePhysicalWindowSize(physical_window_width_, physical_window_height_); ui_manager_->updatePhysicalWindowSize(physical_window_width_, physical_window_height_);
} }
// ============================================================================ // ============================================================================

View File

@@ -76,6 +76,11 @@ class Engine {
void toggleRealFullscreen(); void toggleRealFullscreen();
void toggleIntegerScaling(); void toggleIntegerScaling();
// Campo de juego (tamaño lógico + físico)
void fieldSizeUp();
void fieldSizeDown();
void setFieldScale(float new_scale);
// PostFX presets // PostFX presets
void handlePostFXCycle(); void handlePostFXCycle();
void handlePostFXToggle(); void handlePostFXToggle();
@@ -187,8 +192,11 @@ class Engine {
float postfx_override_vignette_ = -1.f; // -1 = sin override float postfx_override_vignette_ = -1.f; // -1 = sin override
float postfx_override_chroma_ = -1.f; float postfx_override_chroma_ = -1.f;
// Sistema de zoom dinámico // Sistema de escala de ventana
int current_window_zoom_ = DEFAULT_WINDOW_ZOOM; 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 // V-Sync y fullscreen
bool vsync_enabled_ = true; bool vsync_enabled_ = true;
@@ -242,9 +250,9 @@ class Engine {
// Sistema de cambio de sprites dinámico // Sistema de cambio de sprites dinámico
void switchTextureInternal(bool show_notification); void switchTextureInternal(bool show_notification);
// Sistema de zoom dinámico // Sistema de escala de ventana
int calculateMaxWindowZoom() const; float calculateMaxWindowScale() const;
void setWindowZoom(int new_zoom); void setWindowScale(float new_scale);
void zoomIn(); void zoomIn();
void zoomOut(); void zoomOut();
void updatePhysicalWindowSize(); void updatePhysicalWindowSize();

View File

@@ -117,7 +117,7 @@ struct PostFXUniforms {
float vignette_strength; float vignette_strength;
float chroma_strength; float chroma_strength;
float scanline_strength; float scanline_strength;
float time; float screen_height;
}; };
fragment float4 postfx_fs(PostVOut in [[stage_in]], 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; color.a = scene.sample(samp, in.uv ).a;
// Scanlines: horizontal sine-wave at ~360 lines (one dark band per 2 px at 720p) // 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); color.rgb *= mix(1.0, scan, u.scanline_strength);
// Vignette: radial edge darkening // Vignette: radial edge darkening

View File

@@ -11,7 +11,7 @@ struct PostFXUniforms {
float vignette_strength; // 0 = none, 0.8 = default subtle float vignette_strength; // 0 = none, 0.8 = default subtle
float chroma_strength; // 0 = off, 0.2 = default chromatic aberration float chroma_strength; // 0 = off, 0.2 = default chromatic aberration
float scanline_strength; // 0 = off, 1 = full scanlines 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
}; };
// ============================================================================ // ============================================================================

View File

@@ -264,6 +264,17 @@ bool InputHandler::processEvents(Engine& engine) {
engine.toggleIntegerScaling(); engine.toggleIntegerScaling();
break; 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) // Toggle Modo DEMO COMPLETO (auto-play) o Pausar tema dinámico (Shift+D)
case SDLK_D: case SDLK_D:
// Shift+D = Pausar tema dinámico // Shift+D = Pausar tema dinámico