GroveEngine/docs/UI_MODULE_PHASE3_COMPLETE.md
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

10 KiB

UIModule Phase 3: Interaction & Events - Implementation Complete

Date

2025-11-28

Summary

Successfully implemented the Interaction & Events system (Phase 3) for UIModule in GroveEngine. This adds interactive buttons, mouse hit testing, and event publishing capabilities.

Components Implemented

1. Widgets/UIButton.h/cpp

New interactive button widget with full state management:

States

  • Normal: Default resting state
  • Hover: Mouse is over the button
  • Pressed: Mouse button is down on the button
  • Disabled: Button is non-interactive

Features

  • Per-state styling (bgColor, textColor, borderColor, etc.)
  • Hit testing (containsPoint())
  • Event handlers (onMouseButton(), onMouseEnter(), onMouseLeave())
  • Configurable onClick action
  • Enable/disable functionality

Rendering

  • Background rectangle with state-specific color
  • Text rendering (centered approximation)
  • Border support (placeholder)

2. Core/UIContext.cpp

Hit testing and event dispatch implementation:

Functions

  • hitTest(): Recursive search to find topmost widget at point

    • Front-to-back traversal (reverse children order)
    • Only interactive widgets (buttons) are considered
    • Returns topmost hit widget
  • updateHoverState(): Manages hover transitions

    • Calls onMouseEnter() when hover starts
    • Calls onMouseLeave() when hover ends
    • Traverses entire widget tree
  • dispatchMouseButton(): Delivers mouse clicks

    • Hit tests to find target
    • Dispatches to button's onMouseButton()
    • Returns clicked widget for action publishing

3. UIModule.cpp Updates

Enhanced updateUI() with full event system:

Input Processing

  • Subscribes to input:mouse:move, input:mouse:button, input:keyboard
  • Updates UIContext with mouse position and button states
  • Per-frame state tracking (mousePressed, mouseReleased)

Interaction Loop

  1. Hit Testing: Find widget under mouse cursor
  2. Hover State: Update hover state and call widget callbacks
  3. Event Publishing: Publish ui:hover on state change
  4. Mouse Events: Handle clicks and publish events
  5. Widget Update: Call update() on all widgets

Events Published

  • ui:hover: {widgetId, enter: bool}

    • Published when hover state changes
    • enter: true when entering, false when leaving
  • ui:click: {widgetId, x, y}

    • Published on successful button click
    • Includes mouse coordinates
  • ui:action: {action, widgetId}

    • Published when button's onClick is triggered
    • Example: {action: "game:start", widgetId: "btn_play"}
    • Logged to console for debugging

4. UITree.cpp - Button Factory

JSON parsing for button configuration:

Supported Properties

{
  "type": "button",
  "text": "Click Me",
  "onClick": "game:start",
  "enabled": true,
  "style": {
    "fontSize": 18,
    "normal": { "bgColor": "0x444444FF", "textColor": "0xFFFFFFFF" },
    "hover": { "bgColor": "0x666666FF", "textColor": "0xFFFFFFFF" },
    "pressed": { "bgColor": "0x333333FF", "textColor": "0xFFFFFFFF" },
    "disabled": { "bgColor": "0x222222FF", "textColor": "0x666666FF" }
  }
}

Parsing

  • All four states (normal, hover, pressed, disabled)
  • Hex color strings → uint32_t conversion
  • Font size configuration
  • Enable/disable flag

JSON Configuration Examples

Simple Button

{
  "type": "button",
  "id": "btn_play",
  "text": "Play",
  "width": 200,
  "height": 50,
  "onClick": "game:start"
}

Styled Button with All States

{
  "type": "button",
  "id": "btn_quit",
  "text": "Quit",
  "width": 200,
  "height": 50,
  "onClick": "app:quit",
  "style": {
    "fontSize": 18,
    "normal": {
      "bgColor": "0xe74c3cFF",
      "textColor": "0xFFFFFFFF"
    },
    "hover": {
      "bgColor": "0xec7063FF",
      "textColor": "0xFFFFFFFF"
    },
    "pressed": {
      "bgColor": "0xc0392bFF",
      "textColor": "0xFFFFFFFF"
    }
  }
}

Disabled Button

{
  "type": "button",
  "id": "btn_disabled",
  "text": "Disabled",
  "enabled": false,
  "style": {
    "disabled": {
      "bgColor": "0x34495eFF",
      "textColor": "0x7f8c8dFF"
    }
  }
}

Test Files

Visual Test

File: tests/visual/test_26_ui_buttons.cpp

Features Tested

  • Button hover effects (color changes on mouse over)
  • Button press effects (darker color on click)
  • Event publishing (console output for all events)
  • Disabled buttons (no interaction)
  • Action handling (quit button exits app)

Test Layout

JSON: assets/ui/test_buttons.json

  • 3 interactive buttons (Play, Options, Quit)
  • 1 disabled button
  • Color-coded for visual feedback
  • Full state styling for each button

User Interaction

  • Move mouse over buttons → Hover events
  • Click buttons → Click + Action events
  • Click "Quit" → App exits
  • Disabled button → No interaction

Build & Run:

cmake -DGROVE_BUILD_UI_MODULE=ON -DGROVE_BUILD_BGFX_RENDERER=ON -B build-bgfx
cmake --build build-bgfx --target test_26_ui_buttons -j4
cd build-bgfx/tests
./test_26_ui_buttons

Event System Architecture

Input Flow

SDL Events → UIModule::processInput() → UIContext state
           → UIModule::updateUI() → Hit testing
           → Button event handlers → IIO publish

Event Topics

Subscribed (Input)

Topic Data Description
input:mouse:move {x, y} Mouse position update
input:mouse:button {button, pressed, x, y} Mouse click/release
input:keyboard {keyCode, char} Keyboard input

Published (Output)

Topic Data Description
ui:hover {widgetId, enter: bool} Hover state change
ui:click {widgetId, x, y} Button clicked
ui:action {action, widgetId} Button action triggered

Event Flow Example

1. User moves mouse → SDL_MOUSEMOTION
2. Test forwards to IIO → input:mouse:move
3. UIModule receives → Updates UIContext.mouseX/mouseY
4. Hit testing finds button → hoveredWidgetId = "btn_play"
5. updateHoverState() → btn_play.onMouseEnter()
6. Publish → ui:hover {widgetId: "btn_play", enter: true}

7. User clicks → SDL_MOUSEBUTTONDOWN
8. Test forwards → input:mouse:button {pressed: true}
9. UIModule → dispatchMouseButton()
10. Button → onMouseButton() returns true
11. Publish → ui:click {widgetId: "btn_play", x: 350, y: 200}

12. User releases → SDL_MOUSEBUTTONUP
13. dispatchMouseButton() again
14. Button still hovered → Click complete!
15. Publish → ui:action {action: "game:start", widgetId: "btn_play"}
16. Console log: "Button 'btn_play' clicked, action: game:start"

Build Changes

CMakeLists.txt Updates

  1. modules/UIModule/CMakeLists.txt:

    • Added Core/UIContext.cpp
    • Added Widgets/UIButton.cpp
  2. tests/CMakeLists.txt:

    • Added test_26_ui_buttons target

Dependencies

  • No new external dependencies
  • Uses existing UIRenderer for drawing
  • Uses existing IIO for events

Files Created (4)

  1. modules/UIModule/Widgets/UIButton.h
  2. modules/UIModule/Widgets/UIButton.cpp
  3. modules/UIModule/Core/UIContext.cpp
  4. assets/ui/test_buttons.json
  5. tests/visual/test_26_ui_buttons.cpp
  6. docs/UI_MODULE_PHASE3_COMPLETE.md

Files Modified (4)

  1. modules/UIModule/UIModule.cpp - Event system in updateUI()
  2. modules/UIModule/Core/UITree.cpp - Button factory registration
  3. modules/UIModule/CMakeLists.txt - Added new sources
  4. tests/CMakeLists.txt - Added test target

Verification

Compilation

All code compiles without errors or warnings UIModule builds successfully with button support Test executable builds and links

Code Quality

Follows GroveEngine coding conventions Proper state management (Normal/Hover/Pressed/Disabled) Event-driven architecture (IIO pub/sub) Recursive hit testing (correct front-to-back order) Clean separation: rendering vs. interaction logic

Known Limitations

Text Rendering

  • No text centering: UIRenderer doesn't support centered text alignment yet
  • Approximation: Text position calculated but not truly centered
  • Future: Needs text measurement API for proper centering

Border Rendering

  • Placeholder: Border properties exist but not rendered
  • Future: UIRenderer needs border/outline support

Focus Management

  • Not implemented: Tab navigation not yet supported
  • No visual focus: Focus indicator not implemented
  • Phase 3.5: Can be added later without breaking changes

Design Decisions

Hit Testing

  • Front-to-back: Uses reverse children order for correct z-order
  • Type-based: Only certain widgets (buttons) are hit-testable
  • Recursive: Searches entire tree for deepest match

Event Publishing

  • Separate events: ui:click and ui:action are distinct
    • ui:click: Low-level mouse event
    • ui:action: High-level semantic action
  • Logging: Actions logged to console for debugging

State Management

  • Per-frame reset: beginFrame() clears transient state
  • Persistent hover: Hover state persists across frames
  • Click detection: Requires press AND release while hovering

Performance Notes

  • Hit testing: O(n) where n = number of visible widgets
  • Per-frame: Hit testing runs every frame (acceptable for UI)
  • Early exit: Stops at first hit (front-to-back traversal)
  • Typical UI: < 100 widgets, negligible overhead

Next Steps: Phase 4

Phase 4 will add more interactive widgets:

  • UIImage: Display textures
  • UISlider: Draggable value input
  • UICheckbox: Boolean toggle
  • UIProgressBar: Read-only progress display

Phase 3 Status: COMPLETE

All Phase 3 objectives achieved:

  • UIButton widget with state management
  • Hit testing (point → widget lookup)
  • Mouse event handling (hover, click, press)
  • Event publishing (ui:hover, ui:click, ui:action)
  • Enabled/disabled button states
  • JSON configuration with per-state styling
  • Visual test with interactive demo
  • Event logging and debugging
  • Full integration with Phase 2 layout system

The interaction system is fully functional and ready for use!