feat(service-menu): pobla SISTEMA amb reinici, eixir i confirmacions
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
// relaunch.cpp - Implementacio del reinici en calent
|
||||
// © 2026 JailDesigner
|
||||
|
||||
#include "core/system/relaunch.hpp"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <process.h> // _execv
|
||||
#else
|
||||
#include <unistd.h> // execv
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
// Estat global (process-scope). Aquesta TU es la unica que gestiona el
|
||||
// reinici, aixi que els static interns no s'escapen.
|
||||
char** g_argv = nullptr;
|
||||
bool g_requested = false;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace System::Relaunch {
|
||||
|
||||
void setArgv(int /*argc*/, char** argv) {
|
||||
g_argv = argv;
|
||||
}
|
||||
|
||||
void request() {
|
||||
g_requested = true;
|
||||
}
|
||||
|
||||
auto isRequested() -> bool {
|
||||
return g_requested;
|
||||
}
|
||||
|
||||
void execIfRequested() {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// Al navegador el reinici real seria location.reload(); aqui no fem res.
|
||||
return;
|
||||
#else
|
||||
if (!g_requested || g_argv == nullptr || g_argv[0] == nullptr) {
|
||||
return;
|
||||
}
|
||||
std::cout << "[Relaunch] Reiniciant " << g_argv[0] << "...\n";
|
||||
#ifdef _WIN32
|
||||
_execv(g_argv[0], g_argv);
|
||||
#else
|
||||
execv(g_argv[0], g_argv);
|
||||
#endif
|
||||
// Si arribem aqui, execv ha fallat. Tots els subsistemes ja estan
|
||||
// destruits; sortim amb error i el shell rebra el codi.
|
||||
std::cerr << "[Relaunch] Ha fallat: " << std::strerror(errno) << '\n';
|
||||
std::exit(EXIT_FAILURE);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace System::Relaunch
|
||||
@@ -0,0 +1,33 @@
|
||||
// relaunch.hpp - Reinici en calent del proces (execv)
|
||||
// © 2026 JailDesigner
|
||||
//
|
||||
// Helper desacoblat per a permetre que el menu de servei demani un reinici
|
||||
// sense conèixer Director ni main.cpp. Patro:
|
||||
//
|
||||
// main() → Relaunch::setArgv(argc, argv) (a l'arrencada)
|
||||
// ServiceMenu → Relaunch::request() (en activar REINICIAR)
|
||||
// main() → Relaunch::execIfRequested() (a SDL_AppQuit)
|
||||
//
|
||||
// L'execv() reemplaca el proces actual: si torna, ha fallat. A EMSCRIPTEN
|
||||
// no es pot reiniciar; isRequested() seguira dient true pero execIfRequested
|
||||
// sera no-op.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace System::Relaunch {
|
||||
|
||||
// Emmagatzema l'argv original. Cal cridar-ho una vegada des de main.
|
||||
void setArgv(int argc, char** argv);
|
||||
|
||||
// Demana un reinici (no actua immediatament; nomes marca el flag).
|
||||
void request();
|
||||
|
||||
// Consulta del flag.
|
||||
[[nodiscard]] auto isRequested() -> bool;
|
||||
|
||||
// Si hi ha reinici demanat i tenim argv valid, fa execv. En cas d'exit
|
||||
// no torna. Si execv falla, registra l'error i torna; el caller hauria
|
||||
// de sortir normalment.
|
||||
void execIfRequested();
|
||||
|
||||
} // namespace System::Relaunch
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "core/system/service_menu.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <format>
|
||||
@@ -16,8 +17,10 @@
|
||||
#include "core/locale/locale.hpp"
|
||||
#include "core/rendering/sdl_manager.hpp"
|
||||
#include "core/system/debug_overlay.hpp"
|
||||
#include "core/system/relaunch.hpp"
|
||||
#include "core/types.hpp"
|
||||
#include "game/config_yaml.hpp"
|
||||
#include "project.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -50,6 +53,17 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
// VectorText nomes admet ASCII en majuscules. El git hash sortit de git
|
||||
// rev-parse es lowercase (a-f), aixi que el passem a uppercase per al
|
||||
// display sense modificar Project::GIT_HASH.
|
||||
auto toUpperAscii(const std::string& s) -> std::string {
|
||||
std::string result = s;
|
||||
for (char& c : result) {
|
||||
c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace System {
|
||||
@@ -86,6 +100,10 @@ namespace System {
|
||||
animated_h_ = 0.0F;
|
||||
highlight_snap_ = true; // primera frame: enganxar el highlight al cursor
|
||||
buildRootPage();
|
||||
// L'ample comença ja al valor objectiu (la caixa surt amb l'amplada
|
||||
// final i nomes anima l'alçada). L'ample s'animarà despres entre
|
||||
// pagines (push/pop).
|
||||
animated_w_ = computeTargetWidth();
|
||||
playAcceptSound();
|
||||
return;
|
||||
}
|
||||
@@ -121,20 +139,12 @@ namespace System {
|
||||
makeSubmenu("service_menu.video", [this] { pushPage(buildVideoPage()); }),
|
||||
makeSubmenu("service_menu.audio", [this] { pushPage(buildAudioPage()); }),
|
||||
makeSubmenu("service_menu.options", [this] { pushPage(buildOptionsPage()); }),
|
||||
makeSubmenu("service_menu.system", [this] { pushSubmenuPlaceholder("service_menu.system"); }),
|
||||
makeSubmenu("service_menu.system", [this] { pushPage(buildSystemPage()); }),
|
||||
};
|
||||
stack_.clear();
|
||||
stack_.push_back(std::move(root));
|
||||
}
|
||||
|
||||
void ServiceMenu::pushSubmenuPlaceholder(const std::string& title_key) {
|
||||
Page page;
|
||||
page.title_key = title_key;
|
||||
// items buit: el submenu mostra nomes el titol (al·iteracions futures
|
||||
// s'omplen amb opcions reals de Vsync/Zoom/Locale/Restart/Exit/etc.).
|
||||
pushPage(std::move(page));
|
||||
}
|
||||
|
||||
auto ServiceMenu::buildVideoPage() const -> Page {
|
||||
// Helper: localitza ON/OFF per a TOGGLE items.
|
||||
auto on_off_text = [](bool v) -> std::string {
|
||||
@@ -346,6 +356,76 @@ namespace System {
|
||||
return page;
|
||||
}
|
||||
|
||||
auto ServiceMenu::buildSystemPage() -> Page {
|
||||
Page page;
|
||||
page.title_key = "service_menu.system";
|
||||
// Versio + hash com a subtitol sota el titol (apagat, mes petit).
|
||||
// Uppercase del hash perque VectorText nomes admet majuscules.
|
||||
page.subtitle_provider = [] {
|
||||
return std::format("V{} - {}", Project::VERSION, toUpperAscii(Project::GIT_HASH));
|
||||
};
|
||||
page.items = {
|
||||
// REINICIAR (amb confirmacio).
|
||||
Item{
|
||||
.kind = Kind::ACTION,
|
||||
.label_key = "service_menu.system_restart",
|
||||
.selectable = true,
|
||||
.on_activate = [this] {
|
||||
pushConfirmPage("service_menu.confirm_restart", [] {
|
||||
System::Relaunch::request();
|
||||
SDL_Event quit_event{};
|
||||
quit_event.type = SDL_EVENT_QUIT;
|
||||
SDL_PushEvent(&quit_event);
|
||||
});
|
||||
},
|
||||
.get_value_text = {},
|
||||
.on_change = {},
|
||||
},
|
||||
// EIXIR DEL JOC (amb confirmacio).
|
||||
Item{
|
||||
.kind = Kind::ACTION,
|
||||
.label_key = "service_menu.exit",
|
||||
.selectable = true,
|
||||
.on_activate = [this] {
|
||||
pushConfirmPage("service_menu.confirm_exit", [] {
|
||||
SDL_Event quit_event{};
|
||||
quit_event.type = SDL_EVENT_QUIT;
|
||||
SDL_PushEvent(&quit_event);
|
||||
});
|
||||
},
|
||||
.get_value_text = {},
|
||||
.on_change = {},
|
||||
},
|
||||
};
|
||||
return page;
|
||||
}
|
||||
|
||||
void ServiceMenu::pushConfirmPage(const std::string& title_key, std::function<void()> on_yes) {
|
||||
auto yes_callback = std::move(on_yes);
|
||||
Page page;
|
||||
page.title_key = title_key;
|
||||
page.cursor = 0; // per defecte sobre NO (segur)
|
||||
page.items = {
|
||||
Item{
|
||||
.kind = Kind::ACTION,
|
||||
.label_key = "service_menu.confirm_no",
|
||||
.selectable = true,
|
||||
.on_activate = [this] { popPage(); },
|
||||
.get_value_text = {},
|
||||
.on_change = {},
|
||||
},
|
||||
Item{
|
||||
.kind = Kind::ACTION,
|
||||
.label_key = "service_menu.confirm_yes",
|
||||
.selectable = true,
|
||||
.on_activate = std::move(yes_callback),
|
||||
.get_value_text = {},
|
||||
.on_change = {},
|
||||
},
|
||||
};
|
||||
pushPage(std::move(page));
|
||||
}
|
||||
|
||||
void ServiceMenu::pushPage(Page page) {
|
||||
stack_.push_back(std::move(page));
|
||||
// El cursor salta a una pagina nova: enganxem el highlight per a
|
||||
@@ -463,8 +543,12 @@ namespace System {
|
||||
}
|
||||
using namespace Defaults::ServiceMenu;
|
||||
const Page& page = stack_.back();
|
||||
int h = GAP_Y; // padding superior
|
||||
h += TITLE_HEIGHT + GAP_Y; // titol + gap
|
||||
int h = GAP_Y; // padding superior
|
||||
h += TITLE_HEIGHT; // titol
|
||||
if (page.subtitle_provider) {
|
||||
h += GAP_Y / 2 + SUBTITLE_HEIGHT; // subtitol amb mig gap
|
||||
}
|
||||
h += GAP_Y; // gap abans del separador
|
||||
h += SEPARATOR_HEIGHT + GAP_Y; // separador + gap
|
||||
const auto N = static_cast<int>(page.items.size());
|
||||
if (N > 0) {
|
||||
@@ -473,15 +557,58 @@ namespace System {
|
||||
return static_cast<float>(h);
|
||||
}
|
||||
|
||||
auto ServiceMenu::computeItemTopY(float box_y, std::size_t index) -> float {
|
||||
auto ServiceMenu::computeTargetWidth() const -> float {
|
||||
using namespace Defaults::ServiceMenu;
|
||||
const float ITEMS_Y0 = box_y +
|
||||
static_cast<float>(GAP_Y) +
|
||||
static_cast<float>(TITLE_HEIGHT) +
|
||||
if (stack_.empty()) {
|
||||
return static_cast<float>(BOX_WIDTH_MIN);
|
||||
}
|
||||
const Page& page = stack_.back();
|
||||
// Comencem amb l'ample del titol.
|
||||
float content_w = Graphics::VectorText::getTextWidth(
|
||||
Locale::get().text(page.title_key),
|
||||
TITLE_SCALE,
|
||||
TEXT_SPACING);
|
||||
if (page.subtitle_provider) {
|
||||
content_w = std::max(content_w, Graphics::VectorText::getTextWidth(page.subtitle_provider(), SUBTITLE_SCALE, TEXT_SPACING));
|
||||
}
|
||||
for (const Item& item : page.items) {
|
||||
const std::string LABEL = item.label_key.empty()
|
||||
? std::string{}
|
||||
: Locale::get().text(item.label_key);
|
||||
if (item.label_key.empty() && item.get_value_text) {
|
||||
content_w = std::max(content_w, Graphics::VectorText::getTextWidth(item.get_value_text(), ITEM_SCALE, TEXT_SPACING));
|
||||
} else if (item.get_value_text) {
|
||||
const float LABEL_W = Graphics::VectorText::getTextWidth(LABEL, ITEM_SCALE, TEXT_SPACING);
|
||||
const float VALUE_W = Graphics::VectorText::getTextWidth(
|
||||
item.get_value_text(),
|
||||
ITEM_SCALE,
|
||||
TEXT_SPACING);
|
||||
content_w = std::max(content_w,
|
||||
LABEL_W + static_cast<float>(MIN_LABEL_VALUE_GAP) + VALUE_W);
|
||||
} else {
|
||||
content_w = std::max(content_w,
|
||||
Graphics::VectorText::getTextWidth(LABEL, ITEM_SCALE, TEXT_SPACING));
|
||||
}
|
||||
}
|
||||
// Padding total: highlight pad als dos costats + inset del text.
|
||||
const float REQUIRED = content_w +
|
||||
(2.0F * static_cast<float>(HIGHLIGHT_PAD_X)) +
|
||||
(2.0F * static_cast<float>(TEXT_INSET_X));
|
||||
return std::max(static_cast<float>(BOX_WIDTH_MIN), REQUIRED);
|
||||
}
|
||||
|
||||
auto ServiceMenu::computeItemTopY(float box_y, std::size_t index, bool has_subtitle) -> float {
|
||||
using namespace Defaults::ServiceMenu;
|
||||
float items_y0 = box_y +
|
||||
static_cast<float>(GAP_Y) +
|
||||
static_cast<float>(TITLE_HEIGHT);
|
||||
if (has_subtitle) {
|
||||
items_y0 += static_cast<float>(GAP_Y / 2) + static_cast<float>(SUBTITLE_HEIGHT);
|
||||
}
|
||||
items_y0 += static_cast<float>(GAP_Y) +
|
||||
static_cast<float>(SEPARATOR_HEIGHT) +
|
||||
static_cast<float>(GAP_Y);
|
||||
return ITEMS_Y0 + (static_cast<float>(index) * static_cast<float>(ITEM_HEIGHT + ITEM_GAP_Y));
|
||||
return items_y0 + (static_cast<float>(index) * static_cast<float>(ITEM_HEIGHT + ITEM_GAP_Y));
|
||||
}
|
||||
|
||||
void ServiceMenu::update(float delta_time) {
|
||||
@@ -504,10 +631,13 @@ namespace System {
|
||||
open_anim_ = std::min(1.0F, open_anim_ + (OPEN_SPEED * delta_time));
|
||||
}
|
||||
|
||||
// Smoothing exponencial cap a l'alçada objectiu de la pagina superior.
|
||||
const float TARGET = closing_ ? 0.0F : computeTargetHeight();
|
||||
const float ALPHA = 1.0F - std::exp(-HEIGHT_RATE * delta_time);
|
||||
animated_h_ += (TARGET - animated_h_) * ALPHA;
|
||||
// Smoothing exponencial cap a l'alçada i ample objectius de la pagina.
|
||||
const float TARGET_H_BOX = closing_ ? 0.0F : computeTargetHeight();
|
||||
const float TARGET_W_BOX = closing_ ? animated_w_ : computeTargetWidth();
|
||||
const float ALPHA_H = 1.0F - std::exp(-HEIGHT_RATE * delta_time);
|
||||
const float ALPHA_W = 1.0F - std::exp(-WIDTH_RATE * delta_time);
|
||||
animated_h_ += (TARGET_H_BOX - animated_h_) * ALPHA_H;
|
||||
animated_w_ += (TARGET_W_BOX - animated_w_) * ALPHA_W;
|
||||
|
||||
// Highlight: lerp cap a la Y de l'item del cursor. Snapping nomes en
|
||||
// obrir o canviar de pagina; en moure el cursor amb UP/DOWN, el rect
|
||||
@@ -522,7 +652,8 @@ namespace System {
|
||||
}
|
||||
const float BOX_H_TARGET = computeTargetHeight();
|
||||
const float BOX_Y_TARGET = (CANVAS_H - BOX_H_TARGET) * 0.5F;
|
||||
const float ITEM_TOP = computeItemTopY(BOX_Y_TARGET, page.cursor);
|
||||
const bool HAS_SUBTITLE = static_cast<bool>(page.subtitle_provider);
|
||||
const float ITEM_TOP = computeItemTopY(BOX_Y_TARGET, page.cursor, HAS_SUBTITLE);
|
||||
const float TARGET_Y = ITEM_TOP - static_cast<float>(HIGHLIGHT_PAD_Y);
|
||||
const float TARGET_H = static_cast<float>(ITEM_HEIGHT) + (2.0F * static_cast<float>(HIGHLIGHT_PAD_Y));
|
||||
if (highlight_snap_) {
|
||||
@@ -603,9 +734,9 @@ namespace System {
|
||||
return;
|
||||
}
|
||||
|
||||
const float BOX_X = (CANVAS_W - static_cast<float>(BOX_WIDTH)) * 0.5F;
|
||||
const float BOX_W = animated_w_;
|
||||
const float BOX_X = (CANVAS_W - BOX_W) * 0.5F;
|
||||
const float BOX_Y = (CANVAS_H - BOX_H) * 0.5F;
|
||||
const auto BOX_W = static_cast<float>(BOX_WIDTH);
|
||||
const float CENTER_X = BOX_X + (BOX_W * 0.5F);
|
||||
|
||||
// Fons semi-transparent.
|
||||
@@ -623,6 +754,7 @@ namespace System {
|
||||
renderer_->pushClip(CLIP_X, CLIP_Y, CLIP_W, CLIP_H);
|
||||
|
||||
const Page& page = stack_.back();
|
||||
const bool HAS_SUBTITLE = static_cast<bool>(page.subtitle_provider);
|
||||
|
||||
// Titol centrat al cim de la caixa.
|
||||
const std::string TITLE = Locale::get().text(page.title_key);
|
||||
@@ -634,8 +766,27 @@ namespace System {
|
||||
1.0F,
|
||||
TITLE_COLOR);
|
||||
|
||||
// Separador horitzontal sota el titol.
|
||||
const float SEP_Y = BOX_Y + static_cast<float>(GAP_Y) + static_cast<float>(TITLE_HEIGHT) + (static_cast<float>(GAP_Y) * 0.5F);
|
||||
// Subtitol opcional: sota el titol, mes petit i apagat.
|
||||
if (HAS_SUBTITLE) {
|
||||
const float SUBTITLE_CY = BOX_Y + static_cast<float>(GAP_Y) +
|
||||
static_cast<float>(TITLE_HEIGHT) +
|
||||
(static_cast<float>(GAP_Y) / 4.0F) +
|
||||
(static_cast<float>(SUBTITLE_HEIGHT) * 0.5F);
|
||||
text_.renderCentered(page.subtitle_provider(),
|
||||
Vec2{.x = CENTER_X, .y = SUBTITLE_CY},
|
||||
SUBTITLE_SCALE,
|
||||
TEXT_SPACING,
|
||||
1.0F,
|
||||
SUBTITLE_COLOR);
|
||||
}
|
||||
|
||||
// Separador horitzontal sota el titol (o subtitol si n'hi ha).
|
||||
float sep_y = BOX_Y + static_cast<float>(GAP_Y) + static_cast<float>(TITLE_HEIGHT);
|
||||
if (HAS_SUBTITLE) {
|
||||
sep_y += static_cast<float>(GAP_Y / 2) + static_cast<float>(SUBTITLE_HEIGHT);
|
||||
}
|
||||
sep_y += static_cast<float>(GAP_Y) * 0.5F;
|
||||
const float SEP_Y = sep_y;
|
||||
fillRect(renderer_,
|
||||
BOX_X + static_cast<float>(GAP_Y),
|
||||
SEP_Y,
|
||||
@@ -659,11 +810,24 @@ namespace System {
|
||||
for (std::size_t i = 0; i < page.items.size(); ++i) {
|
||||
const Item& item = page.items[i];
|
||||
const SDL_Color COL = (i == page.cursor) ? CURSOR_COLOR : LABEL_COLOR;
|
||||
const std::string LABEL = Locale::get().text(item.label_key);
|
||||
const float ITEM_TOP = computeItemTopY(BOX_Y, i);
|
||||
// Salta el Locale lookup si label_key esta buit (item nomes-valor).
|
||||
const std::string LABEL = item.label_key.empty()
|
||||
? std::string{}
|
||||
: Locale::get().text(item.label_key);
|
||||
const float ITEM_TOP = computeItemTopY(BOX_Y, i, HAS_SUBTITLE);
|
||||
const float ITEM_CY = ITEM_TOP + (static_cast<float>(ITEM_HEIGHT) * 0.5F);
|
||||
|
||||
if (item.get_value_text) {
|
||||
if (item.label_key.empty() && item.get_value_text) {
|
||||
// Item nomes-valor (sense label_key): el text del valor es
|
||||
// renderitza centrat com a label decoratiu. Util per a items
|
||||
// d'informacio com la versio/hash a SISTEMA.
|
||||
text_.renderCentered(item.get_value_text(),
|
||||
Vec2{.x = CENTER_X, .y = ITEM_CY},
|
||||
ITEM_SCALE,
|
||||
TEXT_SPACING,
|
||||
1.0F,
|
||||
COL);
|
||||
} else if (item.get_value_text) {
|
||||
// Layout dues columnes: label esquerra, valor dreta.
|
||||
const std::string VALUE = item.get_value_text();
|
||||
const float TEXT_TOP_Y = ITEM_CY - TEXT_TOP_OFFSET;
|
||||
|
||||
@@ -62,6 +62,10 @@ namespace System {
|
||||
|
||||
struct Page {
|
||||
std::string title_key;
|
||||
// Subtitol opcional, renderitzat sota el titol amb tipografia mes
|
||||
// petita i color apagat. Es una funcio perque pot ser dinamic
|
||||
// (versio+hash, etc.). Si esta buit, no es renderitza.
|
||||
std::function<std::string()> subtitle_provider;
|
||||
std::vector<Item> items;
|
||||
std::size_t cursor = 0;
|
||||
};
|
||||
@@ -88,10 +92,13 @@ namespace System {
|
||||
ServiceMenu(Rendering::Renderer* renderer, SDLManager* sdl, DebugOverlay* debug_overlay);
|
||||
|
||||
void buildRootPage();
|
||||
void pushSubmenuPlaceholder(const std::string& title_key);
|
||||
[[nodiscard]] auto buildVideoPage() const -> Page;
|
||||
[[nodiscard]] static auto buildAudioPage() -> Page;
|
||||
[[nodiscard]] auto buildOptionsPage() const -> Page;
|
||||
[[nodiscard]] auto buildSystemPage() -> Page;
|
||||
// Pagina de confirmacio "ESTAS SEGUR? NO/SI". on_yes s'executa si
|
||||
// l'usuari selecciona SI; el cursor per defecte apunta a NO.
|
||||
void pushConfirmPage(const std::string& title_key, std::function<void()> on_yes);
|
||||
void pushPage(Page page);
|
||||
void popPage();
|
||||
void moveCursor(int direction);
|
||||
@@ -103,8 +110,13 @@ namespace System {
|
||||
// Alçada objectiu de la caixa per a la pagina superior (sense animacio).
|
||||
[[nodiscard]] auto computeTargetHeight() const -> float;
|
||||
|
||||
// Y (top) de l'item index dins una caixa col·locada a box_y.
|
||||
[[nodiscard]] static auto computeItemTopY(float box_y, std::size_t index) -> float;
|
||||
// Ample objectiu de la caixa per a la pagina superior (sense animacio).
|
||||
// Pren com a base BOX_WIDTH_MIN i s'eixampla si algun text no hi cap.
|
||||
[[nodiscard]] auto computeTargetWidth() const -> float;
|
||||
|
||||
// Y (top) de l'item index dins una caixa col·locada a box_y. Si la
|
||||
// pagina te subtitol, els items es desplacen cap avall.
|
||||
[[nodiscard]] static auto computeItemTopY(float box_y, std::size_t index, bool has_subtitle) -> float;
|
||||
|
||||
Rendering::Renderer* renderer_;
|
||||
SDLManager* sdl_;
|
||||
@@ -116,6 +128,7 @@ namespace System {
|
||||
bool closing_ = false;
|
||||
float open_anim_ = 0.0F; // 0..1 raw (sense easing)
|
||||
float animated_h_ = 0.0F; // Alçada animada amb smoothing exponencial
|
||||
float animated_w_ = 0.0F; // Ample animat (eixampla segons contingut)
|
||||
|
||||
// Estat del highlight (rectangle del cursor). Es lerpa cap a l'item
|
||||
// actiu amb ease-out exponencial; quan el cursor "salta" (open o
|
||||
|
||||
Reference in New Issue
Block a user