#pragma once #include #include #include #include #include #include "UILayout.h" namespace grove { class UIContext; class UIRenderer; // Callback for when widget is destroyed (to notify renderer) using WidgetDestroyCallback = std::function; /** * @brief Base interface for all UI widgets * * Retained-mode UI widget with hierarchical structure. * Each widget has position, size, visibility, and can have children. */ class UIWidget { public: virtual ~UIWidget() { // Notify renderer to remove this widget's render entries if (m_renderId != 0 && m_destroyCallback) { m_destroyCallback(m_renderId); } } /** * @brief Update widget state * @param ctx UI context with input state * @param deltaTime Time since last update */ virtual void update(UIContext& ctx, float deltaTime) = 0; /** * @brief Render widget via UIRenderer * @param renderer Renderer that publishes to IIO */ virtual void render(UIRenderer& renderer) = 0; /** * @brief Get widget type name * @return Type string (e.g., "panel", "label", "button") */ virtual std::string getType() const = 0; // Identity std::string id; std::string tooltip; // Tooltip text (empty = no tooltip) // Position and size (relative to parent) float x = 0.0f; float y = 0.0f; float width = 0.0f; float height = 0.0f; bool visible = true; // Layout properties (Phase 2) LayoutProperties layoutProps; // Hierarchy UIWidget* parent = nullptr; std::vector> children; // Computed absolute position (after layout) float absX = 0.0f; float absY = 0.0f; /** * @brief Compute absolute position from parent chain */ void computeAbsolutePosition() { if (parent) { absX = parent->absX + x; absY = parent->absY + y; } else { absX = x; absY = y; } for (auto& child : children) { child->computeAbsolutePosition(); } } /** * @brief Add a child widget * @param child Widget to add */ void addChild(std::unique_ptr child) { child->parent = this; children.push_back(std::move(child)); } /** * @brief Find widget by ID recursively * @param targetId ID to search for * @return Widget pointer or nullptr */ UIWidget* findById(const std::string& targetId) { if (id == targetId) { return this; } for (auto& child : children) { if (UIWidget* found = child->findById(targetId)) { return found; } } return nullptr; } protected: /** * @brief Update all children */ void updateChildren(UIContext& ctx, float deltaTime) { for (auto& child : children) { if (child->visible) { child->update(ctx, deltaTime); } } } /** * @brief Render all children */ void renderChildren(UIRenderer& renderer) { for (auto& child : children) { if (child->visible) { child->render(renderer); } } } // ======================================================================== // Retained Mode / Dirty Tracking // ======================================================================== protected: uint32_t m_renderId = 0; // Unique ID for render system (0 = not registered) bool m_geometryDirty = true; // Position/size changed, needs re-render bool m_appearanceDirty = true; // Color/style changed, needs re-render bool m_registered = false; // Has been registered with renderer WidgetDestroyCallback m_destroyCallback; // Called on destruction public: /** * @brief Get render ID (0 if not registered) */ uint32_t getRenderId() const { return m_renderId; } /** * @brief Set render ID (called by UIRenderer on registration) */ void setRenderId(uint32_t id) { m_renderId = id; } /** * @brief Check if widget needs re-rendering */ bool isDirty() const { return m_geometryDirty || m_appearanceDirty; } /** * @brief Check if registered with renderer */ bool isRegistered() const { return m_registered; } /** * @brief Mark as registered */ void setRegistered(bool reg) { m_registered = reg; } /** * @brief Mark geometry as dirty (position, size changed) */ void markGeometryDirty() { m_geometryDirty = true; } /** * @brief Mark appearance as dirty (color, style changed) */ void markAppearanceDirty() { m_appearanceDirty = true; } /** * @brief Clear dirty flags after rendering */ void clearDirtyFlags() { m_geometryDirty = false; m_appearanceDirty = false; } /** * @brief Set callback for widget destruction */ void setDestroyCallback(WidgetDestroyCallback callback) { m_destroyCallback = std::move(callback); } }; } // namespace grove