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
parent c48c91e5d7
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,45 +71,51 @@ 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;
// 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))
{
// 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;
}
}
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, "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))
const_cast<char**>(alt_keywords), &pos, &texture, &sprite_index, &grid))
{
return -1;
}
// Handle position - default to (0, 0) if not provided
PyVectorObject* pos_result = nullptr;
// Parse position
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__");
PyVectorObject* vec = PyVector::from_arg(pos);
if (!vec) {
PyErr_SetString(PyExc_TypeError, "pos must be a Vector or tuple (x, y)");
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_result) {
PyErr_SetString(PyExc_RuntimeError, "Failed to create default position vector");
return -1;
x = vec->data.x;
y = vec->data.y;
}
}
@ -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;

View File

@ -6,6 +6,7 @@
#include "UISprite.h"
#include "UIGrid.h"
#include "McRFPy_API.h"
#include "PyPositionHelper.h"
UIDrawable* UIFrame::click_at(sf::Vector2f point)
{
@ -301,34 +302,51 @@ PyObject* UIFrame::repr(PyUIFrameObject* self)
int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds)
{
//std::cout << "Init called\n";
const char* keywords[] = { "x", "y", "w", "h", "fill_color", "outline_color", "outline", "children", "click", nullptr };
// Parse position using the standardized helper
auto pos_result = PyPositionHelper::parse_position(args, kwds);
const char* keywords[] = { "x", "y", "w", "h", "fill_color", "outline_color", "outline", "children", "click", "pos", nullptr };
float x = 0.0f, y = 0.0f, w = 0.0f, h=0.0f, outline=0.0f;
PyObject* fill_color = 0;
PyObject* outline_color = 0;
PyObject* children_arg = 0;
PyObject* click_handler = 0;
PyObject* pos_obj = 0;
// First try to parse as (x, y, w, h, ...)
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffffOOfOO", const_cast<char**>(keywords), &x, &y, &w, &h, &fill_color, &outline_color, &outline, &children_arg, &click_handler))
// Try to parse all arguments including x, y
if (PyArg_ParseTupleAndKeywords(args, kwds, "|ffffOOfOOO", const_cast<char**>(keywords),
&x, &y, &w, &h, &fill_color, &outline_color, &outline, &children_arg, &click_handler, &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;
}
}
else
{
PyErr_Clear(); // Clear the error
// Try to parse as ((x,y), w, h, ...) or (Vector, w, h, ...)
PyObject* pos_obj = nullptr;
const char* alt_keywords[] = { "pos", "w", "h", "fill_color", "outline_color", "outline", "children", "click", nullptr };
PyObject* pos_arg = nullptr;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OffOOfOO", const_cast<char**>(alt_keywords),
&pos_obj, &w, &h, &fill_color, &outline_color, &outline, &children_arg, &click_handler))
&pos_arg, &w, &h, &fill_color, &outline_color, &outline, &children_arg, &click_handler))
{
return -1;
}
// Convert position argument to x, y if provided
if (pos_obj && pos_obj != Py_None) {
PyVectorObject* vec = PyVector::from_arg(pos_obj);
if (pos_arg && pos_arg != Py_None) {
PyVectorObject* vec = PyVector::from_arg(pos_arg);
if (!vec) {
PyErr_SetString(PyExc_TypeError, "First argument must be a tuple (x, y) or Vector when not providing x, y separately");
PyErr_SetString(PyExc_TypeError, "pos must be a Vector or tuple (x, y)");
return -1;
}
x = vec->data.x;

View File

@ -1,6 +1,7 @@
#include "UISprite.h"
#include "GameEngine.h"
#include "PyVector.h"
#include "PyPositionHelper.h"
UIDrawable* UISprite::click_at(sf::Vector2f point)
{
@ -297,34 +298,47 @@ PyObject* UISprite::repr(PyUISpriteObject* self)
int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds)
{
//std::cout << "Init called\n";
static const char* keywords[] = { "x", "y", "texture", "sprite_index", "scale", "click", nullptr };
static const char* keywords[] = { "x", "y", "texture", "sprite_index", "scale", "click", "pos", nullptr };
float x = 0.0f, y = 0.0f, scale = 1.0f;
int sprite_index = 0;
PyObject* texture = NULL;
PyObject* click_handler = NULL;
PyObject* pos_obj = NULL;
// First try to parse as (x, y, texture, ...)
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffOifO",
const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &scale, &click_handler))
// Try to parse all arguments with keywords
if (PyArg_ParseTupleAndKeywords(args, kwds, "|ffOifOO",
const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &scale, &click_handler, &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;
}
}
else
{
PyErr_Clear(); // Clear the error
// Try to parse as ((x,y), texture, ...) or (Vector, texture, ...)
PyObject* pos_obj = nullptr;
// Try alternative: first arg is pos tuple/Vector
const char* alt_keywords[] = { "pos", "texture", "sprite_index", "scale", "click", nullptr };
PyObject* pos = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOifO", const_cast<char**>(alt_keywords),
&pos_obj, &texture, &sprite_index, &scale, &click_handler))
&pos, &texture, &sprite_index, &scale, &click_handler))
{
return -1;
}
// Convert position argument to x, y
if (pos_obj) {
PyVectorObject* vec = PyVector::from_arg(pos_obj);
if (pos && pos != Py_None) {
PyVectorObject* vec = PyVector::from_arg(pos);
if (!vec) {
PyErr_SetString(PyExc_TypeError, "First argument must be a tuple (x, y) or Vector when not providing x, y separately");
PyErr_SetString(PyExc_TypeError, "pos must be a Vector or tuple (x, y)");
return -1;
}
x = vec->data.x;