fix: improve click handling with proper z-order and coordinate transforms
- UIFrame: Fix coordinate transformation (subtract parent pos, not add) - UIFrame: Check children in reverse order (highest z-index first) - UIFrame: Skip invisible elements entirely - PyScene: Sort elements by z-index before checking clicks - PyScene: Stop at first element that handles the click - UIGrid: Implement entity click detection with grid coordinate transform - UIGrid: Check entities in reverse order, return sprite as target Click events now correctly respect z-order (top elements get priority), handle coordinate transforms for nested frames, and support clicking on grid entities. Elements without click handlers are transparent to clicks, allowing elements below to receive them. Note: Click testing requires non-headless mode due to PyScene limitation. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
edfe3ba184
commit
1c7195a748
|
@ -29,26 +29,19 @@ void PyScene::do_mouse_input(std::string button, std::string type)
|
|||
|
||||
auto unscaledmousepos = sf::Mouse::getPosition(game->getWindow());
|
||||
auto mousepos = game->getWindow().mapPixelToCoords(unscaledmousepos);
|
||||
UIDrawable* target;
|
||||
for (auto d: *ui_elements)
|
||||
{
|
||||
target = d->click_at(sf::Vector2f(mousepos));
|
||||
if (target)
|
||||
{
|
||||
/*
|
||||
PyObject* args = Py_BuildValue("(iiss)", (int)mousepos.x, (int)mousepos.y, button.c_str(), type.c_str());
|
||||
PyObject* retval = PyObject_Call(target->click_callable, args, NULL);
|
||||
if (!retval)
|
||||
{
|
||||
std::cout << "click_callable has raised an exception. It's going to STDERR and being dropped:" << std::endl;
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
} else if (retval != Py_None)
|
||||
{
|
||||
std::cout << "click_callable returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
|
||||
}
|
||||
*/
|
||||
|
||||
// Create a sorted copy by z-index (highest first)
|
||||
std::vector<std::shared_ptr<UIDrawable>> sorted_elements(*ui_elements);
|
||||
std::sort(sorted_elements.begin(), sorted_elements.end(),
|
||||
[](const auto& a, const auto& b) { return a->z_index > b->z_index; });
|
||||
|
||||
// Check elements in z-order (top to bottom)
|
||||
for (const auto& element : sorted_elements) {
|
||||
if (!element->visible) continue;
|
||||
|
||||
if (auto target = element->click_at(sf::Vector2f(mousepos))) {
|
||||
target->click_callable->call(mousepos, button, type);
|
||||
return; // Stop after first handler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,18 +11,31 @@
|
|||
|
||||
UIDrawable* UIFrame::click_at(sf::Vector2f point)
|
||||
{
|
||||
for (auto e: *children)
|
||||
{
|
||||
auto p = e->click_at(point + box.getPosition());
|
||||
if (p)
|
||||
return p;
|
||||
// Check bounds first (optimization)
|
||||
float x = box.getPosition().x, y = box.getPosition().y, w = box.getSize().x, h = box.getSize().y;
|
||||
if (point.x < x || point.y < y || point.x >= x+w || point.y >= y+h) {
|
||||
return nullptr;
|
||||
}
|
||||
if (click_callable)
|
||||
{
|
||||
float x = box.getPosition().x, y = box.getPosition().y, w = box.getSize().x, h = box.getSize().y;
|
||||
if (point.x > x && point.y > y && point.x < x+w && point.y < y+h) return this;
|
||||
|
||||
// Transform to local coordinates for children
|
||||
sf::Vector2f localPoint = point - box.getPosition();
|
||||
|
||||
// Check children in reverse order (top to bottom, highest z-index first)
|
||||
for (auto it = children->rbegin(); it != children->rend(); ++it) {
|
||||
auto& child = *it;
|
||||
if (!child->visible) continue;
|
||||
|
||||
if (auto target = child->click_at(localPoint)) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
|
||||
// No child handled it, check if we have a handler
|
||||
if (click_callable) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UIFrame::UIFrame()
|
||||
|
|
|
@ -265,11 +265,62 @@ std::shared_ptr<PyTexture> UIGrid::getTexture()
|
|||
|
||||
UIDrawable* UIGrid::click_at(sf::Vector2f point)
|
||||
{
|
||||
if (click_callable)
|
||||
{
|
||||
if(box.getGlobalBounds().contains(point)) return this;
|
||||
// Check grid bounds first
|
||||
if (!box.getGlobalBounds().contains(point)) {
|
||||
return nullptr;
|
||||
}
|
||||
return NULL;
|
||||
|
||||
// Transform to local coordinates
|
||||
sf::Vector2f localPoint = point - box.getPosition();
|
||||
|
||||
// Get cell dimensions
|
||||
int cell_width = ptex ? ptex->sprite_width : DEFAULT_CELL_WIDTH;
|
||||
int cell_height = ptex ? ptex->sprite_height : DEFAULT_CELL_HEIGHT;
|
||||
|
||||
// Calculate visible area parameters (from render function)
|
||||
float center_x_sq = center_x / cell_width;
|
||||
float center_y_sq = center_y / cell_height;
|
||||
float width_sq = box.getSize().x / (cell_width * zoom);
|
||||
float height_sq = box.getSize().y / (cell_height * zoom);
|
||||
|
||||
int left_spritepixels = center_x - (box.getSize().x / 2.0 / zoom);
|
||||
int top_spritepixels = center_y - (box.getSize().y / 2.0 / zoom);
|
||||
|
||||
// Convert click position to grid coordinates
|
||||
float grid_x = (localPoint.x / zoom + left_spritepixels) / cell_width;
|
||||
float grid_y = (localPoint.y / zoom + top_spritepixels) / cell_height;
|
||||
|
||||
// Check entities in reverse order (assuming they should be checked top to bottom)
|
||||
// Note: entities list is not sorted by z-index currently, but we iterate in reverse
|
||||
// to match the render order assumption
|
||||
if (entities) {
|
||||
for (auto it = entities->rbegin(); it != entities->rend(); ++it) {
|
||||
auto& entity = *it;
|
||||
if (!entity || !entity->sprite.visible) continue;
|
||||
|
||||
// Check if click is within entity's grid cell
|
||||
// Entities occupy a 1x1 grid cell centered on their position
|
||||
float dx = grid_x - entity->position.x;
|
||||
float dy = grid_y - entity->position.y;
|
||||
|
||||
if (dx >= -0.5f && dx < 0.5f && dy >= -0.5f && dy < 0.5f) {
|
||||
// Click is within the entity's cell
|
||||
// Check if entity sprite has a click handler
|
||||
// For now, we return the entity's sprite as the click target
|
||||
// Note: UIEntity doesn't derive from UIDrawable, so we check its sprite
|
||||
if (entity->sprite.click_callable) {
|
||||
return &entity->sprite;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No entity handled it, check if grid itself has handler
|
||||
if (click_callable) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue