#include "screen.h" #include // Para SDL_DISABLE, SDL_ENABLE #include // Para SDL_ShowCursor #include // Para SDL_PIXELFORMAT_RGBA8888 #include // Para SDL_GetTicks #include // Para clamp, max, min #include // Para basic_ifstream, ifstream #include // Para istreambuf_iterator, operator== #include // Para allocator, operator+, char_traits, to_s... #include // Para vector #include "asset.h" // Para Asset #include "dbgtxt.h" // Para dbg_print #include "global_inputs.h" // Para service_pressed_counter #include "notifier.h" // Para Notifier #include "on_screen_help.h" // Para OnScreenHelp #include "options.h" // Para Options, OptionsVideo, options, Options... #include "mouse.h" #include "jail_shader.h" // para init, render // [SINGLETON] Screen *Screen::screen_ = nullptr; // [SINGLETON] Crearemos el objeto con esta función estática void Screen::init(SDL_Window *window, SDL_Renderer *renderer) { Screen::screen_ = new Screen(window, renderer); } // [SINGLETON] Destruiremos el objeto con esta función estática void Screen::destroy() { delete Screen::screen_; } // [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él Screen *Screen::get() { return Screen::screen_; } // Constructor Screen::Screen(SDL_Window *window, SDL_Renderer *renderer) : window_(window), renderer_(renderer), game_canvas_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)), src_rect_({0, 0, param.game.width, param.game.height}), dst_rect_({0, 0, param.game.width, param.game.height}) { // Inicializa variables SDL_DisplayMode DM; SDL_GetCurrentDisplayMode(0, &DM); info_resolution_ = std::to_string(DM.w) + " X " + std::to_string(DM.h) + " AT " + std::to_string(DM.refresh_rate) + " HZ"; // Establece el modo de video setVideoMode(options.video.mode); // Muestra la ventana SDL_ShowWindow(window_); } // Destructor Screen::~Screen() { SDL_DestroyTexture(game_canvas_); } // Limpia la pantalla void Screen::clean(Color color) { SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 0xFF); SDL_RenderClear(renderer_); } // Prepara para empezar a dibujar en la textura de juego void Screen::start() { SDL_SetRenderTarget(renderer_, game_canvas_); } // Vuelca el contenido del renderizador en pantalla void Screen::render() { // Actualiza el contador de FPS ++fps_counter_; // Dibuja efectos y elementos sobre el game_canvas_ renderFlash(); renderAttenuate(); renderShake(); OnScreenHelp::get()->render(); renderInfo(); Notifier::get()->render(); // Renderiza el contenido del game_canvas_ renderScreen(); } // Renderiza el contenido del game_canvas_ void Screen::renderScreen() { // Restablece el objetivo de renderizado al buffer de pantalla predeterminado SDL_SetRenderTarget(renderer_, nullptr); clean(); if (options.video.shaders) { // Aplica shaders y renderiza el contenido shader::render(); } else { // Actualiza la pantalla con el contenido del buffer de renderizado SDL_RenderCopy(renderer_, game_canvas_, nullptr, nullptr); SDL_RenderPresent(renderer_); } } // Establece el modo de video void Screen::setVideoMode(ScreenVideoMode videoMode) { #ifdef ARCADE options.video.mode = ScreenVideoMode::WINDOW; #else options.video.mode = videoMode; #endif switch (options.video.mode) { case ScreenVideoMode::WINDOW: { // Cambia a modo de ventana SDL_SetWindowFullscreen(window_, 0); #ifdef ARCADE // Oculta el puntero SDL_ShowCursor(SDL_DISABLE); #else // Muestra el puntero SDL_ShowCursor(SDL_ENABLE); #endif // Modifica el tamaño de la ventana SDL_Point pos = getNewPosition(); SDL_SetWindowSize(window_, param.game.width * options.video.window.size, param.game.height * options.video.window.size); SDL_SetWindowPosition(window_, pos.x, pos.y); break; } // Si está activo el modo de pantalla completa añade el borde case ScreenVideoMode::FULLSCREEN: { // Aplica el modo de video SDL_SetWindowFullscreen(window_, SDL_WINDOW_FULLSCREEN_DESKTOP); // Oculta el puntero SDL_ShowCursor(SDL_DISABLE); break; } default: break; } // Reinicia los shaders if (options.video.shaders) { const std::string glsl_file = param.game.game_area.rect.h == 256 ? "crtpi_256.glsl" : "crtpi_240.glsl"; std::ifstream f(Asset::get()->get(glsl_file).c_str()); std::string source((std::istreambuf_iterator(f)), std::istreambuf_iterator()); shader::init(window_, game_canvas_, source.c_str()); } } // Camibia entre pantalla completa y ventana void Screen::toggleVideoMode() { options.video.mode = options.video.mode == ScreenVideoMode::WINDOW ? ScreenVideoMode::FULLSCREEN : ScreenVideoMode::WINDOW; setVideoMode(options.video.mode); } // Cambia el tamaño de la ventana void Screen::setWindowSize(int size) { options.video.window.size = size; setVideoMode(ScreenVideoMode::WINDOW); } // Reduce el tamaño de la ventana void Screen::decWindowSize() { --options.video.window.size; options.video.window.size = std::max(options.video.window.size, 1); setVideoMode(ScreenVideoMode::WINDOW); } // Aumenta el tamaño de la ventana void Screen::incWindowSize() { ++options.video.window.size; options.video.window.size = std::min(options.video.window.size, options.video.window.max_size); setVideoMode(ScreenVideoMode::WINDOW); } // Cambia el color del borde void Screen::setBorderColor(Color color) { border_color_ = color; } // Cambia el tipo de mezcla void Screen::setBlendMode(SDL_BlendMode blendMode) { SDL_SetRenderDrawBlendMode(renderer_, blendMode); } // Actualiza la lógica de la clase void Screen::update() { updateShakeEffect(); updateFlash(); Notifier::get()->update(); updateFPS(); OnScreenHelp::get()->update(); Mouse::updateCursorVisibility(); } // Agita la pantalla void Screen::shake() { // Si no hay un shake effect activo, se guarda una copia de los valores actuales antes de modificarlos if (!shake_effect_.enabled) { shake_effect_.enabled = true; shake_effect_.originalPos = src_rect_.x; shake_effect_.originalWidth = src_rect_.w; src_rect_.w -= shake_effect_.desp; dst_rect_.w = src_rect_.w; } // Si ya hay un shake effect en marcha no se pilla el origen, solo se renuevan los contadores shake_effect_.remaining = shake_effect_.lenght; shake_effect_.counter = shake_effect_.delay; } // Actualiza la logica para agitar la pantalla void Screen::updateShakeEffect() { if (shake_effect_.enabled) { if (shake_effect_.counter > 0) { shake_effect_.counter--; } else { shake_effect_.counter = shake_effect_.delay; const auto srcdesp = shake_effect_.remaining % 2 == 0 ? 0 : shake_effect_.desp; const auto dstdesp = shake_effect_.remaining % 2 == 1 ? 0 : shake_effect_.desp; src_rect_.x = shake_effect_.originalPos + srcdesp; dst_rect_.x = shake_effect_.originalPos + dstdesp; shake_effect_.remaining--; shake_effect_.enabled = shake_effect_.remaining == -1 ? false : true; if (!shake_effect_.enabled) { src_rect_.x = shake_effect_.originalPos; src_rect_.w = shake_effect_.originalWidth; dst_rect_ = src_rect_; } } } } // Pone la pantalla de color void Screen::flash(Color color, int lenght, int delay) { flash_effect_ = FlashEffect(true, lenght, delay, color); } // Actualiza y dibuja el efecto de flash en la pantalla void Screen::renderFlash() { if (flash_effect_.isRendarable()) { SDL_SetRenderDrawColor(renderer_, flash_effect_.color.r, flash_effect_.color.g, flash_effect_.color.b, 0xFF); SDL_RenderClear(renderer_); } } // Actualiza el efecto de flash void Screen::updateFlash() { flash_effect_.update(); } // Atenua la pantalla void Screen::renderAttenuate() { if (attenuate_effect_) { SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 64); SDL_RenderFillRect(renderer_, nullptr); } } // Aplica el efecto de agitar la pantalla void Screen::renderShake() { if (shake_effect_.enabled) { // Guarda el renderizador actual para dejarlo despues como estaba auto current_target = SDL_GetRenderTarget(renderer_); // Crea una textura temporal auto temp_texture = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height); // Vuelca game_canvas_ a la textura temporal SDL_SetRenderTarget(renderer_, temp_texture); SDL_RenderCopy(renderer_, game_canvas_, nullptr, nullptr); // Vuelca textura temporal a game_canvas_ SDL_SetRenderTarget(renderer_, game_canvas_); SDL_RenderCopy(renderer_, temp_texture, &src_rect_, &dst_rect_); // Elimina la textura temporal SDL_DestroyTexture(temp_texture); // Restaura el renderizador de destino original SDL_SetRenderTarget(renderer_, current_target); } } // Activa / desactiva los shaders void Screen::toggleShaders() { options.video.shaders = !options.video.shaders; setVideoMode(options.video.mode); const std::string value = options.video.shaders ? "on" : "off"; Notifier::get()->showText({"Shaders " + value}); } // Activa / desactiva la información de debug void Screen::toggleDebugInfo() { show_info_ = !show_info_; } // Atenua la pantalla void Screen::attenuate(bool value) { attenuate_effect_ = value; } // Calcula los frames por segundo void Screen::updateFPS() { if (SDL_GetTicks() - fps_ticks_ > 1000) { fps_ticks_ = SDL_GetTicks(); fps_ = fps_counter_; fps_counter_ = 0; } } // Muestra información por pantalla void Screen::renderInfo() { if (show_info_) { // FPS const std::string fpstext = std::to_string(fps_) + " FPS"; dbg_print(param.game.width - fpstext.length() * 8, 0, fpstext.c_str(), 255, 255, 0); // Resolution dbg_print(0, 0, info_resolution_.c_str(), 255, 255, 0); // Contador de service_pressed_counter if (const int counter = globalInputs::service_pressed_counter; counter > 0) { dbg_print(0, 8, std::to_string(counter).c_str(), 255, 0, 255); } } } // Calcula la nueva posición de la ventana a partir de la antigua al cambiarla de tamaño SDL_Point Screen::getNewPosition() { // Obtiene la posición actual de la ventana SDL_Point current_position; SDL_GetWindowPosition(window_, ¤t_position.x, ¤t_position.y); // Obtiene las dimensiones actuales de la ventana int current_width, current_height; SDL_GetWindowSize(window_, ¤t_width, ¤t_height); // Obtiene las dimesiones que tendrá la ventana const int new_width = param.game.width * options.video.window.size; const int new_height = param.game.height * options.video.window.size; // Obtiene el centro de la ventana actual SDL_Point center; center.x = current_position.x + current_width / 2; center.y = current_position.y + current_height / 2; // Calcula la nueva posición a partir del centro y las nuevas diemsiones SDL_Point new_pos; new_pos.x = center.x - new_width / 2; new_pos.y = center.y - new_height / 2; // Obtiene las dimensiones del escritorio SDL_DisplayMode DM; SDL_GetCurrentDisplayMode(0, &DM); // Evita que la ventana quede fuera del escritorio new_pos.x = std::clamp(new_pos.x, 30, DM.w - new_width); new_pos.y = std::clamp(new_pos.y, 30, DM.h - new_height); return new_pos; }