GroveEngine/modules/UIModule/Core/UIStyle.h
StillHammer 579cadeae8 feat: Complete UIModule Phase 7 - ScrollPanel & Tooltips
This commit implements Phase 7 of the UIModule, adding advanced features
that make the UI system production-ready.

## Phase 7.1 - UIScrollPanel

New scrollable container widget with:
- Vertical and horizontal scrolling (configurable)
- Mouse wheel support with smooth scrolling
- Drag-to-scroll functionality (drag content or scrollbar)
- Interactive scrollbar with proportional thumb
- Automatic content size calculation
- Visibility culling for performance
- Full styling support (colors, borders, scrollbar)

Files added:
- modules/UIModule/Widgets/UIScrollPanel.h
- modules/UIModule/Widgets/UIScrollPanel.cpp
- modules/UIModule/Core/UIContext.h (added mouseWheelDelta)
- modules/UIModule/UIModule.cpp (mouse wheel event routing)

## Phase 7.2 - Tooltips

Smart tooltip system with:
- Hover delay (500ms default)
- Automatic positioning with edge avoidance
- Semi-transparent background with border
- Per-widget tooltip text via JSON
- Tooltip property on all UIWidget types
- Renders on top of all UI elements

Files added:
- modules/UIModule/Core/UITooltip.h
- modules/UIModule/Core/UITooltip.cpp
- modules/UIModule/Core/UIWidget.h (added tooltip property)
- modules/UIModule/Core/UITree.cpp (tooltip parsing)

## Tests

Added comprehensive visual tests:
- test_28_ui_scroll.cpp - ScrollPanel with 35+ items
- test_29_ui_advanced.cpp - Tooltips on various widgets
- assets/ui/test_scroll.json - ScrollPanel layout
- assets/ui/test_tooltips.json - Tooltips layout

## Documentation

- docs/UI_MODULE_PHASE7_COMPLETE.md - Complete Phase 7 docs
- docs/PROMPT_UI_MODULE_PHASE6.md - Phase 6 & 7 prompt
- Updated CMakeLists.txt for new files and tests

## UIModule Status

UIModule is now feature-complete with:
 9 widget types (Panel, Label, Button, Image, Slider, Checkbox,
   ProgressBar, TextInput, ScrollPanel)
 Flexible layout system (vertical, horizontal, stack, absolute)
 Theme and style system
 Complete event system
 Tooltips with smart positioning
 Hot-reload support
 Comprehensive tests (Phases 1-7)

🚀 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 07:13:13 +08:00

193 lines
5.7 KiB
C++

#pragma once
#include <string>
#include <unordered_map>
#include <cstdint>
#include <memory>
#include <vector>
namespace grove {
class IDataNode;
/**
* @brief Style properties that can be applied to widgets
*
* Contains all visual properties like colors, sizes, padding, etc.
* Can be partially defined (unset values use parent/default).
*/
struct WidgetStyle {
// Colors (0 = not set)
uint32_t bgColor = 0;
uint32_t textColor = 0;
uint32_t borderColor = 0;
uint32_t accentColor = 0; // For fills, checks, etc.
// Sizes (-1 = not set)
float fontSize = -1.0f;
float padding = -1.0f;
float margin = -1.0f;
float borderWidth = -1.0f;
float borderRadius = -1.0f;
// Specific widget properties
float handleSize = -1.0f; // For sliders
float boxSize = -1.0f; // For checkboxes
float spacing = -1.0f; // For checkboxes (text spacing)
/**
* @brief Check if a property is set
*/
bool hasBgColor() const { return bgColor != 0; }
bool hasTextColor() const { return textColor != 0; }
bool hasBorderColor() const { return borderColor != 0; }
bool hasAccentColor() const { return accentColor != 0; }
bool hasFontSize() const { return fontSize >= 0.0f; }
bool hasPadding() const { return padding >= 0.0f; }
bool hasMargin() const { return margin >= 0.0f; }
bool hasBorderWidth() const { return borderWidth >= 0.0f; }
bool hasBorderRadius() const { return borderRadius >= 0.0f; }
/**
* @brief Merge another style on top of this one
* Only overwrites properties that are set in the other style
*/
void merge(const WidgetStyle& other);
/**
* @brief Parse from JSON data node
*/
void parseFromJson(const IDataNode& styleData);
};
/**
* @brief Theme definition with named colors and widget styles
*
* A theme contains:
* - Named color palette (e.g., "primary", "secondary", "background")
* - Default styles per widget type (e.g., "button", "panel")
* - Style variants (e.g., "button:hover", "button:pressed")
*/
class UITheme {
public:
UITheme() = default;
UITheme(const std::string& name) : m_name(name) {}
/**
* @brief Get theme name
*/
const std::string& getName() const { return m_name; }
/**
* @brief Define a named color in the palette
*/
void setColor(const std::string& name, uint32_t color);
/**
* @brief Get a named color from the palette
* @return Color value, or 0 if not found
*/
uint32_t getColor(const std::string& name) const;
/**
* @brief Resolve color references (e.g., "$primary" -> actual color)
*/
uint32_t resolveColor(const std::string& colorRef) const;
/**
* @brief Set style for a widget type
* @param widgetType Type of widget (e.g., "button", "panel")
* @param style Style to apply
*/
void setWidgetStyle(const std::string& widgetType, const WidgetStyle& style);
/**
* @brief Set style for a widget variant
* @param widgetType Type of widget (e.g., "button")
* @param variant Variant name (e.g., "hover", "pressed")
* @param style Style to apply
*/
void setWidgetVariantStyle(const std::string& widgetType, const std::string& variant, const WidgetStyle& style);
/**
* @brief Get style for a widget type
* @return Style, or empty style if not found
*/
const WidgetStyle& getWidgetStyle(const std::string& widgetType) const;
/**
* @brief Get style for a widget variant
* @return Style, or empty style if not found
*/
const WidgetStyle& getWidgetVariantStyle(const std::string& widgetType, const std::string& variant) const;
/**
* @brief Load theme from JSON
*/
bool loadFromJson(const IDataNode& themeData);
private:
std::string m_name;
std::unordered_map<std::string, uint32_t> m_colors;
std::unordered_map<std::string, WidgetStyle> m_widgetStyles;
std::unordered_map<std::string, WidgetStyle> m_variantStyles; // Key: "widgetType:variant"
static WidgetStyle s_emptyStyle;
/**
* @brief Make variant key from widget type and variant name
*/
static std::string makeVariantKey(const std::string& widgetType, const std::string& variant);
};
/**
* @brief Style manager - holds current theme and provides style resolution
*/
class UIStyleManager {
public:
UIStyleManager() = default;
/**
* @brief Set the current theme
*/
void setTheme(std::unique_ptr<UITheme> theme);
/**
* @brief Get the current theme
*/
UITheme* getTheme() const { return m_currentTheme.get(); }
/**
* @brief Resolve a complete style for a widget
*
* Resolution order:
* 1. Widget inline style
* 2. Theme widget type style
* 3. Default style
*
* @param widgetType Type of widget
* @param inlineStyle Style defined inline in JSON
* @return Resolved style
*/
WidgetStyle resolveStyle(const std::string& widgetType, const WidgetStyle& inlineStyle) const;
/**
* @brief Resolve a variant style
* @param widgetType Type of widget
* @param variant Variant name
* @param inlineStyle Inline variant style
* @return Resolved style
*/
WidgetStyle resolveVariantStyle(const std::string& widgetType, const std::string& variant, const WidgetStyle& inlineStyle) const;
private:
std::unique_ptr<UITheme> m_currentTheme;
/**
* @brief Get default style for a widget type
*/
WidgetStyle getDefaultStyle(const std::string& widgetType) const;
};
} // namespace grove