πΌοΈ UI System
Fase: 4 β O CΓ©rebro
Namespace: Caffeine::UI
Arquivo: src/ui/UISystem.hpp
Status: π
Planejado
RF: RF4.11
VisΓ£o Geral
Sistema de UI retained mode β widgets sΓ£o entidades ECS com componentes de UI. Isso permite que UI seja afetada por ECS systems (ex: HealthBar reflete automaticamente o valor de Health component).
Fase 6 adiciona Dear ImGui para a interface do editor β ver docs/fase6/embedded-ui.md.
API Planejada
namespace Caffeine::UI {
// ============================================================================
// @brief Layout rect em espaΓ§o de tela ou fraΓ§Γ΅es do parent.
//
// anchorMin/Max em [0, 1] β (0,0) = canto inferior esquerdo da tela
// pivot em [0, 1] β (0.5, 0.5) = centro do widget
// ============================================================================
struct RectTransform {
Vec2 anchorMin = {0, 0};
Vec2 anchorMax = {1, 1};
Vec2 pivot = {0.5f, 0.5f};
Rect2D offset = {}; // pixels de deslocamento das anchors
};
// ============================================================================
// @brief Estilo visual do widget.
// ============================================================================
struct UIStyle {
Color backgroundColor = {0.1f, 0.1f, 0.1f, 0.9f};
Color textColor = Color::WHITE;
Color borderColor = {0.3f, 0.3f, 0.3f, 1.0f};
f32 borderWidth = 1.0f;
f32 borderRadius = 4.0f;
Font* font = nullptr;
f32 fontSize = 16.0f;
Vec2 textAlignment = {0.5f, 0.5f}; // (0,0) = esquerda, (1,1) = direita
};
// ============================================================================
// @brief Widget base β componente ECS para UI.
// ============================================================================
struct UIWidget {
enum class Type : u8 {
Canvas, // Raiz da hierarquia UI
Panel, // Container de outros widgets
Button, // ClicΓ‘vel
Label, // Texto estΓ‘tico
ProgressBar, // Barra de progresso (HP, XP, etc.)
Checkbox, // Toggle
Slider // Valor analΓ³gico
};
Type type = Type::Panel;
bool visible = true;
bool interactable = true;
i32 siblingOrder = 0; // ordem de renderizaΓ§Γ£o entre irmΓ£os
UIStyle style;
RectTransform transform;
// Callbacks de interaΓ§Γ£o
std::function<void(ECS::Entity)> onClick;
std::function<void(ECS::Entity)> onHoverEnter;
std::function<void(ECS::Entity)> onHoverExit;
std::function<void(ECS::Entity, f32)> onValueChanged; // Slider/ProgressBar
};
// ββ Widgets especΓficos ββββββββββββββββββββββββββββββββββββββββ
struct Button : UIWidget {
FixedString<64> labelText;
Color idleColor = {0.2f, 0.2f, 0.2f, 1.0f};
Color hoverColor = {0.35f, 0.35f, 0.35f, 1.0f};
Color pressedColor = {0.1f, 0.1f, 0.1f, 1.0f};
};
struct Label : UIWidget {
FixedString<256> text;
bool wordWrap = false;
};
struct ProgressBar : UIWidget {
f32 minValue = 0.0f;
f32 maxValue = 100.0f;
f32 currentValue = 50.0f;
bool showText = false;
Color fillColor = {0.2f, 0.8f, 0.2f, 1.0f}; // verde por default
};
struct Slider : UIWidget {
f32 minValue = 0.0f;
f32 maxValue = 1.0f;
f32 currentValue = 0.5f;
bool snapToInt = false;
};
// ============================================================================
// @brief Sistema de UI ECS.
//
// Memory: Widgets alocados no PoolAllocator por tipo.
// Priority: 500 β depois de physics/animation, antes de render
// ============================================================================
class UISystem : public ECS::ISystem {
public:
void update(ECS::World& world, f64 dt) override;
i32 priority() const override { return 500; }
const char* name() const override { return "UI"; }
// ββ Factory helpers ββββββββββββββββββββββββββββββββββββββββ
ECS::Entity createCanvas(ECS::World& world);
ECS::Entity createButton(ECS::World& world, ECS::Entity parent,
const char* text, Vec2 pos, Vec2 size = {120, 40});
ECS::Entity createLabel(ECS::World& world, ECS::Entity parent,
const char* text, Vec2 pos);
ECS::Entity createProgressBar(ECS::World& world, ECS::Entity parent,
Vec2 pos, Vec2 size = {200, 20});
// ββ Data binding βββββββββββββββββββββββββββββββββββββββββββ
// Conecta automaticamente um campo de um componente a um widget
// Ex: HealthBar.currentValue β componente Health.current
void bindComponent(ECS::Entity widget, ECS::Entity target,
ECS::ComponentID component, const char* fieldPath);
// ββ Hit testing ββββββββββββββββββββββββββββββββββββββββββββ
ECS::Entity hitTest(Vec2 screenPos) const;
private:
void layoutWidgets(ECS::World& world);
void renderWidget(ECS::Entity e, const UIWidget& widget,
RHI::CommandBuffer* cmd);
void processInput(ECS::World& world, const Input::InputManager& input);
};
} // namespace Caffeine::UI
Hierarquia de Widgets
Canvas (root)
βββ Panel (HUD)
β βββ ProgressBar (health) β bind β Health.current
β βββ Label (score) β bind β Score.value
β βββ Label (fps counter)
βββ Panel (inventory)
β βββ [slots dinamicamente criados]
βββ Panel (pause menu β hidden by default)
βββ Button "Resume"
βββ Button "Options"
βββ Button "Quit"
Exemplos de Uso
// ββ Criar HUD βββββββββββββββββββββββββββββββββββββββββββββββββ
auto* uiSys = world.registerSystem<UISystem>();
Entity canvas = uiSys->createCanvas(world);
Entity hudPanel = uiSys->createPanel(world, canvas, {{0,0},{1280,720}});
// Health bar
Entity healthBar = uiSys->createProgressBar(world, hudPanel,
{20, 700}, {200, 20});
world.get<ProgressBar>(healthBar)->fillColor = {0.9f, 0.2f, 0.2f, 1.0f};
// Bind automΓ‘tico: healthBar.currentValue β playerHealth.current
uiSys->bindComponent(healthBar, playerEntity,
ComponentID::of<Health>(), "current");
// Label de score
Entity scoreLabel = uiSys->createLabel(world, hudPanel, "Score: 0", {1100, 700});
// ββ BotΓ£o com callback ββββββββββββββββββββββββββββββββββββββββ
Entity playBtn = uiSys->createButton(world, canvas, "Play Game",
{640, 360}, {200, 50});
world.get<Button>(playBtn)->onClick = [&](ECS::Entity e) {
sceneManager.switchScene("assets/scenes/level1.caf");
};
// ββ Bind manual via update ββββββββββββββββββββββββββββββββββββ
// (alternativa ao bindComponent para lΓ³gica customizada)
world.query(scoreQuery, [&](Label& lbl, const Score& score) {
lbl.text = FixedString<256>("Score: ") + score.value;
});
DecisΓ΅es de Design
| DecisΓ£o |
Justificativa |
| Retained mode (vs immediate) |
UI persiste entre frames sem reconstruΓ§Γ£o |
| Widgets como entidades ECS |
UI pode ser afetada por systems (bindings automΓ‘ticos) |
bindComponent |
Elimina boilerplate de sincronizaΓ§Γ£o manual |
priority = 500 |
UI apΓ³s gameplay, mas antes de render final |
| Pool allocator por tipo |
Zero alloc em runtime para widgets frequentes |
CritΓ©rio de AceitaΓ§Γ£o
DependΓͺncias
ReferΓͺncias
πΌοΈ UI System
VisΓ£o Geral
Sistema de UI retained mode β widgets sΓ£o entidades ECS com componentes de UI. Isso permite que UI seja afetada por ECS systems (ex: HealthBar reflete automaticamente o valor de
Healthcomponent).Fase 6 adiciona Dear ImGui para a interface do editor β ver
docs/fase6/embedded-ui.md.API Planejada
Hierarquia de Widgets
Exemplos de Uso
DecisΓ΅es de Design
bindComponentpriority = 500CritΓ©rio de AceitaΓ§Γ£o
bindComponentatualiza ProgressBar no mesmo frame que o componente mudavisible = falsenΓ£o processadosDependΓͺncias
ReferΓͺncias
docs/architecture_specs.mdβ Β§14 UI Systemdocs/fase6/embedded-ui.mdβ Dear ImGui para editor