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:
parent
fe5976c425
commit
c48c91e5d7
|
@ -0,0 +1,164 @@
|
||||||
|
#pragma once
|
||||||
|
#include "Python.h"
|
||||||
|
#include "PyVector.h"
|
||||||
|
#include "McRFPy_API.h"
|
||||||
|
|
||||||
|
// Helper class for standardized position argument parsing across UI classes
|
||||||
|
class PyPositionHelper {
|
||||||
|
public:
|
||||||
|
// Template structure for parsing results
|
||||||
|
struct ParseResult {
|
||||||
|
float x = 0.0f;
|
||||||
|
float y = 0.0f;
|
||||||
|
bool has_position = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParseResultInt {
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
bool has_position = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse position from multiple formats for UI class constructors
|
||||||
|
// Supports: (x, y), x=x, y=y, ((x,y)), (pos=(x,y)), (Vector), pos=Vector
|
||||||
|
static ParseResult parse_position(PyObject* args, PyObject* kwds,
|
||||||
|
int* arg_index = nullptr)
|
||||||
|
{
|
||||||
|
ParseResult result;
|
||||||
|
float x = 0.0f, y = 0.0f;
|
||||||
|
PyObject* pos_obj = nullptr;
|
||||||
|
int start_index = arg_index ? *arg_index : 0;
|
||||||
|
|
||||||
|
// Check for positional tuple (x, y) first
|
||||||
|
if (!kwds && PyTuple_Size(args) > start_index + 1) {
|
||||||
|
PyObject* first = PyTuple_GetItem(args, start_index);
|
||||||
|
PyObject* second = PyTuple_GetItem(args, start_index + 1);
|
||||||
|
|
||||||
|
// Check if both are numbers
|
||||||
|
if ((PyFloat_Check(first) || PyLong_Check(first)) &&
|
||||||
|
(PyFloat_Check(second) || PyLong_Check(second))) {
|
||||||
|
x = PyFloat_Check(first) ? PyFloat_AsDouble(first) : PyLong_AsLong(first);
|
||||||
|
y = PyFloat_Check(second) ? PyFloat_AsDouble(second) : PyLong_AsLong(second);
|
||||||
|
result.x = x;
|
||||||
|
result.y = y;
|
||||||
|
result.has_position = true;
|
||||||
|
if (arg_index) *arg_index += 2;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for single positional argument that might be tuple or Vector
|
||||||
|
if (!kwds && PyTuple_Size(args) > start_index) {
|
||||||
|
PyObject* first = PyTuple_GetItem(args, start_index);
|
||||||
|
PyVectorObject* vec = PyVector::from_arg(first);
|
||||||
|
if (vec) {
|
||||||
|
result.x = vec->data.x;
|
||||||
|
result.y = vec->data.y;
|
||||||
|
result.has_position = true;
|
||||||
|
if (arg_index) *arg_index += 1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try keyword arguments
|
||||||
|
if (kwds) {
|
||||||
|
PyObject* x_obj = PyDict_GetItemString(kwds, "x");
|
||||||
|
PyObject* y_obj = PyDict_GetItemString(kwds, "y");
|
||||||
|
PyObject* pos_kw = PyDict_GetItemString(kwds, "pos");
|
||||||
|
|
||||||
|
if (x_obj && y_obj) {
|
||||||
|
if ((PyFloat_Check(x_obj) || PyLong_Check(x_obj)) &&
|
||||||
|
(PyFloat_Check(y_obj) || PyLong_Check(y_obj))) {
|
||||||
|
result.x = PyFloat_Check(x_obj) ? PyFloat_AsDouble(x_obj) : PyLong_AsLong(x_obj);
|
||||||
|
result.y = PyFloat_Check(y_obj) ? PyFloat_AsDouble(y_obj) : PyLong_AsLong(y_obj);
|
||||||
|
result.has_position = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos_kw) {
|
||||||
|
PyVectorObject* vec = PyVector::from_arg(pos_kw);
|
||||||
|
if (vec) {
|
||||||
|
result.x = vec->data.x;
|
||||||
|
result.y = vec->data.y;
|
||||||
|
result.has_position = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse integer position for Grid.at() and similar
|
||||||
|
static ParseResultInt parse_position_int(PyObject* args, PyObject* kwds)
|
||||||
|
{
|
||||||
|
ParseResultInt result;
|
||||||
|
|
||||||
|
// Check for positional tuple (x, y) first
|
||||||
|
if (!kwds && PyTuple_Size(args) >= 2) {
|
||||||
|
PyObject* first = PyTuple_GetItem(args, 0);
|
||||||
|
PyObject* second = PyTuple_GetItem(args, 1);
|
||||||
|
|
||||||
|
if (PyLong_Check(first) && PyLong_Check(second)) {
|
||||||
|
result.x = PyLong_AsLong(first);
|
||||||
|
result.y = PyLong_AsLong(second);
|
||||||
|
result.has_position = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for single tuple argument
|
||||||
|
if (!kwds && PyTuple_Size(args) == 1) {
|
||||||
|
PyObject* first = PyTuple_GetItem(args, 0);
|
||||||
|
if (PyTuple_Check(first) && PyTuple_Size(first) == 2) {
|
||||||
|
PyObject* x_obj = PyTuple_GetItem(first, 0);
|
||||||
|
PyObject* y_obj = PyTuple_GetItem(first, 1);
|
||||||
|
if (PyLong_Check(x_obj) && PyLong_Check(y_obj)) {
|
||||||
|
result.x = PyLong_AsLong(x_obj);
|
||||||
|
result.y = PyLong_AsLong(y_obj);
|
||||||
|
result.has_position = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try keyword arguments
|
||||||
|
if (kwds) {
|
||||||
|
PyObject* x_obj = PyDict_GetItemString(kwds, "x");
|
||||||
|
PyObject* y_obj = PyDict_GetItemString(kwds, "y");
|
||||||
|
PyObject* pos_obj = PyDict_GetItemString(kwds, "pos");
|
||||||
|
|
||||||
|
if (x_obj && y_obj && PyLong_Check(x_obj) && PyLong_Check(y_obj)) {
|
||||||
|
result.x = PyLong_AsLong(x_obj);
|
||||||
|
result.y = PyLong_AsLong(y_obj);
|
||||||
|
result.has_position = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos_obj && PyTuple_Check(pos_obj) && PyTuple_Size(pos_obj) == 2) {
|
||||||
|
PyObject* x_val = PyTuple_GetItem(pos_obj, 0);
|
||||||
|
PyObject* y_val = PyTuple_GetItem(pos_obj, 1);
|
||||||
|
if (PyLong_Check(x_val) && PyLong_Check(y_val)) {
|
||||||
|
result.x = PyLong_AsLong(x_val);
|
||||||
|
result.y = PyLong_AsLong(y_val);
|
||||||
|
result.has_position = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error message helper
|
||||||
|
static void set_position_error() {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"Position can be specified as: (x, y), x=x, y=y, ((x,y)), pos=(x,y), or pos=Vector");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_position_int_error() {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"Position must be specified as: (x, y), x=x, y=y, ((x,y)), or pos=(x,y) with integer values");
|
||||||
|
}
|
||||||
|
};
|
|
@ -3,6 +3,7 @@
|
||||||
#include "PyColor.h"
|
#include "PyColor.h"
|
||||||
#include "PyVector.h"
|
#include "PyVector.h"
|
||||||
#include "PyFont.h"
|
#include "PyFont.h"
|
||||||
|
#include "PyPositionHelper.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
UICaption::UICaption()
|
UICaption::UICaption()
|
||||||
|
@ -265,35 +266,59 @@ PyObject* UICaption::repr(PyUICaptionObject* self)
|
||||||
int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
||||||
{
|
{
|
||||||
using namespace mcrfpydef;
|
using namespace mcrfpydef;
|
||||||
// Constructor switch to Vector position
|
|
||||||
//static const char* keywords[] = { "x", "y", "text", "font", "fill_color", "outline_color", "outline", nullptr };
|
static const char* keywords[] = { "x", "y", "text", "font", "fill_color", "outline_color", "outline", "click", "pos", nullptr };
|
||||||
//float x = 0.0f, y = 0.0f, outline = 0.0f;
|
float x = 0.0f, y = 0.0f, outline = 0.0f;
|
||||||
static const char* keywords[] = { "pos", "text", "font", "fill_color", "outline_color", "outline", "click", nullptr };
|
|
||||||
PyObject* pos = NULL;
|
|
||||||
float outline = 0.0f;
|
|
||||||
char* text = NULL;
|
char* text = NULL;
|
||||||
PyObject* font=NULL, *fill_color=NULL, *outline_color=NULL, *click_handler=NULL;
|
PyObject* font = NULL;
|
||||||
|
PyObject* fill_color = NULL;
|
||||||
|
PyObject* outline_color = NULL;
|
||||||
|
PyObject* click_handler = NULL;
|
||||||
|
PyObject* pos_obj = NULL;
|
||||||
|
|
||||||
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf",
|
// Try parsing all arguments with keywords
|
||||||
// const_cast<char**>(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline))
|
if (PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOfOO",
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OzOOOfO",
|
const_cast<char**>(keywords),
|
||||||
const_cast<char**>(keywords), &pos, &text, &font, &fill_color, &outline_color, &outline, &click_handler))
|
&x, &y, &text, &font, &fill_color, &outline_color, &outline, &click_handler, &pos_obj))
|
||||||
{
|
{
|
||||||
return -1;
|
// 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();
|
||||||
|
|
||||||
// Handle position - default to (0, 0) if not provided
|
// Try alternative: first arg is pos tuple/Vector
|
||||||
if (pos && pos != Py_None) {
|
static const char* alt_keywords[] = { "pos", "text", "font", "fill_color", "outline_color", "outline", "click", nullptr };
|
||||||
PyVectorObject* pos_result = PyVector::from_arg(pos);
|
PyObject* pos = NULL;
|
||||||
if (!pos_result)
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OzOOOfO",
|
||||||
|
const_cast<char**>(alt_keywords),
|
||||||
|
&pos, &text, &font, &fill_color, &outline_color, &outline, &click_handler))
|
||||||
{
|
{
|
||||||
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
self->data->text.setPosition(pos_result->data);
|
|
||||||
} else {
|
// Parse position
|
||||||
self->data->text.setPosition(0.0f, 0.0f);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self->data->text.setPosition(x, y);
|
||||||
// check types for font, fill_color, outline_color
|
// check types for font, fill_color, outline_color
|
||||||
|
|
||||||
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl;
|
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
|
#include "PyPositionHelper.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
UIGrid::UIGrid()
|
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};
|
static const char* keywords[] = {"grid_x", "grid_y", "texture", "pos", "size", "grid_size", NULL};
|
||||||
|
|
||||||
// First try parsing with keywords
|
// First try parsing with keywords
|
||||||
if (kwds && PyArg_ParseTupleAndKeywords(args, kwds, "|iiOOOO", const_cast<char**>(keywords),
|
if (PyArg_ParseTupleAndKeywords(args, kwds, "|iiOOOO", const_cast<char**>(keywords),
|
||||||
&grid_x, &grid_y, &textureObj, &pos, &size, &grid_size_obj)) {
|
&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 is provided, use it to override grid_x and grid_y
|
||||||
if (grid_size_obj && grid_size_obj != Py_None) {
|
if (grid_size_obj && grid_size_obj != Py_None) {
|
||||||
if (PyTuple_Check(grid_size_obj) && PyTuple_Size(grid_size_obj) == 2) {
|
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)
|
PyObject* UIGrid::py_at(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
||||||
{
|
{
|
||||||
static const char* keywords[] = { "x", "y", "pos", nullptr };
|
// Use the standardized position parser
|
||||||
int x = -1, y = -1;
|
auto result = PyPositionHelper::parse_position_int(args, kwds);
|
||||||
PyObject* pos = nullptr;
|
|
||||||
|
|
||||||
// Try to parse with keywords first
|
if (!result.has_position) {
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiO", const_cast<char**>(keywords), &x, &y, &pos)) {
|
PyPositionHelper::set_position_int_error();
|
||||||
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");
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int x = result.x;
|
||||||
|
int y = result.y;
|
||||||
|
|
||||||
// Range validation
|
// Range validation
|
||||||
if (x < 0 || x >= self->data->grid_x) {
|
if (x < 0 || x >= self->data->grid_x) {
|
||||||
PyErr_SetString(PyExc_ValueError, "x value out of range (0, Grid.grid_x)");
|
PyErr_SetString(PyExc_ValueError, "x value out of range (0, Grid.grid_x)");
|
||||||
|
|
Loading…
Reference in New Issue