From 75f75d250fa8a354a11b0c01c24c461e1e89df7f Mon Sep 17 00:00:00 2001 From: John McCardle Date: Sun, 6 Jul 2025 01:14:45 -0400 Subject: [PATCH] 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 --- src/UIEntity.cpp | 79 +++++++++++++++++++++++++----------------------- src/UIFrame.cpp | 36 ++++++++++++++++------ src/UISprite.cpp | 36 +++++++++++++++------- 3 files changed, 94 insertions(+), 57 deletions(-) diff --git a/src/UIEntity.cpp b/src/UIEntity.cpp index 8f825dd..609c1d6 100644 --- a/src/UIEntity.cpp +++ b/src/UIEntity.cpp @@ -4,6 +4,7 @@ #include #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(keywords), &x, &y, &texture, &sprite_index, &grid)) - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOiO", - const_cast(keywords), &pos, &texture, &sprite_index, &grid)) + // Try to parse all arguments with keywords + if (PyArg_ParseTupleAndKeywords(args, kwds, "|ffOiOO", + const_cast(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(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(x), static_cast(y)); if (grid != NULL) { PyUIGridObject* pygrid = (PyUIGridObject*)grid; diff --git a/src/UIFrame.cpp b/src/UIFrame.cpp index c49aa58..2139a54 100644 --- a/src/UIFrame.cpp +++ b/src/UIFrame.cpp @@ -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(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(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(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; diff --git a/src/UISprite.cpp b/src/UISprite.cpp index c2ef3fb..a56b123 100644 --- a/src/UISprite.cpp +++ b/src/UISprite.cpp @@ -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(keywords), &x, &y, &texture, &sprite_index, &scale, &click_handler)) + // Try to parse all arguments with keywords + if (PyArg_ParseTupleAndKeywords(args, kwds, "|ffOifOO", + const_cast(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(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;