feat: Complete position argument standardization for all UI classes

- Frame and Sprite now support pos keyword override
- Entity now accepts x,y arguments (was pos-only before)
- All UI classes now consistently support:
  - (x, y) positional
  - ((x, y)) tuple
  - x=x, y=y keywords
  - pos=(x,y) keyword
  - pos=Vector keyword
- Improves API consistency and flexibility
This commit is contained in:
John McCardle 2025-07-06 01:14:45 -04:00
commit 75f75d250f
3 changed files with 94 additions and 57 deletions

View file

@ -4,6 +4,7 @@
#include <algorithm>
#include "PyObjectUtils.h"
#include "PyVector.h"
#include "PyPositionHelper.h"
UIEntity::UIEntity()
@ -70,46 +71,52 @@ PyObject* UIEntity::index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored))
}
int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
//static const char* keywords[] = { "x", "y", "texture", "sprite_index", "grid", nullptr };
//float x = 0.0f, y = 0.0f, scale = 1.0f;
static const char* keywords[] = { "pos", "texture", "sprite_index", "grid", nullptr };
PyObject* pos = NULL; // Must initialize to NULL for optional arguments
float scale = 1.0f;
int sprite_index = 0; // Default to sprite index 0 instead of -1
static const char* keywords[] = { "x", "y", "texture", "sprite_index", "grid", "pos", nullptr };
float x = 0.0f, y = 0.0f;
int sprite_index = 0; // Default to sprite index 0
PyObject* texture = NULL;
PyObject* grid = NULL;
PyObject* pos_obj = NULL;
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O",
// const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &grid))
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOiO",
const_cast<char**>(keywords), &pos, &texture, &sprite_index, &grid))
// Try to parse all arguments with keywords
if (PyArg_ParseTupleAndKeywords(args, kwds, "|ffOiOO",
const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &grid, &pos_obj))
{
return -1;
}
// Handle position - default to (0, 0) if not provided
PyVectorObject* pos_result = nullptr;
if (pos && pos != Py_None) {
pos_result = PyVector::from_arg(pos);
if (!pos_result)
{
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
return -1;
}
} else {
// Create default position (0, 0)
PyObject* vector_class = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
if (vector_class) {
PyObject* pos_obj = PyObject_CallFunction(vector_class, "ff", 0.0f, 0.0f);
Py_DECREF(vector_class);
if (pos_obj) {
pos_result = (PyVectorObject*)pos_obj;
// If pos was provided, it overrides x,y
if (pos_obj && pos_obj != Py_None) {
PyVectorObject* vec = PyVector::from_arg(pos_obj);
if (!vec) {
PyErr_SetString(PyExc_TypeError, "pos must be a Vector or tuple (x, y)");
return -1;
}
x = vec->data.x;
y = vec->data.y;
}
if (!pos_result) {
PyErr_SetString(PyExc_RuntimeError, "Failed to create default position vector");
}
else
{
PyErr_Clear();
// Try alternative: pos as first argument
static const char* alt_keywords[] = { "pos", "texture", "sprite_index", "grid", nullptr };
PyObject* pos = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOiO",
const_cast<char**>(alt_keywords), &pos, &texture, &sprite_index, &grid))
{
return -1;
}
// Parse position
if (pos && pos != Py_None) {
PyVectorObject* vec = PyVector::from_arg(pos);
if (!vec) {
PyErr_SetString(PyExc_TypeError, "pos must be a Vector or tuple (x, y)");
return -1;
}
x = vec->data.x;
y = vec->data.y;
}
}
// check types for texture
@ -155,12 +162,10 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
// Create an empty sprite for testing
self->data->sprite = UISprite();
}
self->data->position = pos_result->data;
// Clean up the position object if we created it
if (!pos || pos == Py_None) {
Py_DECREF(pos_result);
}
// Set position
self->data->position = sf::Vector2f(x, y);
self->data->collision_pos = sf::Vector2i(static_cast<int>(x), static_cast<int>(y));
if (grid != NULL) {
PyUIGridObject* pygrid = (PyUIGridObject*)grid;