feat: Implement comprehensive mouse event system
Implements multiple mouse event improvements for UI elements: - Mouse enter/exit events (#140): on_enter, on_exit callbacks and hovered property for all UIDrawable types (Frame, Caption, Sprite, Grid) - Headless click events (#111): Track simulated mouse position for automation testing in headless mode - Mouse move events (#141): on_move callback fires continuously while mouse is within element bounds - Grid cell events (#142): on_cell_enter, on_cell_exit, on_cell_click callbacks with cell coordinates (x, y), plus hovered_cell property Includes comprehensive tests for all new functionality. Closes #140, closes #111, closes #141, closes #142 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6d5a5e9e16
commit
6c496b8732
14 changed files with 1353 additions and 27 deletions
|
|
@ -2,7 +2,11 @@
|
|||
#include "ActionCode.h"
|
||||
#include "Resources.h"
|
||||
#include "PyCallable.h"
|
||||
#include "UIFrame.h"
|
||||
#include "UIGrid.h"
|
||||
#include "McRFPy_Automation.h" // #111 - For simulated mouse position
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
PyScene::PyScene(GameEngine* g) : Scene(g)
|
||||
{
|
||||
|
|
@ -22,15 +26,18 @@ void PyScene::update()
|
|||
|
||||
void PyScene::do_mouse_input(std::string button, std::string type)
|
||||
{
|
||||
// In headless mode, mouse input is not available
|
||||
sf::Vector2f mousepos;
|
||||
|
||||
// #111 - In headless mode, use simulated mouse position
|
||||
if (game->isHeadless()) {
|
||||
return;
|
||||
sf::Vector2i simPos = McRFPy_Automation::getSimulatedMousePosition();
|
||||
mousepos = sf::Vector2f(static_cast<float>(simPos.x), static_cast<float>(simPos.y));
|
||||
} else {
|
||||
auto unscaledmousepos = sf::Mouse::getPosition(game->getWindow());
|
||||
// Convert window coordinates to game coordinates using the viewport
|
||||
mousepos = game->windowToGameCoords(sf::Vector2f(unscaledmousepos));
|
||||
}
|
||||
|
||||
auto unscaledmousepos = sf::Mouse::getPosition(game->getWindow());
|
||||
// Convert window coordinates to game coordinates using the viewport
|
||||
auto mousepos = game->windowToGameCoords(sf::Vector2f(unscaledmousepos));
|
||||
|
||||
// Only sort if z_index values have changed
|
||||
if (ui_elements_need_sort) {
|
||||
// Sort in ascending order (same as render)
|
||||
|
|
@ -62,6 +69,74 @@ void PyScene::doAction(std::string name, std::string type)
|
|||
}
|
||||
}
|
||||
|
||||
// #140 - Mouse enter/exit tracking
|
||||
void PyScene::do_mouse_hover(int x, int y)
|
||||
{
|
||||
// In headless mode, use the coordinates directly (already in game space)
|
||||
sf::Vector2f mousepos;
|
||||
if (game->isHeadless()) {
|
||||
mousepos = sf::Vector2f(static_cast<float>(x), static_cast<float>(y));
|
||||
} else {
|
||||
// Convert window coordinates to game coordinates using the viewport
|
||||
mousepos = game->windowToGameCoords(sf::Vector2f(static_cast<float>(x), static_cast<float>(y)));
|
||||
}
|
||||
|
||||
// Helper function to process hover for a single drawable and its children
|
||||
std::function<void(UIDrawable*)> processHover = [&](UIDrawable* drawable) {
|
||||
if (!drawable || !drawable->visible) return;
|
||||
|
||||
bool is_inside = drawable->contains_point(mousepos.x, mousepos.y);
|
||||
bool was_hovered = drawable->hovered;
|
||||
|
||||
if (is_inside && !was_hovered) {
|
||||
// Mouse entered
|
||||
drawable->hovered = true;
|
||||
if (drawable->on_enter_callable) {
|
||||
drawable->on_enter_callable->call(mousepos, "enter", "start");
|
||||
}
|
||||
} else if (!is_inside && was_hovered) {
|
||||
// Mouse exited
|
||||
drawable->hovered = false;
|
||||
if (drawable->on_exit_callable) {
|
||||
drawable->on_exit_callable->call(mousepos, "exit", "start");
|
||||
}
|
||||
}
|
||||
|
||||
// #141 - Fire on_move if mouse is inside and has a move callback
|
||||
if (is_inside && drawable->on_move_callable) {
|
||||
drawable->on_move_callable->call(mousepos, "move", "start");
|
||||
}
|
||||
|
||||
// Process children for Frame elements
|
||||
if (drawable->derived_type() == PyObjectsEnum::UIFRAME) {
|
||||
auto frame = static_cast<UIFrame*>(drawable);
|
||||
if (frame->children) {
|
||||
for (auto& child : *frame->children) {
|
||||
processHover(child.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Process children for Grid elements
|
||||
else if (drawable->derived_type() == PyObjectsEnum::UIGRID) {
|
||||
auto grid = static_cast<UIGrid*>(drawable);
|
||||
|
||||
// #142 - Update cell hover tracking for grid
|
||||
grid->updateCellHover(mousepos);
|
||||
|
||||
if (grid->children) {
|
||||
for (auto& child : *grid->children) {
|
||||
processHover(child.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Process all top-level UI elements
|
||||
for (auto& element : *ui_elements) {
|
||||
processHover(element.get());
|
||||
}
|
||||
}
|
||||
|
||||
void PyScene::render()
|
||||
{
|
||||
// #118: Skip rendering if scene is not visible
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue