diff --git a/docs/fase4/README.md b/docs/fase4/README.md index 8c6e346..ab580c8 100644 --- a/docs/fase4/README.md +++ b/docs/fase4/README.md @@ -18,7 +18,7 @@ Esta fase implementa o **ECS completo** β€” a arquitetura de dados central que c | **Audio System** | [`audio.md`](audio.md) | `Caffeine::Audio` | 4 | πŸ“… | | **Animation System** | [`animation.md`](animation.md) | `Caffeine::Animation` | 4 | πŸ“… | | **Physics 2D** | [`physics.md`](physics.md) | `Caffeine::Physics2D` | 4 | βœ… | -| **UI System** | [`ui.md`](ui.md) | `Caffeine::UI` | 4 | πŸ“… | +| **UI System** | [`ui.md`](ui.md) | `Caffeine::UI` | 4 | βœ… | --- diff --git a/docs/fase4/ui.md b/docs/fase4/ui.md index 5490fb7..b572a01 100644 --- a/docs/fase4/ui.md +++ b/docs/fase4/ui.md @@ -2,143 +2,99 @@ > **Fase:** 4 β€” O CΓ©rebro > **Namespace:** `Caffeine::UI` -> **Arquivo:** `src/ui/UISystem.hpp` -> **Status:** πŸ“… Planejado +> **Arquivos:** `src/ui/UIComponents.hpp`, `src/ui/UISystem.hpp` +> **Status:** βœ… Implementado > **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). +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 um `Health` component via `bindValue`). **Fase 6** adiciona Dear ImGui para a interface do editor β€” ver [`docs/fase6/embedded-ui.md`](../fase6/embedded-ui.md). --- -## API Planejada +## Componentes -```cpp -namespace Caffeine::UI { +### `UIColor` -// ============================================================================ -// @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 -}; +Cor RGBA com canais `f32` em `[0, 1]`. Presets estΓ‘ticos: `white()`, `black()`, `transparent()`, `red()`, `green()`, `blue()`. -// ============================================================================ -// @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 -}; +### `UIRect` -// ============================================================================ -// @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 onClick; - std::function onHoverEnter; - std::function onHoverExit; - std::function onValueChanged; // Slider/ProgressBar -}; +Rect de tela com `position` e `size` (`Vec2`). MΓ©todos: `contains(Vec2)`, `isValid()`. -// ── 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}; -}; +### `RectTransform` -struct Label : UIWidget { - FixedString<256> text; - bool wordWrap = false; -}; +Layout relativo ao parent. `anchorMin`/`anchorMax` sΓ£o fraΓ§Γ΅es do tamanho do parent; `offsetMin`/`offsetMax` sΓ£o deltas em pixels. -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 -}; +``` +anchorMin=anchorMax={0,0}, offsetMin={px,py}, offsetMax={px+w,py+h} β†’ widget fixo em (px,py) tamanho (w,h) +``` -struct Slider : UIWidget { - f32 minValue = 0.0f; - f32 maxValue = 1.0f; - f32 currentValue = 0.5f; - bool snapToInt = false; -}; +### `UIStyle` + +AparΓͺncia visual: `backgroundColor`, `textColor`, `borderColor`, `borderWidth`, `borderRadius`, `fontSize`, `textAlignment`. + +### `UIWidget` + +Componente base presente em toda entidade UI: + +| Campo | Tipo | DescriΓ§Γ£o | +|-------|------|-----------| +| `type` | `UIWidgetType` | Canvas, Panel, Button, Label, ProgressBar, Checkbox, Slider | +| `parentId` | `u32` | ID do parent; `kUIInvalidParent` para canvas raiz | +| `visible` | `bool` | Se false, nΓ£o Γ© processado nem retornado por hitTest | +| `interactable` | `bool` | Se false, ignorado por hitTest | +| `siblingOrder` | `i32` | Desempata hitTest quando dois widgets se sobrepΓ΅em | +| `computedRect` | `UIRect` | Preenchido a cada frame por `layoutWidgets()` | +| `onClick` | `std::function` | Disparado ao clicar | +| `onHoverEnter` | `std::function` | Disparado ao entrar com o mouse | +| `onHoverExit` | `std::function` | Disparado ao sair com o mouse | +| `onValueChanged` | `std::function` | Disparado por bindValue | + +### Componentes especΓ­ficos + +| Struct | Campos relevantes | +|--------|-------------------| +| `UIButton` | `labelText`, `idleColor`, `hoverColor`, `pressedColor`, `isHovered`, `isPressed` | +| `UILabel` | `text`, `wordWrap` | +| `UIProgressBar` | `minValue`, `maxValue`, `currentValue`, `showText`, `fillColor` | +| `UISlider` | `minValue`, `maxValue`, `currentValue`, `snapToInt` | +| `UICheckbox` | `checked`, `checkedColor` | + +--- + +## API β€” `UISystem` + +```cpp +namespace Caffeine::UI { -// ============================================================================ -// @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); + explicit UISystem(Events::EventBus* eventBus = nullptr); + + void onUpdate(ECS::World& world, f32 dt) override; + + ECS::Entity createCanvas(ECS::World& world, Vec2 size = {1280.0f, 720.0f}); + ECS::Entity createPanel(ECS::World& world, u32 parentId, UIRect rect); + ECS::Entity createButton(ECS::World& world, u32 parentId, const char* text, + Vec2 pos, Vec2 size = {120.0f, 40.0f}); + ECS::Entity createLabel(ECS::World& world, u32 parentId, const char* text, Vec2 pos); + ECS::Entity createProgressBar(ECS::World& world, u32 parentId, + Vec2 pos, Vec2 size = {200.0f, 20.0f}); + ECS::Entity createSlider(ECS::World& world, u32 parentId, + Vec2 pos, Vec2 size = {200.0f, 20.0f}); + ECS::Entity createCheckbox(ECS::World& world, u32 parentId, Vec2 pos); + + void bindValue(ECS::Entity widget, std::function getter); + + ECS::Entity hitTest(ECS::World& world, Vec2 screenPos); + + void injectMousePosition(Vec2 pos); + void injectMouseClick(bool pressed); }; } // namespace Caffeine::UI @@ -151,12 +107,12 @@ private: ``` Canvas (root) β”œβ”€β”€ Panel (HUD) - β”‚ β”œβ”€β”€ ProgressBar (health) ← bind β†’ Health.current - β”‚ β”œβ”€β”€ Label (score) ← bind β†’ Score.value + β”‚ β”œβ”€β”€ ProgressBar (health) ← bindValue β†’ retorna Health.current + β”‚ β”œβ”€β”€ Label (score) β”‚ └── Label (fps counter) β”œβ”€β”€ Panel (inventory) β”‚ └── [slots dinamicamente criados] - └── Panel (pause menu β€” hidden by default) + └── Panel (pause menu β€” visible=false por default) β”œβ”€β”€ Button "Resume" β”œβ”€β”€ Button "Options" └── Button "Quit" @@ -164,39 +120,31 @@ Canvas (root) --- -## Exemplos de Uso +## Exemplo de Uso ```cpp -// ── Criar HUD ───────────────────────────────────────────────── -auto* uiSys = world.registerSystem(); +UISystem uiSys; -Entity canvas = uiSys->createCanvas(world); -Entity hudPanel = uiSys->createPanel(world, canvas, {{0,0},{1280,720}}); +Entity canvas = uiSys.createCanvas(world, {1280.0f, 720.0f}); +Entity hudPanel = uiSys.createPanel(world, canvas.id(), {{0,0},{1280,720}}); -// Health bar -Entity healthBar = uiSys->createProgressBar(world, hudPanel, - {20, 700}, {200, 20}); -world.get(healthBar)->fillColor = {0.9f, 0.2f, 0.2f, 1.0f}; +Entity healthBar = uiSys.createProgressBar(world, hudPanel.id(), {20.0f, 700.0f}, {200.0f, 20.0f}); +world.get(healthBar)->fillColor = {0.9f, 0.2f, 0.2f, 1.0f}; -// Bind automΓ‘tico: healthBar.currentValue ↔ playerHealth.current -uiSys->bindComponent(healthBar, playerEntity, - ComponentID::of(), "current"); +uiSys.bindValue(healthBar, [&](ECS::World& w) { + return w.get(playerEntity)->current; +}); -// Label de score -Entity scoreLabel = uiSys->createLabel(world, hudPanel, "Score: 0", {1100, 700}); +Entity scoreLabel = uiSys.createLabel(world, hudPanel.id(), "Score: 0", {1100.0f, 700.0f}); -// ── BotΓ£o com callback ──────────────────────────────────────── -Entity playBtn = uiSys->createButton(world, canvas, "Play Game", - {640, 360}, {200, 50}); -world.get