feat: Standardize position arguments across all UI classes

- Create PyPositionHelper for consistent position parsing
- Grid.at() now accepts (x,y), ((x,y)), x=x, y=y, pos=(x,y)
- Caption now accepts x,y args in addition to pos
- Grid init fully supports keyword arguments
- Maintain backward compatibility for all formats
- Consistent error messages across classes
This commit is contained in:
John McCardle 2025-07-06 01:06:12 -04:00
commit c48c91e5d7
3 changed files with 223 additions and 88 deletions

View file

@ -1,6 +1,7 @@
#include "UIGrid.h"
#include "GameEngine.h"
#include "McRFPy_API.h"
#include "PyPositionHelper.h"
#include <algorithm>
UIGrid::UIGrid()
@ -281,8 +282,8 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
static const char* keywords[] = {"grid_x", "grid_y", "texture", "pos", "size", "grid_size", NULL};
// First try parsing with keywords
if (kwds && PyArg_ParseTupleAndKeywords(args, kwds, "|iiOOOO", const_cast<char**>(keywords),
&grid_x, &grid_y, &textureObj, &pos, &size, &grid_size_obj)) {
if (PyArg_ParseTupleAndKeywords(args, kwds, "|iiOOOO", const_cast<char**>(keywords),
&grid_x, &grid_y, &textureObj, &pos, &size, &grid_size_obj)) {
// If grid_size is provided, use it to override grid_x and grid_y
if (grid_size_obj && grid_size_obj != Py_None) {
if (PyTuple_Check(grid_size_obj) && PyTuple_Size(grid_size_obj) == 2) {
@ -566,72 +567,17 @@ PyObject* UIGrid::get_texture(PyUIGridObject* self, void* closure) {
PyObject* UIGrid::py_at(PyUIGridObject* self, PyObject* args, PyObject* kwds)
{
static const char* keywords[] = { "x", "y", "pos", nullptr };
int x = -1, y = -1;
PyObject* pos = nullptr;
// Use the standardized position parser
auto result = PyPositionHelper::parse_position_int(args, kwds);
// Try to parse with keywords first
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiO", const_cast<char**>(keywords), &x, &y, &pos)) {
PyErr_Clear(); // Clear the error and try different parsing
// Check if we have a single tuple argument (x, y)
if (PyTuple_Size(args) == 1 && kwds == nullptr) {
PyObject* arg = PyTuple_GetItem(args, 0);
if (PyTuple_Check(arg) && PyTuple_Size(arg) == 2) {
// It's a tuple, extract x and y
PyObject* x_obj = PyTuple_GetItem(arg, 0);
PyObject* y_obj = PyTuple_GetItem(arg, 1);
if (PyLong_Check(x_obj) && PyLong_Check(y_obj)) {
x = PyLong_AsLong(x_obj);
y = PyLong_AsLong(y_obj);
} else {
PyErr_SetString(PyExc_TypeError, "Tuple elements must be integers");
return NULL;
}
} else {
PyErr_SetString(PyExc_TypeError, "UIGrid.at accepts: (x, y), x, y, x=x, y=y, or pos=(x,y)");
return NULL;
}
} else if (PyTuple_Size(args) == 2 && kwds == nullptr) {
// Two positional arguments
if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
PyErr_SetString(PyExc_TypeError, "Arguments must be integers");
return NULL;
}
} else {
PyErr_SetString(PyExc_TypeError, "UIGrid.at accepts: (x, y), x, y, x=x, y=y, or pos=(x,y)");
return NULL;
}
}
// Handle pos keyword argument
if (pos != nullptr) {
if (x != -1 || y != -1) {
PyErr_SetString(PyExc_TypeError, "Cannot specify both pos and x/y arguments");
return NULL;
}
if (PyTuple_Check(pos) && PyTuple_Size(pos) == 2) {
PyObject* x_obj = PyTuple_GetItem(pos, 0);
PyObject* y_obj = PyTuple_GetItem(pos, 1);
if (PyLong_Check(x_obj) && PyLong_Check(y_obj)) {
x = PyLong_AsLong(x_obj);
y = PyLong_AsLong(y_obj);
} else {
PyErr_SetString(PyExc_TypeError, "pos tuple elements must be integers");
return NULL;
}
} else {
PyErr_SetString(PyExc_TypeError, "pos must be a tuple of two integers");
return NULL;
}
}
// Validate we have both x and y
if (x == -1 || y == -1) {
PyErr_SetString(PyExc_TypeError, "UIGrid.at requires both x and y coordinates");
if (!result.has_position) {
PyPositionHelper::set_position_int_error();
return NULL;
}
int x = result.x;
int y = result.y;
// Range validation
if (x < 0 || x >= self->data->grid_x) {
PyErr_SetString(PyExc_ValueError, "x value out of range (0, Grid.grid_x)");