# 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 ```json { "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 ```json { "type": "button", "id": "btn_play", "text": "Play", "width": 200, "height": 50, "onClick": "game:start" } ``` ### Styled Button with All States ```json { "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 ```json { "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**: ```bash 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!