diff --git a/source/game/ui/console.cpp b/source/game/ui/console.cpp index f47d8ba..aeeef25 100644 --- a/source/game/ui/console.cpp +++ b/source/game/ui/console.cpp @@ -193,84 +193,92 @@ void Console::redrawText() { Screen::get()->setRendererSurface(previous_renderer); } -// Actualiza la animación de la consola -void Console::update(float delta_time) { // NOLINT(readability-function-cognitive-complexity) - if (status_ == Status::HIDDEN) { +// Parpadeig del cursor (només quan ACTIVE) +void Console::updateCursorBlink(float delta_time) { + 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_; + } +} + +// Revelat lletra a lletra de msg_lines_ (només quan ACTIVE) +void Console::updateTypewriter(float delta_time) { + const int TOTAL_CHARS = std::accumulate(msg_lines_.begin(), msg_lines_.end(), 0, [](int acc, const auto& line) { return acc + static_cast(line.size()); }); + if (typewriter_chars_ >= TOTAL_CHARS) { return; } + typewriter_timer_ += delta_time; + while (typewriter_timer_ >= TYPEWRITER_CHAR_DELAY && typewriter_chars_ < TOTAL_CHARS) { + typewriter_timer_ -= TYPEWRITER_CHAR_DELAY; + ++typewriter_chars_; + } +} + +// Animació d'altura quan msg_lines_ canvia (només quan ACTIVE i height_ != target_height_) +void Console::updateResizeAnimation(float delta_time) { + if (anim_progress_ == 0.0F) { + // Iniciar animació de resize + anim_start_ = height_; + anim_end_ = target_height_; + } + anim_progress_ = std::min(anim_progress_ + (delta_time / ANIM_DURATION), 1.0F); + const float PREV_HEIGHT = height_; + height_ = anim_start_ + ((anim_end_ - anim_start_) * Easing::cubicInOut(anim_progress_)); + if (anim_progress_ >= 1.0F) { + height_ = target_height_; + anim_progress_ = 0.0F; + } + // Actualitzar el Notifier incrementalment amb el delta d'altura + if (Notifier::get() != nullptr) { + const int DELTA_PX = static_cast(height_) - static_cast(PREV_HEIGHT); + if (DELTA_PX > 0) { + Notifier::get()->addYOffset(DELTA_PX); + notifier_offset_applied_ += DELTA_PX; + } else if (DELTA_PX < 0) { + Notifier::get()->removeYOffset(-DELTA_PX); + notifier_offset_applied_ += DELTA_PX; + } + } + // Reconstruir la Surface al nou tamany (xicoteta: 256×~18-72px) + const float WIDTH = Options::game.width; + surface_ = std::make_shared(WIDTH, height_); + sprite_->setSurface(surface_); +} + +// Animació RISING/VANISHING (basada en temps amb easing) +void Console::updateOpenCloseAnimation(float delta_time) { + anim_progress_ = std::min(anim_progress_ + (delta_time / ANIM_DURATION), 1.0F); + y_ = anim_start_ + ((anim_end_ - anim_start_) * Easing::cubicInOut(anim_progress_)); + + if (anim_progress_ < 1.0F) { return; } + y_ = anim_end_; + anim_progress_ = 0.0F; + if (status_ == Status::RISING) { + status_ = Status::ACTIVE; return; } + status_ = Status::HIDDEN; + // Reset del missatge una vegada completament oculta + msg_lines_ = {std::string(CONSOLE_NAME) + " " + std::string(CONSOLE_VERSION)}; + target_height_ = calcTargetHeight(static_cast(msg_lines_.size())); +} + +// 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_; + updateCursorBlink(delta_time); + updateTypewriter(delta_time); + if (height_ != target_height_) { + updateResizeAnimation(delta_time); } } - // Efecto typewriter: revelar letras una a una (solo cuando ACTIVE) - if (status_ == Status::ACTIVE) { - const int TOTAL_CHARS = std::accumulate(msg_lines_.begin(), msg_lines_.end(), 0, [](int acc, const auto& line) { return acc + static_cast(line.size()); }); - if (typewriter_chars_ < TOTAL_CHARS) { - typewriter_timer_ += delta_time; - while (typewriter_timer_ >= TYPEWRITER_CHAR_DELAY && typewriter_chars_ < TOTAL_CHARS) { - typewriter_timer_ -= TYPEWRITER_CHAR_DELAY; - ++typewriter_chars_; - } - } - } - - // Animación de altura (resize cuando msg_lines_ cambia); solo en ACTIVE - if (status_ == Status::ACTIVE && height_ != target_height_) { - if (anim_progress_ == 0.0F) { - // Iniciar animación de resize - anim_start_ = height_; - anim_end_ = target_height_; - } - anim_progress_ = std::min(anim_progress_ + (delta_time / ANIM_DURATION), 1.0F); - const float PREV_HEIGHT = height_; - height_ = anim_start_ + ((anim_end_ - anim_start_) * Easing::cubicInOut(anim_progress_)); - if (anim_progress_ >= 1.0F) { - height_ = target_height_; - anim_progress_ = 0.0F; - } - // Actualizar el Notifier incrementalmente con el delta de altura - if (Notifier::get() != nullptr) { - const int DELTA_PX = static_cast(height_) - static_cast(PREV_HEIGHT); - if (DELTA_PX > 0) { - Notifier::get()->addYOffset(DELTA_PX); - notifier_offset_applied_ += DELTA_PX; - } else if (DELTA_PX < 0) { - Notifier::get()->removeYOffset(-DELTA_PX); - notifier_offset_applied_ += DELTA_PX; - } - } - // Reconstruir la Surface al nuevo tamaño (pequeña: 256×~18-72px) - const float WIDTH = Options::game.width; - surface_ = std::make_shared(WIDTH, height_); - sprite_->setSurface(surface_); - } - - // Redibujar texto cada frame redrawText(); - // Animación de apertura/cierre (basada en tiempo) if (status_ == Status::RISING || status_ == Status::VANISHING) { - anim_progress_ = std::min(anim_progress_ + (delta_time / ANIM_DURATION), 1.0F); - y_ = anim_start_ + ((anim_end_ - anim_start_) * Easing::cubicInOut(anim_progress_)); - - if (anim_progress_ >= 1.0F) { - y_ = anim_end_; - anim_progress_ = 0.0F; - if (status_ == Status::RISING) { - status_ = Status::ACTIVE; - } else { - status_ = Status::HIDDEN; - msg_lines_ = {std::string(CONSOLE_NAME) + " " + std::string(CONSOLE_VERSION)}; - target_height_ = calcTargetHeight(static_cast(msg_lines_.size())); - } - } + updateOpenCloseAnimation(delta_time); } SDL_FRect rect = {.x = 0, .y = y_, .w = Options::game.width, .h = height_}; @@ -334,94 +342,103 @@ void Console::toggle() { } } +// Insereix caràcters imprimibles a input_line_ (filtra control i la toggle key activa) +void Console::handleTextInput(const SDL_Event& event) { + if (static_cast(event.text.text[0]) < 32) { return; } + // Ignorar text si la tecla toggle està pulsada (evita escriure el seu caràcter) + if (KeyConfig::get() != nullptr) { + SDL_Keycode toggle_key = KeyConfig::get()->key("GLOBAL", "console"); + SDL_Scancode toggle_sc = SDL_GetScancodeFromKey(toggle_key, nullptr); + if (toggle_sc != SDL_SCANCODE_UNKNOWN) { + const bool* ks = SDL_GetKeyboardState(nullptr); + if (ks[toggle_sc]) { return; } + } + } + if (static_cast(input_line_.size()) < MAX_LINE_CHARS) { + input_line_ += event.text.text; + } + tab_matches_.clear(); +} + +// Navega enrere a l'historial (cap a comandes més antigues) +void Console::handleHistoryUp() { + tab_matches_.clear(); + if (history_index_ >= static_cast(history_.size()) - 1) { return; } + if (history_index_ == -1) { saved_input_ = input_line_; } + ++history_index_; + input_line_ = history_[static_cast(history_index_)]; +} + +// Navega cap al present a l'historial (cap a comandes més recents) +void Console::handleHistoryDown() { + tab_matches_.clear(); + if (history_index_ < 0) { return; } + --history_index_; + input_line_ = (history_index_ == -1) ? saved_input_ : history_[static_cast(history_index_)]; +} + +// Autocompletat per TAB: calcula candidats si cal i cicla +void Console::handleTab() { + if (tab_matches_.empty()) { + std::string upper; + for (const unsigned char C : input_line_) { upper += static_cast(std::toupper(C)); } + + const size_t SPACE_POS = upper.rfind(' '); + if (SPACE_POS == std::string::npos) { + // Mode comanda: cicla keywords visibles que comencen pel prefix + const auto KEYWORDS = registry_.getVisibleKeywords(); + std::ranges::copy_if(KEYWORDS, std::back_inserter(tab_matches_), [&upper](const auto& kw) { return upper.empty() || kw.starts_with(upper); }); + } else { + const std::string BASE_CMD = upper.substr(0, SPACE_POS); + const std::string SUB_PREFIX = upper.substr(SPACE_POS + 1); + const auto OPTS = registry_.getCompletions(BASE_CMD); + for (const auto& arg : OPTS) { + if (!SUB_PREFIX.empty() && !std::string_view{arg}.starts_with(SUB_PREFIX)) { continue; } + std::string match = BASE_CMD; + match += ' '; + match += arg; + tab_matches_.emplace_back(std::move(match)); + } + } + tab_index_ = -1; + } + if (tab_matches_.empty()) { return; } + tab_index_ = (tab_index_ + 1) % static_cast(tab_matches_.size()); + std::string result = tab_matches_[static_cast(tab_index_)]; + std::ranges::transform(result, result.begin(), [](char c) { return static_cast(std::tolower(static_cast(c))); }); + input_line_ = result; +} + // Procesa el evento SDL: entrada de texto, Backspace, Enter -void Console::handleEvent(const SDL_Event& event) { // NOLINT(readability-function-cognitive-complexity) +void Console::handleEvent(const SDL_Event& event) { if (status_ != Status::ACTIVE) { return; } if (event.type == SDL_EVENT_TEXT_INPUT) { - // Filtrar caracteres de control (tab, newline, etc.) - if (static_cast(event.text.text[0]) < 32) { return; } - // Ignorar texto si la tecla toggle está pulsada (evita escribir su carácter) - if (KeyConfig::get() != nullptr) { - SDL_Keycode toggle_key = KeyConfig::get()->key("GLOBAL", "console"); - SDL_Scancode toggle_sc = SDL_GetScancodeFromKey(toggle_key, nullptr); - if (toggle_sc != SDL_SCANCODE_UNKNOWN) { - const bool* ks = SDL_GetKeyboardState(nullptr); - if (ks[toggle_sc]) { return; } - } - } - if (static_cast(input_line_.size()) < MAX_LINE_CHARS) { - input_line_ += event.text.text; - } - tab_matches_.clear(); + handleTextInput(event); return; } + if (event.type != SDL_EVENT_KEY_DOWN) { return; } - if (event.type == SDL_EVENT_KEY_DOWN) { - switch (event.key.scancode) { - case SDL_SCANCODE_BACKSPACE: - tab_matches_.clear(); - if (!input_line_.empty()) { input_line_.pop_back(); } - break; - case SDL_SCANCODE_RETURN: - case SDL_SCANCODE_KP_ENTER: - processCommand(); - break; - case SDL_SCANCODE_UP: - // Navegar hacia atrás en el historial - tab_matches_.clear(); - if (history_index_ < static_cast(history_.size()) - 1) { - if (history_index_ == -1) { saved_input_ = input_line_; } - ++history_index_; - input_line_ = history_[static_cast(history_index_)]; - } - break; - case SDL_SCANCODE_DOWN: - // Navegar hacia el presente en el historial - tab_matches_.clear(); - if (history_index_ >= 0) { - --history_index_; - input_line_ = (history_index_ == -1) - ? saved_input_ - : history_[static_cast(history_index_)]; - } - break; - case SDL_SCANCODE_TAB: { - if (tab_matches_.empty()) { - // Calcular el input actual en mayúsculas - std::string upper; - for (unsigned char c : input_line_) { upper += static_cast(std::toupper(c)); } - - const size_t SPACE_POS = upper.rfind(' '); - if (SPACE_POS == std::string::npos) { - // Modo comando: ciclar keywords visibles que empiecen por el prefijo - const auto VISIBLE = registry_.getVisibleKeywords(); - std::ranges::copy_if(VISIBLE, std::back_inserter(tab_matches_), [&](const auto& kw) { return upper.empty() || kw.starts_with(upper); }); - } else { - const std::string BASE_CMD = upper.substr(0, SPACE_POS); - const std::string SUB_PREFIX = upper.substr(SPACE_POS + 1); - const auto OPTS = registry_.getCompletions(BASE_CMD); - for (const auto& arg : OPTS) { - if (SUB_PREFIX.empty() || std::string_view{arg}.starts_with(SUB_PREFIX)) { - std::string match = BASE_CMD; - match += ' '; - match += arg; - tab_matches_.emplace_back(std::move(match)); - } - } - } - tab_index_ = -1; - } - if (tab_matches_.empty()) { break; } - tab_index_ = (tab_index_ + 1) % static_cast(tab_matches_.size()); - std::string result = tab_matches_[static_cast(tab_index_)]; - std::ranges::transform(result, result.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); - input_line_ = result; - break; - } - default: - break; - } + switch (event.key.scancode) { + case SDL_SCANCODE_BACKSPACE: + tab_matches_.clear(); + if (!input_line_.empty()) { input_line_.pop_back(); } + break; + case SDL_SCANCODE_RETURN: + case SDL_SCANCODE_KP_ENTER: + processCommand(); + break; + case SDL_SCANCODE_UP: + handleHistoryUp(); + break; + case SDL_SCANCODE_DOWN: + handleHistoryDown(); + break; + case SDL_SCANCODE_TAB: + handleTab(); + break; + default: + break; } } diff --git a/source/game/ui/console.hpp b/source/game/ui/console.hpp index 9a779d7..5f49202 100644 --- a/source/game/ui/console.hpp +++ b/source/game/ui/console.hpp @@ -79,6 +79,14 @@ class Console { void redrawText(); // Redibuja el texto dinámico (msg + input + cursor) void processCommand(); // Procesa el comando introducido por el usuario [[nodiscard]] auto wrapText(const std::string& text) const -> std::vector; // Word-wrap por ancho en píxeles + void updateCursorBlink(float delta_time); // Parpadeig del cursor (només quan ACTIVE) + void updateTypewriter(float delta_time); // Revelat lletra a lletra de msg_lines_ + void updateResizeAnimation(float delta_time); // Animació d'altura quan msg_lines_ canvia + void updateOpenCloseAnimation(float delta_time); // Animació RISING/VANISHING + void handleTextInput(const SDL_Event& event); // Insereix caràcters imprimibles a input_line_ + void handleHistoryUp(); // Navega enrere a l'historial + void handleHistoryDown(); // Navega cap al present a l'historial + void handleTab(); // Autocompletat per TAB: calcula candidats si cal i cicla // Objetos de renderizado std::shared_ptr text_;