feat: Grid size tuple support closes #90

- Add grid_size keyword parameter to Grid.__init__
- Accept tuple or list of two integers
- Override grid_x/grid_y if grid_size provided
- Maintain backward compatibility
- Add comprehensive test coverage
This commit is contained in:
John McCardle 2025-07-06 00:31:29 -04:00
parent f1b354e47d
commit da7180f5ed
4 changed files with 143 additions and 19 deletions

View File

@ -268,16 +268,16 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
// Constructor switch to Vector position // 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", 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", nullptr }; static const char* keywords[] = { "pos", "text", "font", "fill_color", "outline_color", "outline", "click", nullptr };
PyObject* pos = NULL; PyObject* pos = NULL;
float outline = 0.0f; float outline = 0.0f;
char* text = NULL; char* text = NULL;
PyObject* font=NULL, *fill_color=NULL, *outline_color=NULL; PyObject* font=NULL, *fill_color=NULL, *outline_color=NULL, *click_handler=NULL;
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf", //if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf",
// const_cast<char**>(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline)) // const_cast<char**>(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline))
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OzOOOf", if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OzOOOfO",
const_cast<char**>(keywords), &pos, &text, &font, &fill_color, &outline_color, &outline)) const_cast<char**>(keywords), &pos, &text, &font, &fill_color, &outline_color, &outline, &click_handler))
{ {
return -1; return -1;
} }
@ -351,6 +351,15 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
self->data->text.setOutlineColor(sf::Color(128,128,128,255)); self->data->text.setOutlineColor(sf::Color(128,128,128,255));
} }
// Process click handler if provided
if (click_handler && click_handler != Py_None) {
if (!PyCallable_Check(click_handler)) {
PyErr_SetString(PyExc_TypeError, "click must be callable");
return -1;
}
self->data->click_register(click_handler);
}
return 0; return 0;
} }

View File

@ -2,6 +2,10 @@
#include "UICollection.h" #include "UICollection.h"
#include "GameEngine.h" #include "GameEngine.h"
#include "PyVector.h" #include "PyVector.h"
#include "UICaption.h"
#include "UISprite.h"
#include "UIGrid.h"
#include "McRFPy_API.h"
UIDrawable* UIFrame::click_at(sf::Vector2f point) UIDrawable* UIFrame::click_at(sf::Vector2f point)
{ {
@ -298,22 +302,24 @@ PyObject* UIFrame::repr(PyUIFrameObject* self)
int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds) int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds)
{ {
//std::cout << "Init called\n"; //std::cout << "Init called\n";
const char* keywords[] = { "x", "y", "w", "h", "fill_color", "outline_color", "outline", nullptr }; const char* keywords[] = { "x", "y", "w", "h", "fill_color", "outline_color", "outline", "children", "click", nullptr };
float x = 0.0f, y = 0.0f, w = 0.0f, h=0.0f, outline=0.0f; float x = 0.0f, y = 0.0f, w = 0.0f, h=0.0f, outline=0.0f;
PyObject* fill_color = 0; PyObject* fill_color = 0;
PyObject* outline_color = 0; PyObject* outline_color = 0;
PyObject* children_arg = 0;
PyObject* click_handler = 0;
// First try to parse as (x, y, w, h, ...) // First try to parse as (x, y, w, h, ...)
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffffOOf", const_cast<char**>(keywords), &x, &y, &w, &h, &fill_color, &outline_color, &outline)) if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffffOOfOO", const_cast<char**>(keywords), &x, &y, &w, &h, &fill_color, &outline_color, &outline, &children_arg, &click_handler))
{ {
PyErr_Clear(); // Clear the error PyErr_Clear(); // Clear the error
// Try to parse as ((x,y), w, h, ...) or (Vector, w, h, ...) // Try to parse as ((x,y), w, h, ...) or (Vector, w, h, ...)
PyObject* pos_obj = nullptr; PyObject* pos_obj = nullptr;
const char* alt_keywords[] = { "pos", "w", "h", "fill_color", "outline_color", "outline", nullptr }; const char* alt_keywords[] = { "pos", "w", "h", "fill_color", "outline_color", "outline", "children", "click", nullptr };
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OffOOf", const_cast<char**>(alt_keywords), if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OffOOfOO", const_cast<char**>(alt_keywords),
&pos_obj, &w, &h, &fill_color, &outline_color, &outline)) &pos_obj, &w, &h, &fill_color, &outline_color, &outline, &children_arg, &click_handler))
{ {
return -1; return -1;
} }
@ -341,6 +347,70 @@ int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds)
if (outline_color && outline_color != Py_None) err_val = UIFrame::set_color_member(self, outline_color, (void*)1); if (outline_color && outline_color != Py_None) err_val = UIFrame::set_color_member(self, outline_color, (void*)1);
else self->data->box.setOutlineColor(sf::Color(128,128,128,255)); else self->data->box.setOutlineColor(sf::Color(128,128,128,255));
if (err_val) return err_val; if (err_val) return err_val;
// Process children argument if provided
if (children_arg && children_arg != Py_None) {
if (!PySequence_Check(children_arg)) {
PyErr_SetString(PyExc_TypeError, "children must be a sequence");
return -1;
}
Py_ssize_t len = PySequence_Length(children_arg);
for (Py_ssize_t i = 0; i < len; i++) {
PyObject* child = PySequence_GetItem(children_arg, i);
if (!child) return -1;
// Check if it's a UIDrawable (Frame, Caption, Sprite, or Grid)
PyObject* frame_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame");
PyObject* caption_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption");
PyObject* sprite_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite");
PyObject* grid_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid");
if (!PyObject_IsInstance(child, frame_type) &&
!PyObject_IsInstance(child, caption_type) &&
!PyObject_IsInstance(child, sprite_type) &&
!PyObject_IsInstance(child, grid_type)) {
Py_DECREF(child);
PyErr_SetString(PyExc_TypeError, "children must contain only Frame, Caption, Sprite, or Grid objects");
return -1;
}
// Get the shared_ptr and add to children
std::shared_ptr<UIDrawable> drawable = nullptr;
if (PyObject_IsInstance(child, frame_type)) {
drawable = ((PyUIFrameObject*)child)->data;
} else if (PyObject_IsInstance(child, caption_type)) {
drawable = ((PyUICaptionObject*)child)->data;
} else if (PyObject_IsInstance(child, sprite_type)) {
drawable = ((PyUISpriteObject*)child)->data;
} else if (PyObject_IsInstance(child, grid_type)) {
drawable = ((PyUIGridObject*)child)->data;
}
// Clean up type references
Py_DECREF(frame_type);
Py_DECREF(caption_type);
Py_DECREF(sprite_type);
Py_DECREF(grid_type);
if (drawable) {
self->data->children->push_back(drawable);
self->data->children_need_sort = true;
}
Py_DECREF(child);
}
}
// Process click handler if provided
if (click_handler && click_handler != Py_None) {
if (!PyCallable_Check(click_handler)) {
PyErr_SetString(PyExc_TypeError, "click must be callable");
return -1;
}
self->data->click_register(click_handler);
}
return 0; return 0;
} }

View File

@ -268,13 +268,48 @@ UIDrawable* UIGrid::click_at(sf::Vector2f point)
int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) { int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
int grid_x = 0, grid_y = 0; // Default to 0x0 grid int grid_x = 0, grid_y = 0; // Default to 0x0 grid
PyObject* textureObj = Py_None; PyObject* textureObj = Py_None;
//float box_x, box_y, box_w, box_h;
PyObject* pos = NULL; PyObject* pos = NULL;
PyObject* size = NULL; PyObject* size = NULL;
PyObject* grid_size_obj = NULL;
//if (!PyArg_ParseTuple(args, "iiOffff", &grid_x, &grid_y, &textureObj, &box_x, &box_y, &box_w, &box_h)) { static const char* keywords[] = {"grid_x", "grid_y", "texture", "pos", "size", "grid_size", NULL};
if (!PyArg_ParseTuple(args, "|iiOOO", &grid_x, &grid_y, &textureObj, &pos, &size)) {
return -1; // If parsing fails, return an error // 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 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) {
PyObject* x_obj = PyTuple_GetItem(grid_size_obj, 0);
PyObject* y_obj = PyTuple_GetItem(grid_size_obj, 1);
if (PyLong_Check(x_obj) && PyLong_Check(y_obj)) {
grid_x = PyLong_AsLong(x_obj);
grid_y = PyLong_AsLong(y_obj);
} else {
PyErr_SetString(PyExc_TypeError, "grid_size tuple must contain integers");
return -1;
}
} else if (PyList_Check(grid_size_obj) && PyList_Size(grid_size_obj) == 2) {
PyObject* x_obj = PyList_GetItem(grid_size_obj, 0);
PyObject* y_obj = PyList_GetItem(grid_size_obj, 1);
if (PyLong_Check(x_obj) && PyLong_Check(y_obj)) {
grid_x = PyLong_AsLong(x_obj);
grid_y = PyLong_AsLong(y_obj);
} else {
PyErr_SetString(PyExc_TypeError, "grid_size list must contain integers");
return -1;
}
} else {
PyErr_SetString(PyExc_TypeError, "grid_size must be a tuple or list of two integers");
return -1;
}
}
} else {
// Clear error and try parsing without keywords (backward compatibility)
PyErr_Clear();
if (!PyArg_ParseTuple(args, "|iiOOO", &grid_x, &grid_y, &textureObj, &pos, &size)) {
return -1; // If parsing fails, return an error
}
} }
// Default position and size if not provided // Default position and size if not provided

View File

@ -298,23 +298,24 @@ PyObject* UISprite::repr(PyUISpriteObject* self)
int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds) int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds)
{ {
//std::cout << "Init called\n"; //std::cout << "Init called\n";
static const char* keywords[] = { "x", "y", "texture", "sprite_index", "scale", nullptr }; static const char* keywords[] = { "x", "y", "texture", "sprite_index", "scale", "click", nullptr };
float x = 0.0f, y = 0.0f, scale = 1.0f; float x = 0.0f, y = 0.0f, scale = 1.0f;
int sprite_index = 0; int sprite_index = 0;
PyObject* texture = NULL; PyObject* texture = NULL;
PyObject* click_handler = NULL;
// First try to parse as (x, y, texture, ...) // First try to parse as (x, y, texture, ...)
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffOif", if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffOifO",
const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &scale)) const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &scale, &click_handler))
{ {
PyErr_Clear(); // Clear the error PyErr_Clear(); // Clear the error
// Try to parse as ((x,y), texture, ...) or (Vector, texture, ...) // Try to parse as ((x,y), texture, ...) or (Vector, texture, ...)
PyObject* pos_obj = nullptr; PyObject* pos_obj = nullptr;
const char* alt_keywords[] = { "pos", "texture", "sprite_index", "scale", nullptr }; const char* alt_keywords[] = { "pos", "texture", "sprite_index", "scale", "click", nullptr };
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOif", const_cast<char**>(alt_keywords), if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOifO", const_cast<char**>(alt_keywords),
&pos_obj, &texture, &sprite_index, &scale)) &pos_obj, &texture, &sprite_index, &scale, &click_handler))
{ {
return -1; return -1;
} }
@ -352,6 +353,15 @@ int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds)
self->data = std::make_shared<UISprite>(texture_ptr, sprite_index, sf::Vector2f(x, y), scale); self->data = std::make_shared<UISprite>(texture_ptr, sprite_index, sf::Vector2f(x, y), scale);
self->data->setPosition(sf::Vector2f(x, y)); self->data->setPosition(sf::Vector2f(x, y));
// Process click handler if provided
if (click_handler && click_handler != Py_None) {
if (!PyCallable_Check(click_handler)) {
PyErr_SetString(PyExc_TypeError, "click must be callable");
return -1;
}
self->data->click_register(click_handler);
}
return 0; return 0;
} }