refactor console: extreu updateCursorBlink/Typewriter/Resize/OpenClose i handleTextInput/HistoryUp/Down/Tab
This commit is contained in:
+166
-149
@@ -193,84 +193,92 @@ void Console::redrawText() {
|
|||||||
Screen::get()->setRendererSurface(previous_renderer);
|
Screen::get()->setRendererSurface(previous_renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualiza la animación de la consola
|
// Parpadeig del cursor (només quan ACTIVE)
|
||||||
void Console::update(float delta_time) { // NOLINT(readability-function-cognitive-complexity)
|
void Console::updateCursorBlink(float delta_time) {
|
||||||
if (status_ == Status::HIDDEN) {
|
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<int>(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<int>(height_) - static_cast<int>(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<Surface>(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;
|
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<int>(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) {
|
if (status_ == Status::ACTIVE) {
|
||||||
cursor_timer_ += delta_time;
|
updateCursorBlink(delta_time);
|
||||||
const float THRESHOLD = cursor_visible_ ? CURSOR_ON_TIME : CURSOR_OFF_TIME;
|
updateTypewriter(delta_time);
|
||||||
if (cursor_timer_ >= THRESHOLD) {
|
if (height_ != target_height_) {
|
||||||
cursor_timer_ = 0.0F;
|
updateResizeAnimation(delta_time);
|
||||||
cursor_visible_ = !cursor_visible_;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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<int>(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<int>(height_) - static_cast<int>(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<Surface>(WIDTH, height_);
|
|
||||||
sprite_->setSurface(surface_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redibujar texto cada frame
|
|
||||||
redrawText();
|
redrawText();
|
||||||
|
|
||||||
// Animación de apertura/cierre (basada en tiempo)
|
|
||||||
if (status_ == Status::RISING || status_ == Status::VANISHING) {
|
if (status_ == Status::RISING || status_ == Status::VANISHING) {
|
||||||
anim_progress_ = std::min(anim_progress_ + (delta_time / ANIM_DURATION), 1.0F);
|
updateOpenCloseAnimation(delta_time);
|
||||||
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<int>(msg_lines_.size()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_FRect rect = {.x = 0, .y = y_, .w = Options::game.width, .h = height_};
|
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<unsigned char>(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<int>(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<int>(history_.size()) - 1) { return; }
|
||||||
|
if (history_index_ == -1) { saved_input_ = input_line_; }
|
||||||
|
++history_index_;
|
||||||
|
input_line_ = history_[static_cast<size_t>(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<size_t>(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<char>(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<int>(tab_matches_.size());
|
||||||
|
std::string result = tab_matches_[static_cast<size_t>(tab_index_)];
|
||||||
|
std::ranges::transform(result, result.begin(), [](char c) { return static_cast<char>(std::tolower(static_cast<unsigned char>(c))); });
|
||||||
|
input_line_ = result;
|
||||||
|
}
|
||||||
|
|
||||||
// Procesa el evento SDL: entrada de texto, Backspace, Enter
|
// 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 (status_ != Status::ACTIVE) { return; }
|
||||||
|
|
||||||
if (event.type == SDL_EVENT_TEXT_INPUT) {
|
if (event.type == SDL_EVENT_TEXT_INPUT) {
|
||||||
// Filtrar caracteres de control (tab, newline, etc.)
|
handleTextInput(event);
|
||||||
if (static_cast<unsigned char>(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<int>(input_line_.size()) < MAX_LINE_CHARS) {
|
|
||||||
input_line_ += event.text.text;
|
|
||||||
}
|
|
||||||
tab_matches_.clear();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (event.type != SDL_EVENT_KEY_DOWN) { return; }
|
||||||
|
|
||||||
if (event.type == SDL_EVENT_KEY_DOWN) {
|
switch (event.key.scancode) {
|
||||||
switch (event.key.scancode) {
|
case SDL_SCANCODE_BACKSPACE:
|
||||||
case SDL_SCANCODE_BACKSPACE:
|
tab_matches_.clear();
|
||||||
tab_matches_.clear();
|
if (!input_line_.empty()) { input_line_.pop_back(); }
|
||||||
if (!input_line_.empty()) { input_line_.pop_back(); }
|
break;
|
||||||
break;
|
case SDL_SCANCODE_RETURN:
|
||||||
case SDL_SCANCODE_RETURN:
|
case SDL_SCANCODE_KP_ENTER:
|
||||||
case SDL_SCANCODE_KP_ENTER:
|
processCommand();
|
||||||
processCommand();
|
break;
|
||||||
break;
|
case SDL_SCANCODE_UP:
|
||||||
case SDL_SCANCODE_UP:
|
handleHistoryUp();
|
||||||
// Navegar hacia atrás en el historial
|
break;
|
||||||
tab_matches_.clear();
|
case SDL_SCANCODE_DOWN:
|
||||||
if (history_index_ < static_cast<int>(history_.size()) - 1) {
|
handleHistoryDown();
|
||||||
if (history_index_ == -1) { saved_input_ = input_line_; }
|
break;
|
||||||
++history_index_;
|
case SDL_SCANCODE_TAB:
|
||||||
input_line_ = history_[static_cast<size_t>(history_index_)];
|
handleTab();
|
||||||
}
|
break;
|
||||||
break;
|
default:
|
||||||
case SDL_SCANCODE_DOWN:
|
break;
|
||||||
// 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<size_t>(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<char>(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<int>(tab_matches_.size());
|
|
||||||
std::string result = tab_matches_[static_cast<size_t>(tab_index_)];
|
|
||||||
std::ranges::transform(result, result.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
|
||||||
input_line_ = result;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,14 @@ class Console {
|
|||||||
void redrawText(); // Redibuja el texto dinámico (msg + input + cursor)
|
void redrawText(); // Redibuja el texto dinámico (msg + input + cursor)
|
||||||
void processCommand(); // Procesa el comando introducido por el usuario
|
void processCommand(); // Procesa el comando introducido por el usuario
|
||||||
[[nodiscard]] auto wrapText(const std::string& text) const -> std::vector<std::string>; // Word-wrap por ancho en píxeles
|
[[nodiscard]] auto wrapText(const std::string& text) const -> std::vector<std::string>; // 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
|
// Objetos de renderizado
|
||||||
std::shared_ptr<Text> text_;
|
std::shared_ptr<Text> text_;
|
||||||
|
|||||||
Reference in New Issue
Block a user