Simplify on_enter/on_exit callbacks to position-only signature

BREAKING CHANGE: Hover callbacks now take only (pos) instead of (pos, button, action)

- Add PyHoverCallable class for on_enter/on_exit/on_move callbacks (position-only)
- Add PyCellHoverCallable class for on_cell_enter/on_cell_exit callbacks
- Change UIDrawable member types from PyClickCallable to PyHoverCallable
- Update PyScene::do_mouse_hover() to call hover callbacks with only position
- Add tryCallPythonMethod overload for position-only subclass method calls
- Update UIGrid::fireCellEnter/fireCellExit to use position-only signature
- Update all tests for new callback signatures

New callback signatures:
| Callback       | Old                      | New        |
|----------------|--------------------------|------------|
| on_enter       | (pos, button, action)    | (pos)      |
| on_exit        | (pos, button, action)    | (pos)      |
| on_move        | (pos, button, action)    | (pos)      |
| on_cell_enter  | (cell_pos, button, action)| (cell_pos)|
| on_cell_exit   | (cell_pos, button, action)| (cell_pos)|
| on_click       | unchanged                | unchanged  |
| on_cell_click  | unchanged                | unchanged  |

closes #230

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John McCardle 2026-01-28 17:36:02 -05:00
commit 2daebc84b5
12 changed files with 598 additions and 71 deletions

View file

@ -186,3 +186,123 @@ void PyKeyCallable::call(std::string key, std::string action)
std::cout << "KeyCallable returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
}
}
// #230 - PyHoverCallable implementation (position-only for on_enter/on_exit/on_move)
PyHoverCallable::PyHoverCallable(PyObject* _target)
: PyCallable(_target)
{}
PyHoverCallable::PyHoverCallable()
: PyCallable(Py_None)
{}
void PyHoverCallable::call(sf::Vector2f mousepos)
{
if (target == Py_None || target == NULL) return;
// Create a Vector object for the position
PyObject* vector_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
if (!vector_type) {
std::cerr << "Failed to get Vector type for hover callback" << std::endl;
PyErr_Print();
PyErr_Clear();
return;
}
PyObject* pos = PyObject_CallFunction(vector_type, "ff", mousepos.x, mousepos.y);
Py_DECREF(vector_type);
if (!pos) {
std::cerr << "Failed to create Vector object for hover callback" << std::endl;
PyErr_Print();
PyErr_Clear();
return;
}
// #230 - Hover callbacks take only (pos), not (pos, button, action)
PyObject* args = Py_BuildValue("(O)", pos);
Py_DECREF(pos);
PyObject* retval = PyCallable::call(args, NULL);
Py_DECREF(args);
if (!retval)
{
std::cerr << "Hover callback raised an exception:" << std::endl;
PyErr_Print();
PyErr_Clear();
// Check if we should exit on exception
if (McRFPy_API::game && McRFPy_API::game->getConfig().exit_on_exception) {
McRFPy_API::signalPythonException();
}
} else if (retval != Py_None)
{
std::cout << "HoverCallable returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
Py_DECREF(retval);
} else {
Py_DECREF(retval);
}
}
PyObject* PyHoverCallable::borrow()
{
return target;
}
// #230 - PyCellHoverCallable implementation (cell position-only for on_cell_enter/on_cell_exit)
PyCellHoverCallable::PyCellHoverCallable(PyObject* _target)
: PyCallable(_target)
{}
PyCellHoverCallable::PyCellHoverCallable()
: PyCallable(Py_None)
{}
void PyCellHoverCallable::call(sf::Vector2i cellpos)
{
if (target == Py_None || target == NULL) return;
// Create a Vector object for the cell position
PyObject* vector_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
if (!vector_type) {
std::cerr << "Failed to get Vector type for cell hover callback" << std::endl;
PyErr_Print();
PyErr_Clear();
return;
}
PyObject* pos = PyObject_CallFunction(vector_type, "ii", cellpos.x, cellpos.y);
Py_DECREF(vector_type);
if (!pos) {
std::cerr << "Failed to create Vector object for cell hover callback" << std::endl;
PyErr_Print();
PyErr_Clear();
return;
}
// #230 - Cell hover callbacks take only (cell_pos), not (cell_pos, button, action)
PyObject* args = Py_BuildValue("(O)", pos);
Py_DECREF(pos);
PyObject* retval = PyCallable::call(args, NULL);
Py_DECREF(args);
if (!retval)
{
std::cerr << "Cell hover callback raised an exception:" << std::endl;
PyErr_Print();
PyErr_Clear();
// Check if we should exit on exception
if (McRFPy_API::game && McRFPy_API::game->getConfig().exit_on_exception) {
McRFPy_API::signalPythonException();
}
} else if (retval != Py_None)
{
std::cout << "CellHoverCallable returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
Py_DECREF(retval);
} else {
Py_DECREF(retval);
}
}
PyObject* PyCellHoverCallable::borrow()
{
return target;
}