service menu vitaminat: cliping, swapping animation i versió
This commit is contained in:
@@ -7,8 +7,10 @@
|
||||
#include "core/rendering/text.hpp"
|
||||
#include "game/ui/menu_option.hpp"
|
||||
#include "utils/color.hpp"
|
||||
#include "utils/defines.hpp" // Para Texts::VERSION
|
||||
#include "utils/param.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "version.h" // Para Version::GIT_HASH
|
||||
|
||||
// --- Implementación de las estructuras de animación ---
|
||||
|
||||
@@ -74,6 +76,17 @@ void MenuRenderer::render(const ServiceMenu* menu_state) {
|
||||
|
||||
// Solo renderizar contenido si la animación lo permite
|
||||
if (shouldShowContent()) {
|
||||
// Recorta todas las operaciones de texto y líneas al rect actual del panel
|
||||
// para que durante el resize animation nada se pinte fuera del borde.
|
||||
auto* renderer = Screen::get()->getRenderer();
|
||||
const SDL_Rect CLIP_RECT = {
|
||||
.x = static_cast<int>(rect_.x),
|
||||
.y = static_cast<int>(rect_.y),
|
||||
.w = static_cast<int>(rect_.w),
|
||||
.h = static_cast<int>(rect_.h),
|
||||
};
|
||||
SDL_SetRenderClipRect(renderer, &CLIP_RECT);
|
||||
|
||||
// Dibuja el título
|
||||
float y = rect_.y + title_padding_;
|
||||
title_text_->writeDX(Text::COLOR | Text::CENTER, rect_.x + (rect_.w / 2.0F), y, menu_state->getTitle(), -4, param.service_menu.title_color);
|
||||
@@ -83,27 +96,52 @@ void MenuRenderer::render(const ServiceMenu* menu_state) {
|
||||
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), BORDER_COLOR.r, BORDER_COLOR.g, BORDER_COLOR.b, 255);
|
||||
SDL_RenderLine(Screen::get()->getRenderer(), rect_.x + ServiceMenu::OPTIONS_HORIZONTAL_PADDING, y, rect_.x + rect_.w - ServiceMenu::OPTIONS_HORIZONTAL_PADDING, y);
|
||||
|
||||
// Dibuja el subtítulo del grupo (versión + hash en SYSTEM)
|
||||
if (groupHasSubtitle(menu_state->getCurrentGroup())) {
|
||||
const std::string SUBTITLE = "ver. " + std::string(Texts::VERSION) + " (" + std::string(Version::GIT_HASH) + ")";
|
||||
element_text_->writeDX(
|
||||
Text::CENTER | Text::COLOR,
|
||||
rect_.x + (rect_.w / 2.0F),
|
||||
y + options_padding_,
|
||||
SUBTITLE,
|
||||
-2,
|
||||
param.service_menu.text_color);
|
||||
}
|
||||
|
||||
// Dibuja las opciones
|
||||
y = options_y_;
|
||||
const auto& option_pairs = menu_state->getOptionPairs();
|
||||
const float ROW_HEIGHT = static_cast<float>(options_height_ + options_padding_);
|
||||
|
||||
for (size_t i = 0; i < option_pairs.size(); ++i) {
|
||||
const bool IS_SELECTED = (i == menu_state->getSelectedIndex());
|
||||
const Color& current_color = IS_SELECTED ? param.service_menu.selected_color : param.service_menu.text_color;
|
||||
|
||||
// Offset Y del valor durante la animación de swap: va desde la fila
|
||||
// origen (la otra implicada) hasta su fila natural con easeOut.
|
||||
float value_y_offset = 0.0F;
|
||||
if (swap_animation_.active && (i == swap_animation_.idx_a || i == swap_animation_.idx_b)) {
|
||||
const size_t OTHER = (i == swap_animation_.idx_a) ? swap_animation_.idx_b : swap_animation_.idx_a;
|
||||
const float PROGRESS = easeOut(swap_animation_.elapsed / swap_animation_.duration);
|
||||
const float DELTA_ROWS = static_cast<float>(OTHER) - static_cast<float>(i);
|
||||
value_y_offset = DELTA_ROWS * ROW_HEIGHT * (1.0F - PROGRESS);
|
||||
}
|
||||
|
||||
if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) {
|
||||
const int AVAILABLE_WIDTH = rect_.w - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2) - element_text_->length(option_pairs.at(i).first, -2) - ServiceMenu::MIN_GAP_OPTION_VALUE;
|
||||
std::string truncated_value = getTruncatedValue(option_pairs.at(i).second, AVAILABLE_WIDTH);
|
||||
element_text_->writeColored(rect_.x + ServiceMenu::OPTIONS_HORIZONTAL_PADDING, y, option_pairs.at(i).first, current_color, -2);
|
||||
const int X = rect_.x + rect_.w - ServiceMenu::OPTIONS_HORIZONTAL_PADDING - element_text_->length(truncated_value, -2);
|
||||
element_text_->writeColored(X, y, truncated_value, current_color, -2);
|
||||
element_text_->writeColored(X, static_cast<int>(y + value_y_offset), truncated_value, current_color, -2);
|
||||
} else {
|
||||
const int AVAILABLE_WIDTH = rect_.w - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2);
|
||||
std::string truncated_caption = getTruncatedValue(option_pairs.at(i).first, AVAILABLE_WIDTH);
|
||||
element_text_->writeDX(Text::CENTER | Text::COLOR, rect_.x + (rect_.w / 2.0F), y, truncated_caption, -2, current_color);
|
||||
}
|
||||
y += options_height_ + options_padding_;
|
||||
y += ROW_HEIGHT;
|
||||
}
|
||||
|
||||
SDL_SetRenderClipRect(renderer, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +239,8 @@ auto MenuRenderer::calculateNewRect(const ServiceMenu* menu_state) -> SDL_FRect
|
||||
width_ = std::min(static_cast<size_t>(getMenuWidthForGroup(menu_state->getCurrentGroup())), max_menu_width_);
|
||||
const auto& display_options = menu_state->getDisplayOptions();
|
||||
lower_height_ = ((!display_options.empty() ? display_options.size() - 1 : 0) * (options_height_ + options_padding_)) + options_height_ + (lower_padding_ * 2);
|
||||
height_ = std::min(upper_height_ + lower_height_, max_menu_height_);
|
||||
subtitle_offset_ = getGroupSubtitleHeight(menu_state);
|
||||
height_ = std::min(upper_height_ + subtitle_offset_ + lower_height_, max_menu_height_);
|
||||
|
||||
SDL_FRect new_rect = {.x = 0, .y = 0, .w = static_cast<float>(width_), .h = static_cast<float>(height_)};
|
||||
|
||||
@@ -209,6 +248,18 @@ auto MenuRenderer::calculateNewRect(const ServiceMenu* menu_state) -> SDL_FRect
|
||||
return new_rect;
|
||||
}
|
||||
|
||||
// Grupos que muestran una línea de subtítulo bajo el título (antes de las opciones).
|
||||
auto MenuRenderer::groupHasSubtitle(ServiceMenu::SettingsGroup group) -> bool {
|
||||
return group == ServiceMenu::SettingsGroup::SYSTEM;
|
||||
}
|
||||
|
||||
// Altura extra reservada para el subtítulo del grupo actual.
|
||||
// Reservamos el alto de una línea de element_text_ más un pequeño padding.
|
||||
auto MenuRenderer::getGroupSubtitleHeight(const ServiceMenu* menu_state) const -> size_t {
|
||||
if (!groupHasSubtitle(menu_state->getCurrentGroup())) { return 0; }
|
||||
return element_text_->getCharacterSize() + options_padding_;
|
||||
}
|
||||
|
||||
void MenuRenderer::resize(const ServiceMenu* menu_state) {
|
||||
SDL_FRect new_rect = calculateNewRect(menu_state);
|
||||
|
||||
@@ -219,7 +270,7 @@ void MenuRenderer::resize(const ServiceMenu* menu_state) {
|
||||
// Si no hay cambio de tamaño, solo actualizamos la posición
|
||||
updatePosition();
|
||||
}
|
||||
options_y_ = rect_.y + upper_height_ + lower_padding_;
|
||||
options_y_ = rect_.y + upper_height_ + subtitle_offset_ + lower_padding_;
|
||||
}
|
||||
|
||||
void MenuRenderer::setSize(const ServiceMenu* menu_state) {
|
||||
@@ -231,16 +282,35 @@ void MenuRenderer::setSize(const ServiceMenu* menu_state) {
|
||||
resize_animation_.stop();
|
||||
|
||||
updatePosition();
|
||||
options_y_ = rect_.y + upper_height_ + lower_padding_;
|
||||
options_y_ = rect_.y + upper_height_ + subtitle_offset_ + lower_padding_;
|
||||
border_rect_ = {.x = rect_.x - 1, .y = rect_.y + 1, .w = rect_.w + 2, .h = rect_.h - 2};
|
||||
}
|
||||
|
||||
// --- Métodos de animación y posición ---
|
||||
|
||||
void MenuRenderer::startSwapAnimation(size_t idx_a, size_t idx_b) {
|
||||
if (idx_a == idx_b) { return; }
|
||||
swap_animation_.active = true;
|
||||
swap_animation_.idx_a = idx_a;
|
||||
swap_animation_.idx_b = idx_b;
|
||||
swap_animation_.elapsed = 0.0F;
|
||||
}
|
||||
|
||||
void MenuRenderer::updateSwapAnimation(float delta_time) {
|
||||
swap_animation_.elapsed += delta_time;
|
||||
if (swap_animation_.elapsed >= swap_animation_.duration) {
|
||||
swap_animation_.active = false;
|
||||
swap_animation_.elapsed = 0.0F;
|
||||
}
|
||||
}
|
||||
|
||||
void MenuRenderer::updateAnimations(float delta_time) {
|
||||
if (show_hide_animation_.active) {
|
||||
updateShowHideAnimation(delta_time);
|
||||
}
|
||||
if (swap_animation_.active) {
|
||||
updateSwapAnimation(delta_time);
|
||||
}
|
||||
if (resize_animation_.active) {
|
||||
updateResizeAnimation(delta_time);
|
||||
}
|
||||
@@ -272,7 +342,7 @@ void MenuRenderer::updateShowHideAnimation(float delta_time) {
|
||||
}
|
||||
updatePosition();
|
||||
}
|
||||
options_y_ = rect_.y + upper_height_ + lower_padding_;
|
||||
options_y_ = rect_.y + upper_height_ + subtitle_offset_ + lower_padding_;
|
||||
}
|
||||
|
||||
void MenuRenderer::updateResizeAnimation(float delta_time) {
|
||||
@@ -290,7 +360,7 @@ void MenuRenderer::updateResizeAnimation(float delta_time) {
|
||||
rect_.h = resize_animation_.start_height + ((resize_animation_.target_height - resize_animation_.start_height) * progress);
|
||||
updatePosition();
|
||||
}
|
||||
options_y_ = rect_.y + upper_height_ + lower_padding_;
|
||||
options_y_ = rect_.y + upper_height_ + subtitle_offset_ + lower_padding_;
|
||||
}
|
||||
|
||||
void MenuRenderer::updatePosition() {
|
||||
@@ -341,6 +411,12 @@ void MenuRenderer::precalculateMenuWidths(const std::vector<std::unique_ptr<Menu
|
||||
if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) {
|
||||
total_width += ServiceMenu::MIN_GAP_OPTION_VALUE + max_value_width;
|
||||
}
|
||||
// Si el grupo tiene subtítulo, ensanchamos lo necesario para que quepa
|
||||
if (groupHasSubtitle(sg)) {
|
||||
const std::string SUBTITLE = "ver. " + std::string(Texts::VERSION) + " (" + std::string(Version::GIT_HASH) + ")";
|
||||
const size_t SUBTITLE_WIDTH = static_cast<size_t>(element_text_->length(SUBTITLE, -2)) + (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2);
|
||||
total_width = std::max(total_width, SUBTITLE_WIDTH);
|
||||
}
|
||||
group_menu_widths_[group] = std::min(std::max(static_cast<int>(ServiceMenu::MIN_WIDTH), static_cast<int>(total_width)), static_cast<int>(max_menu_width_));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user