Files
jdd_opendingux/source/game/ui/console.cpp

271 lines
8.4 KiB
C++

#include "game/ui/console.hpp"
#include <SDL3/SDL.h>
#include <cctype> // Para toupper
#include <functional> // Para function
#include <string> // Para string
#include <vector> // Para vector
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/sprite/sprite.hpp" // Para Sprite
#include "core/rendering/surface.hpp" // Para Surface
#include "core/rendering/text.hpp" // Para Text
#include "core/resources/resource_cache.hpp" // Para Resource
#include "game/options.hpp" // Para Options
// ── Sistema de comandos ────────────────────────────────────────────────────────
struct ConsoleCommand {
std::string_view keyword;
std::function<std::string(const std::vector<std::string>& args)> execute;
};
// Convierte la entrada a uppercase y la divide en tokens por espacios
static auto parseTokens(const std::string& input) -> std::vector<std::string> {
std::vector<std::string> tokens;
std::string token;
for (unsigned char c : input) {
if (c == ' ') {
if (!token.empty()) {
tokens.push_back(token);
token.clear();
}
} else {
token += static_cast<char>(std::toupper(c));
}
}
if (!token.empty()) {
tokens.push_back(token);
}
return tokens;
}
// Tabla de comandos disponibles
static const std::vector<ConsoleCommand> COMMANDS = {
{"SS", [](const std::vector<std::string>& args) -> std::string {
if (args.empty()) {
Screen::get()->toggleSupersampling();
return std::string("Supersampling ") + (Options::video.supersampling ? "ON" : "OFF");
}
if (args[0] == "ON") {
if (Options::video.supersampling) { return "Supersampling already ON"; }
Screen::get()->toggleSupersampling();
return "Supersampling ON";
}
if (args[0] == "OFF") {
if (!Options::video.supersampling) { return "Supersampling already OFF"; }
Screen::get()->toggleSupersampling();
return "Supersampling OFF";
}
return "Usage: SS [ON|OFF]";
}},
{"HELP", [](const std::vector<std::string>&) -> std::string {
return "Commands: SS [ON|OFF]";
}},
{"?", [](const std::vector<std::string>&) -> std::string {
return "Commands: SS [ON|OFF]";
}},
};
// ── Singleton ─────────────────────────────────────────────────────────────────
// [SINGLETON]
Console* Console::console = nullptr;
// [SINGLETON]
void Console::init(const std::string& font_name) {
Console::console = new Console(font_name);
}
// [SINGLETON]
void Console::destroy() {
delete Console::console;
Console::console = nullptr;
}
// [SINGLETON]
auto Console::get() -> Console* {
return Console::console;
}
// Constructor
Console::Console(const std::string& font_name)
: text_(Resource::Cache::get()->getText(font_name)) {
const int TEXT_SIZE = 6;
const int PADDING_IN_V = TEXT_SIZE / 2;
height_ = static_cast<float>((TEXT_SIZE * 2) + (PADDING_IN_V * 2));
y_ = -height_;
buildSurface();
}
// Crea la Surface con el aspecto visual de la consola
void Console::buildSurface() {
const float WIDTH = Options::game.width;
surface_ = std::make_shared<Surface>(WIDTH, height_);
// Posición inicial (fuera de pantalla por arriba)
SDL_FRect sprite_rect = {.x = 0, .y = y_, .w = WIDTH, .h = height_};
sprite_ = std::make_shared<Sprite>(surface_, sprite_rect);
// Dibujo inicial del texto
redrawText();
}
// Redibuja el texto dinámico sobre la surface (fondo + borde + líneas)
void Console::redrawText() {
const float WIDTH = Options::game.width;
const int TEXT_SIZE = 6;
const int PADDING_IN_H = TEXT_SIZE;
const int PADDING_IN_V = TEXT_SIZE / 2;
auto previous_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(surface_);
// Fondo y borde
surface_->clear(BG_COLOR);
SDL_FRect rect = {.x = 0, .y = 0, .w = WIDTH, .h = height_};
surface_->drawRectBorder(&rect, BORDER_COLOR);
// Línea 1: mensajes
text_->writeColored(PADDING_IN_H, PADDING_IN_V, msg_line_, MSG_COLOR);
// Línea 2: prompt + input + cursor
const bool SHOW_CURSOR = cursor_visible_ && (static_cast<int>(input_line_.size()) < MAX_INPUT_CHARS);
const std::string INPUT_STR = "> " + input_line_ + (SHOW_CURSOR ? "_" : "");
text_->writeColored(PADDING_IN_H, PADDING_IN_V + TEXT_SIZE, INPUT_STR, BORDER_COLOR);
Screen::get()->setRendererSurface(previous_renderer);
}
// Actualiza la animación de la consola
void Console::update(float delta_time) {
if (status_ == Status::HIDDEN) {
return;
}
// Parpadeo del cursor (solo cuando activa)
if (status_ == Status::ACTIVE) {
cursor_timer_ += delta_time;
const float THRESHOLD = cursor_visible_ ? CURSOR_ON_TIME : CURSOR_OFF_TIME;
if (cursor_timer_ >= THRESHOLD) {
cursor_timer_ = 0.0F;
cursor_visible_ = !cursor_visible_;
}
}
// Redibujar texto cada frame
redrawText();
switch (status_) {
case Status::RISING: {
y_ += SLIDE_SPEED * delta_time;
if (y_ >= 0.0F) {
y_ = 0.0F;
status_ = Status::ACTIVE;
}
break;
}
case Status::VANISHING: {
y_ -= SLIDE_SPEED * delta_time;
if (y_ <= -height_) {
y_ = -height_;
status_ = Status::HIDDEN;
}
break;
}
default:
break;
}
SDL_FRect rect = {.x = 0, .y = y_, .w = Options::game.width, .h = height_};
sprite_->setPosition(rect);
}
// Renderiza la consola
void Console::render() {
if (status_ == Status::HIDDEN) {
return;
}
sprite_->render();
}
// Activa o desactiva la consola
void Console::toggle() {
switch (status_) {
case Status::HIDDEN:
status_ = Status::RISING;
input_line_.clear();
cursor_timer_ = 0.0F;
cursor_visible_ = true;
SDL_StartTextInput(SDL_GetKeyboardFocus());
break;
case Status::ACTIVE:
status_ = Status::VANISHING;
SDL_StopTextInput(SDL_GetKeyboardFocus());
break;
default:
// Durante RISING o VANISHING no se hace nada
break;
}
}
// Procesa el evento SDL: entrada de texto, Backspace, Enter
void Console::handleEvent(const SDL_Event& event) {
if (status_ != Status::ACTIVE) { return; }
if (event.type == SDL_EVENT_TEXT_INPUT) {
if (static_cast<int>(input_line_.size()) < MAX_INPUT_CHARS) {
input_line_ += event.text.text;
}
return;
}
if (event.type == SDL_EVENT_KEY_DOWN) {
if (event.key.scancode == SDL_SCANCODE_BACKSPACE && !input_line_.empty()) {
input_line_.pop_back();
} else if (event.key.scancode == SDL_SCANCODE_RETURN ||
event.key.scancode == SDL_SCANCODE_KP_ENTER) {
processCommand();
}
}
}
// Ejecuta el comando introducido y reinicia la línea de input
void Console::processCommand() {
if (!input_line_.empty()) {
const auto TOKENS = parseTokens(input_line_);
if (!TOKENS.empty()) {
const std::string& cmd = TOKENS[0];
const std::vector<std::string> ARGS(TOKENS.begin() + 1, TOKENS.end());
bool found = false;
for (const auto& command : COMMANDS) {
if (command.keyword == cmd) {
msg_line_ = command.execute(ARGS);
found = true;
break;
}
}
if (!found) {
msg_line_ = "Unknown: " + cmd;
}
}
}
input_line_.clear();
cursor_timer_ = 0.0F;
cursor_visible_ = true;
}
// Indica si la consola está activa (visible o en animación)
auto Console::isActive() -> bool {
return status_ != Status::HIDDEN;
}
// Devuelve los píxeles visibles de la consola (sincronizado con la animación)
auto Console::getVisibleHeight() -> int {
if (status_ == Status::HIDDEN) { return 0; }
return static_cast<int>(y_ + height_);
}