#include "UIContext.h" #include "UIWidget.h" #include "../Widgets/UIButton.h" #include "../Widgets/UISlider.h" #include "../Widgets/UICheckbox.h" #include namespace grove { /** * @brief Perform hit testing to find the topmost widget at a point * * Recursively searches the widget tree from front to back (reverse order) * to find the topmost visible widget containing the point. * * @param widget Root widget to search from * @param x Point X coordinate * @param y Point Y coordinate * @return Topmost widget at point, or nullptr */ UIWidget* hitTest(UIWidget* widget, float x, float y) { if (!widget || !widget->visible) { return nullptr; } // Check children first (front to back = reverse order for hit testing) for (auto it = widget->children.rbegin(); it != widget->children.rend(); ++it) { UIWidget* hit = hitTest(it->get(), x, y); if (hit) { return hit; } } // Check this widget if it's interactive std::string type = widget->getType(); if (type == "button") { UIButton* button = static_cast(widget); if (button->containsPoint(x, y)) { return widget; } } else if (type == "slider") { UISlider* slider = static_cast(widget); if (slider->containsPoint(x, y)) { return widget; } } else if (type == "checkbox") { UICheckbox* checkbox = static_cast(widget); if (checkbox->containsPoint(x, y)) { return widget; } } return nullptr; } /** * @brief Update hover state for all widgets in tree * * Calls onMouseEnter/onMouseLeave for buttons based on hover state. * * @param widget Root widget * @param ctx UI context with hover state * @param prevHoveredId Previous frame's hovered widget ID */ void updateHoverState(UIWidget* widget, UIContext& ctx, const std::string& prevHoveredId) { if (!widget) return; // Check if this widget's hover state changed if (widget->getType() == "button") { UIButton* button = static_cast(widget); bool wasHovered = (widget->id == prevHoveredId); bool isHovered = (widget->id == ctx.hoveredWidgetId); if (isHovered && !wasHovered) { button->onMouseEnter(); } else if (!isHovered && wasHovered) { button->onMouseLeave(); } } // Recurse to children for (auto& child : widget->children) { updateHoverState(child.get(), ctx, prevHoveredId); } } /** * @brief Dispatch mouse button event to widget tree * * Finds the widget under the mouse and delivers the event. * * @param widget Root widget * @param ctx UI context * @param button Mouse button (0 = left, 1 = right, 2 = middle) * @param pressed true if button pressed, false if released * @return Widget that handled the event (for action publishing), or nullptr */ UIWidget* dispatchMouseButton(UIWidget* widget, UIContext& ctx, int button, bool pressed) { // Hit test to find target widget UIWidget* target = hitTest(widget, ctx.mouseX, ctx.mouseY); if (!target) { return nullptr; } // Dispatch to appropriate widget type std::string type = target->getType(); bool handled = false; if (type == "button") { UIButton* btn = static_cast(target); handled = btn->onMouseButton(button, pressed, ctx.mouseX, ctx.mouseY); if (handled && !pressed && !btn->onClick.empty()) { return target; // Return for action publishing } } else if (type == "slider") { UISlider* slider = static_cast(target); handled = slider->onMouseButton(button, pressed, ctx.mouseX, ctx.mouseY); if (handled) { return target; // Return for value_changed publishing } } else if (type == "checkbox") { UICheckbox* checkbox = static_cast(target); handled = checkbox->onMouseButton(button, pressed, ctx.mouseX, ctx.mouseY); if (handled) { return target; // Return for value_changed publishing } } return handled ? target : nullptr; } } // namespace grove