#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" #ifndef NO_SHADERS #include "jail_shader.h" // para init, render #endif // [SINGLETON] Hay que definir las variables estáticas, desde el .h sólo la hemos declarado Screen *Screen::screen_ = nullptr; // [SINGLETON] Crearemos el objeto screen 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 screen con esta función estática void Screen::destroy() { delete Screen::screen_; } // [SINGLETON] Con este método obtenemos el objeto screen 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)), shader_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_); SDL_DestroyTexture(shader_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_; // Actualiza y dibuja el efecto de flash en la pantalla renderFlash(); // Atenua la pantalla renderAttenuate(); // Muestra la ayuda por pantalla OnScreenHelp::get()->render(); // Muestra información por pantalla renderInfo(); // Muestra las notificaciones Notifier::get()->render(); #ifdef NO_SHADERS // Vuelve a dejar el renderizador en modo normal SDL_SetRenderTarget(renderer_, nullptr); // Borra el contenido previo // SDL_SetRenderDrawColor(renderer_, border_color_.r, border_color_.g, border_color_.b, 0xFF); // SDL_RenderClear(renderer_); // Copia la textura de juego en el renderizador en la posición adecuada if (shake_effect_.enabled) { SDL_RenderCopy(renderer_, game_canvas_, nullptr, nullptr); } SDL_RenderCopy(renderer_, game_canvas_, &src_rect_, &dst_rect_); // Muestra por pantalla el renderizador SDL_RenderPresent(renderer_); #else if (options.video.shaders) { // Dibuja sobre la textura de shaders SDL_SetRenderTarget(renderer_, shader_canvas_); // Copia la textura de juego en el renderizador en la posición adecuada if (shake_effect_.enabled) { // Esta copia es para evitar que se vea negro por los laterales SDL_RenderCopy(renderer_, game_canvas_, nullptr, nullptr); } SDL_RenderCopy(renderer_, game_canvas_, &src_rect_, &dst_rect_); SDL_SetRenderTarget(renderer_, nullptr); // Muestra por pantalla el renderizador shader::render(); } else { // Vuelve a dejar el renderizador en modo normal SDL_SetRenderTarget(renderer_, nullptr); // Borra el render SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF); SDL_RenderClear(renderer_); // Copia la textura de juego en el renderizador en la posición adecuada if (shake_effect_.enabled) { // Esta copia es para evitar que se vea negro por los laterales SDL_RenderCopy(renderer_, game_canvas_, nullptr, nullptr); } SDL_RenderCopy(renderer_, game_canvas_, &src_rect_, &dst_rect_); // Muestra por pantalla el renderizador SDL_RenderPresent(renderer_); } #endif } // 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) { #ifndef NO_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_, shader_canvas_, source.c_str()); #endif } } // 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); } } // 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; } // Obtiene el puntero al renderizador SDL_Renderer *Screen::getRenderer() { return renderer_; } // 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); const std::string atten = attenuate_effect_ ? "ATTEN YES" : "ATTEN NO"; dbg_print(0, 16, atten.c_str(), 255, 0, 0); } } // 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; }