Python API improvements: Vectors, bounds, window singleton, hidden types
- #177: GridPoint.grid_pos property returns (x, y) tuple - #179: Grid.grid_size returns Vector instead of tuple - #181: Grid.center returns Vector instead of tuple - #182: Caption.size/w/h read-only properties for text dimensions - #184: mcrfpy.window singleton for window access - #185: Removed get_bounds() method, use .bounds property instead - #188: bounds/global_bounds return (pos, size) as pair of Vectors - #189: Hide internal types from module namespace (iterators, collections) Also fixed critical bug: Changed static PyTypeObject to inline in headers to ensure single instance across translation units (was causing segfaults). Closes #177, closes #179, closes #181, closes #182, closes #184, closes #185, closes #188, closes #189 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c6233fa47f
commit
f9b6cdef1c
17 changed files with 448 additions and 87 deletions
|
|
@ -306,7 +306,9 @@ PyObject* PyInit_mcrfpy()
|
|||
Py_SET_TYPE(m, &McRFPyModuleType);
|
||||
|
||||
using namespace mcrfpydef;
|
||||
PyTypeObject* pytypes[] = {
|
||||
|
||||
// Types that are exported to Python (visible in module namespace)
|
||||
PyTypeObject* exported_types[] = {
|
||||
/*SFML exposed types*/
|
||||
&PyColorType, /*&PyLinkedColorType,*/ &PyFontType, &PyTextureType, &PyVectorType,
|
||||
|
||||
|
|
@ -317,23 +319,16 @@ PyObject* PyInit_mcrfpy()
|
|||
&PyUICaptionType, &PyUISpriteType, &PyUIFrameType, &PyUIEntityType, &PyUIGridType,
|
||||
&PyUILineType, &PyUICircleType, &PyUIArcType,
|
||||
|
||||
/*game map & perspective data*/
|
||||
&PyUIGridPointType, &PyUIGridPointStateType,
|
||||
|
||||
/*grid layers (#147)*/
|
||||
&PyColorLayerType, &PyTileLayerType,
|
||||
|
||||
/*collections & iterators*/
|
||||
&PyUICollectionType, &PyUICollectionIterType,
|
||||
&PyUIEntityCollectionType, &PyUIEntityCollectionIterType,
|
||||
|
||||
/*animation*/
|
||||
&PyAnimationType,
|
||||
|
||||
/*timer*/
|
||||
&PyTimerType,
|
||||
|
||||
/*window singleton*/
|
||||
/*window singleton type (#184 - type exported for isinstance checks)*/
|
||||
&PyWindowType,
|
||||
|
||||
/*scene class*/
|
||||
|
|
@ -347,6 +342,18 @@ PyObject* PyInit_mcrfpy()
|
|||
&PyKeyboardType,
|
||||
|
||||
nullptr};
|
||||
|
||||
// Types that are used internally but NOT exported to module namespace (#189)
|
||||
// These still need PyType_Ready() but are not added to module
|
||||
PyTypeObject* internal_types[] = {
|
||||
/*game map & perspective data - returned by Grid.at() but not directly instantiable*/
|
||||
&PyUIGridPointType, &PyUIGridPointStateType,
|
||||
|
||||
/*collections & iterators - returned by .children/.entities but not directly instantiable*/
|
||||
&PyUICollectionType, &PyUICollectionIterType,
|
||||
&PyUIEntityCollectionType, &PyUIEntityCollectionIterType,
|
||||
|
||||
nullptr};
|
||||
|
||||
// Set up PyWindowType methods and getsetters before PyType_Ready
|
||||
PyWindowType.tp_methods = PyWindow::methods;
|
||||
|
|
@ -367,19 +374,32 @@ PyObject* PyInit_mcrfpy()
|
|||
PyUICircleType.tp_weaklistoffset = offsetof(PyUICircleObject, weakreflist);
|
||||
PyUIArcType.tp_weaklistoffset = offsetof(PyUIArcObject, weakreflist);
|
||||
|
||||
// Process exported types - PyType_Ready AND add to module
|
||||
int i = 0;
|
||||
auto t = pytypes[i];
|
||||
auto t = exported_types[i];
|
||||
while (t != nullptr)
|
||||
{
|
||||
//std::cout << "Registering type: " << t->tp_name << std::endl;
|
||||
if (PyType_Ready(t) < 0) {
|
||||
std::cout << "ERROR: PyType_Ready failed for " << t->tp_name << std::endl;
|
||||
return NULL;
|
||||
}
|
||||
//std::cout << " tp_alloc after PyType_Ready: " << (void*)t->tp_alloc << std::endl;
|
||||
PyModule_AddType(m, t);
|
||||
i++;
|
||||
t = pytypes[i];
|
||||
t = exported_types[i];
|
||||
}
|
||||
|
||||
// Process internal types - PyType_Ready only, NOT added to module (#189)
|
||||
i = 0;
|
||||
t = internal_types[i];
|
||||
while (t != nullptr)
|
||||
{
|
||||
if (PyType_Ready(t) < 0) {
|
||||
std::cout << "ERROR: PyType_Ready failed for " << t->tp_name << std::endl;
|
||||
return NULL;
|
||||
}
|
||||
// Note: NOT calling PyModule_AddType - these are internal-only types
|
||||
i++;
|
||||
t = internal_types[i];
|
||||
}
|
||||
|
||||
// Add default_font and default_texture to module
|
||||
|
|
@ -395,6 +415,13 @@ PyObject* PyInit_mcrfpy()
|
|||
PyModule_AddObject(m, "keyboard", keyboard_instance);
|
||||
}
|
||||
|
||||
// Add window singleton (#184)
|
||||
// Use tp_alloc directly to bypass tp_new which blocks user instantiation
|
||||
PyObject* window_instance = PyWindowType.tp_alloc(&PyWindowType, 0);
|
||||
if (window_instance) {
|
||||
PyModule_AddObject(m, "window", window_instance);
|
||||
}
|
||||
|
||||
// Add version string (#164)
|
||||
PyModule_AddStringConstant(m, "__version__", MCRFPY_VERSION);
|
||||
|
||||
|
|
|
|||
|
|
@ -123,13 +123,6 @@ static PyGetSetDef PyDrawable_getsetters[] = {
|
|||
{NULL} // Sentinel
|
||||
};
|
||||
|
||||
// get_bounds method implementation (#89)
|
||||
static PyObject* PyDrawable_get_bounds(PyDrawableObject* self, PyObject* Py_UNUSED(args))
|
||||
{
|
||||
auto bounds = self->data->get_bounds();
|
||||
return Py_BuildValue("(ffff)", bounds.left, bounds.top, bounds.width, bounds.height);
|
||||
}
|
||||
|
||||
// move method implementation (#98)
|
||||
static PyObject* PyDrawable_move(PyDrawableObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
|
|
@ -156,13 +149,6 @@ static PyObject* PyDrawable_resize(PyDrawableObject* self, PyObject* args, PyObj
|
|||
|
||||
// Method definitions
|
||||
static PyMethodDef PyDrawable_methods[] = {
|
||||
{"get_bounds", (PyCFunction)PyDrawable_get_bounds, METH_NOARGS,
|
||||
MCRF_METHOD(Drawable, get_bounds,
|
||||
MCRF_SIG("()", "tuple"),
|
||||
MCRF_DESC("Get the bounding rectangle of this drawable element."),
|
||||
MCRF_RETURNS("tuple: (x, y, width, height) representing the element's bounds")
|
||||
MCRF_NOTE("The bounds are in screen coordinates and account for current position and size.")
|
||||
)},
|
||||
{"move", (PyCFunction)PyDrawable_move, METH_VARARGS | METH_KEYWORDS,
|
||||
MCRF_METHOD(Drawable, move,
|
||||
MCRF_SIG("(dx, dy) or (delta)", "None"),
|
||||
|
|
|
|||
20
src/UIBase.h
20
src/UIBase.h
|
|
@ -43,14 +43,6 @@ typedef struct {
|
|||
// Common Python method implementations for UIDrawable-derived classes
|
||||
// These template functions provide shared functionality for Python bindings
|
||||
|
||||
// get_bounds method implementation (#89)
|
||||
template<typename T>
|
||||
static PyObject* UIDrawable_get_bounds(T* self, PyObject* Py_UNUSED(args))
|
||||
{
|
||||
auto bounds = self->data->get_bounds();
|
||||
return Py_BuildValue("(ffff)", bounds.left, bounds.top, bounds.width, bounds.height);
|
||||
}
|
||||
|
||||
// move method implementation (#98)
|
||||
template<typename T>
|
||||
static PyObject* UIDrawable_move(T* self, PyObject* args, PyObject* kwds)
|
||||
|
|
@ -90,14 +82,8 @@ static PyObject* UIDrawable_animate(T* self, PyObject* args, PyObject* kwds)
|
|||
}
|
||||
|
||||
// Macro to add common UIDrawable methods to a method array (without animate - for base types)
|
||||
// #185: Removed get_bounds method - use .bounds property instead
|
||||
#define UIDRAWABLE_METHODS_BASE \
|
||||
{"get_bounds", (PyCFunction)UIDrawable_get_bounds<PyObjectType>, METH_NOARGS, \
|
||||
MCRF_METHOD(Drawable, get_bounds, \
|
||||
MCRF_SIG("()", "tuple"), \
|
||||
MCRF_DESC("Get the bounding rectangle of this drawable element."), \
|
||||
MCRF_RETURNS("tuple: (x, y, width, height) representing the element's bounds") \
|
||||
MCRF_NOTE("The bounds are in screen coordinates and account for current position and size.") \
|
||||
)}, \
|
||||
{"move", (PyCFunction)UIDrawable_move<PyObjectType>, METH_VARARGS | METH_KEYWORDS, \
|
||||
MCRF_METHOD(Drawable, move, \
|
||||
MCRF_SIG("(dx, dy) or (delta)", "None"), \
|
||||
|
|
@ -216,11 +202,11 @@ static int UIDrawable_set_opacity(T* self, PyObject* value, void* closure)
|
|||
), (void*)type_enum}, \
|
||||
{"bounds", (getter)UIDrawable::get_bounds_py, NULL, \
|
||||
MCRF_PROPERTY(bounds, \
|
||||
"Bounding rectangle (x, y, width, height) in local coordinates." \
|
||||
"Bounding box as (pos, size) tuple of Vectors. Returns (Vector(x, y), Vector(width, height))." \
|
||||
), (void*)type_enum}, \
|
||||
{"global_bounds", (getter)UIDrawable::get_global_bounds_py, NULL, \
|
||||
MCRF_PROPERTY(global_bounds, \
|
||||
"Bounding rectangle (x, y, width, height) in screen coordinates." \
|
||||
"Bounding box as (pos, size) tuple of Vectors in screen coordinates. Returns (Vector(x, y), Vector(width, height))." \
|
||||
), (void*)type_enum}, \
|
||||
{"on_enter", (getter)UIDrawable::get_on_enter, (setter)UIDrawable::set_on_enter, \
|
||||
MCRF_PROPERTY(on_enter, \
|
||||
|
|
|
|||
|
|
@ -261,12 +261,31 @@ int UICaption::set_text(PyUICaptionObject* self, PyObject* value, void* closure)
|
|||
return 0;
|
||||
}
|
||||
|
||||
PyObject* UICaption::get_size(PyUICaptionObject* self, void* closure)
|
||||
{
|
||||
auto bounds = self->data->text.getGlobalBounds();
|
||||
return PyVector(sf::Vector2f(bounds.width, bounds.height)).pyObject();
|
||||
}
|
||||
|
||||
PyObject* UICaption::get_w(PyUICaptionObject* self, void* closure)
|
||||
{
|
||||
auto bounds = self->data->text.getGlobalBounds();
|
||||
return PyFloat_FromDouble(bounds.width);
|
||||
}
|
||||
|
||||
PyObject* UICaption::get_h(PyUICaptionObject* self, void* closure)
|
||||
{
|
||||
auto bounds = self->data->text.getGlobalBounds();
|
||||
return PyFloat_FromDouble(bounds.height);
|
||||
}
|
||||
|
||||
PyGetSetDef UICaption::getsetters[] = {
|
||||
{"x", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "X coordinate of top-left corner", (void*)((intptr_t)PyObjectsEnum::UICAPTION << 8 | 0)},
|
||||
{"y", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "Y coordinate of top-left corner", (void*)((intptr_t)PyObjectsEnum::UICAPTION << 8 | 1)},
|
||||
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos, "(x, y) vector", (void*)PyObjectsEnum::UICAPTION},
|
||||
//{"w", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "width of the rectangle", (void*)2},
|
||||
//{"h", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "height of the rectangle", (void*)3},
|
||||
{"size", (getter)UICaption::get_size, NULL, "Text dimensions as Vector (read-only)", NULL},
|
||||
{"w", (getter)UICaption::get_w, NULL, "Text width in pixels (read-only)", NULL},
|
||||
{"h", (getter)UICaption::get_h, NULL, "Text height in pixels (read-only)", NULL},
|
||||
{"outline", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Thickness of the border", (void*)4},
|
||||
{"fill_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member,
|
||||
"Fill color of the text. Returns a copy; modifying components requires reassignment. "
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ public:
|
|||
static int set_color_member(PyUICaptionObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_text(PyUICaptionObject* self, void* closure);
|
||||
static int set_text(PyUICaptionObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_size(PyUICaptionObject* self, void* closure);
|
||||
static PyObject* get_w(PyUICaptionObject* self, void* closure);
|
||||
static PyObject* get_h(PyUICaptionObject* self, void* closure);
|
||||
static PyGetSetDef getsetters[];
|
||||
static PyObject* repr(PyUICaptionObject* self);
|
||||
static int init(PyUICaptionObject* self, PyObject* args, PyObject* kwds);
|
||||
|
|
|
|||
|
|
@ -1285,18 +1285,13 @@ int UICollection::init(PyUICollectionObject* self, PyObject* args, PyObject* kwd
|
|||
|
||||
PyObject* UICollection::iter(PyUICollectionObject* self)
|
||||
{
|
||||
// Get the iterator type from the module to ensure we have the registered version
|
||||
PyTypeObject* iterType = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UICollectionIter");
|
||||
if (!iterType) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Could not find UICollectionIter type in module");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Use the iterator type directly from namespace (#189 - type not exported to module)
|
||||
PyTypeObject* iterType = &PyUICollectionIterType;
|
||||
|
||||
// Allocate new iterator instance
|
||||
PyUICollectionIterObject* iterObj = (PyUICollectionIterObject*)iterType->tp_alloc(iterType, 0);
|
||||
|
||||
|
||||
if (iterObj == NULL) {
|
||||
Py_DECREF(iterType);
|
||||
return NULL; // Failed to allocate memory for the iterator object
|
||||
}
|
||||
|
||||
|
|
@ -1304,6 +1299,5 @@ PyObject* UICollection::iter(PyUICollectionObject* self)
|
|||
iterObj->index = 0;
|
||||
iterObj->start_size = self->data->size();
|
||||
|
||||
Py_DECREF(iterType);
|
||||
return (PyObject*)iterObj;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ public:
|
|||
};
|
||||
|
||||
namespace mcrfpydef {
|
||||
static PyTypeObject PyUICollectionIterType = {
|
||||
// #189 - Use inline instead of static to ensure single instance across translation units
|
||||
inline PyTypeObject PyUICollectionIterType = {
|
||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||
.tp_name = "mcrfpy.UICollectionIter",
|
||||
.tp_basicsize = sizeof(PyUICollectionIterObject),
|
||||
|
|
@ -70,7 +71,8 @@ namespace mcrfpydef {
|
|||
}
|
||||
};
|
||||
|
||||
static PyTypeObject PyUICollectionType = {
|
||||
// #189 - Use inline instead of static to ensure single instance across translation units
|
||||
inline PyTypeObject PyUICollectionType = {
|
||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||
.tp_name = "mcrfpy.UICollection",
|
||||
.tp_basicsize = sizeof(PyUICollectionObject),
|
||||
|
|
|
|||
|
|
@ -1089,7 +1089,7 @@ PyObject* UIDrawable::get_global_pos(PyObject* self, void* closure) {
|
|||
return result;
|
||||
}
|
||||
|
||||
// #138 - Python API for bounds property
|
||||
// #138, #188 - Python API for bounds property - returns (pos, size) as pair of Vectors
|
||||
PyObject* UIDrawable::get_bounds_py(PyObject* self, void* closure) {
|
||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||
UIDrawable* drawable = nullptr;
|
||||
|
|
@ -1122,10 +1122,35 @@ PyObject* UIDrawable::get_bounds_py(PyObject* self, void* closure) {
|
|||
}
|
||||
|
||||
sf::FloatRect bounds = drawable->get_bounds();
|
||||
return Py_BuildValue("(ffff)", bounds.left, bounds.top, bounds.width, bounds.height);
|
||||
|
||||
// Get Vector type from mcrfpy module
|
||||
PyObject* vector_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
||||
if (!vector_type) return NULL;
|
||||
|
||||
// Create pos vector
|
||||
PyObject* pos_args = Py_BuildValue("(ff)", bounds.left, bounds.top);
|
||||
PyObject* pos = PyObject_CallObject(vector_type, pos_args);
|
||||
Py_DECREF(pos_args);
|
||||
if (!pos) {
|
||||
Py_DECREF(vector_type);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create size vector
|
||||
PyObject* size_args = Py_BuildValue("(ff)", bounds.width, bounds.height);
|
||||
PyObject* size = PyObject_CallObject(vector_type, size_args);
|
||||
Py_DECREF(size_args);
|
||||
Py_DECREF(vector_type);
|
||||
if (!size) {
|
||||
Py_DECREF(pos);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Return tuple of two vectors (N steals reference)
|
||||
return Py_BuildValue("(NN)", pos, size);
|
||||
}
|
||||
|
||||
// #138 - Python API for global_bounds property
|
||||
// #138, #188 - Python API for global_bounds property - returns (pos, size) as pair of Vectors
|
||||
PyObject* UIDrawable::get_global_bounds_py(PyObject* self, void* closure) {
|
||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||
UIDrawable* drawable = nullptr;
|
||||
|
|
@ -1158,7 +1183,32 @@ PyObject* UIDrawable::get_global_bounds_py(PyObject* self, void* closure) {
|
|||
}
|
||||
|
||||
sf::FloatRect bounds = drawable->get_global_bounds();
|
||||
return Py_BuildValue("(ffff)", bounds.left, bounds.top, bounds.width, bounds.height);
|
||||
|
||||
// Get Vector type from mcrfpy module
|
||||
PyObject* vector_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
||||
if (!vector_type) return NULL;
|
||||
|
||||
// Create pos vector
|
||||
PyObject* pos_args = Py_BuildValue("(ff)", bounds.left, bounds.top);
|
||||
PyObject* pos = PyObject_CallObject(vector_type, pos_args);
|
||||
Py_DECREF(pos_args);
|
||||
if (!pos) {
|
||||
Py_DECREF(vector_type);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create size vector
|
||||
PyObject* size_args = Py_BuildValue("(ff)", bounds.width, bounds.height);
|
||||
PyObject* size = PyObject_CallObject(vector_type, size_args);
|
||||
Py_DECREF(size_args);
|
||||
Py_DECREF(vector_type);
|
||||
if (!size) {
|
||||
Py_DECREF(pos);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Return tuple of two vectors (N steals reference)
|
||||
return Py_BuildValue("(NN)", pos, size);
|
||||
}
|
||||
|
||||
// #140 - Python API for on_enter property
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "Profiler.h"
|
||||
#include "PyFOV.h"
|
||||
#include "PyPositionHelper.h" // For standardized position argument parsing
|
||||
#include "PyVector.h" // #179, #181 - For Vector return types
|
||||
#include <algorithm>
|
||||
#include <cmath> // #142 - for std::floor, std::isnan
|
||||
#include <cstring> // #150 - for strcmp
|
||||
|
|
@ -990,8 +991,10 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
|||
return 0; // Success
|
||||
}
|
||||
|
||||
// #179 - Return grid_size as Vector
|
||||
PyObject* UIGrid::get_grid_size(PyUIGridObject* self, void* closure) {
|
||||
return Py_BuildValue("(ii)", self->data->grid_x, self->data->grid_y);
|
||||
return PyVector(sf::Vector2f(static_cast<float>(self->data->grid_x),
|
||||
static_cast<float>(self->data->grid_y))).pyObject();
|
||||
}
|
||||
|
||||
PyObject* UIGrid::get_grid_x(PyUIGridObject* self, void* closure) {
|
||||
|
|
@ -1045,8 +1048,9 @@ int UIGrid::set_size(PyUIGridObject* self, PyObject* value, void* closure) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// #181 - Return center as Vector
|
||||
PyObject* UIGrid::get_center(PyUIGridObject* self, void* closure) {
|
||||
return Py_BuildValue("(ff)", self->data->center_x, self->data->center_y);
|
||||
return PyVector(sf::Vector2f(self->data->center_x, self->data->center_y)).pyObject();
|
||||
}
|
||||
|
||||
int UIGrid::set_center(PyUIGridObject* self, PyObject* value, void* closure) {
|
||||
|
|
@ -3273,20 +3277,11 @@ int UIEntityCollection::init(PyUIEntityCollectionObject* self, PyObject* args, P
|
|||
|
||||
PyObject* UIEntityCollection::iter(PyUIEntityCollectionObject* self)
|
||||
{
|
||||
// Cache the iterator type to avoid repeated dictionary lookups (#159)
|
||||
static PyTypeObject* cached_iter_type = nullptr;
|
||||
if (!cached_iter_type) {
|
||||
cached_iter_type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UIEntityCollectionIter");
|
||||
if (!cached_iter_type) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Could not find UIEntityCollectionIter type in module");
|
||||
return NULL;
|
||||
}
|
||||
// Keep a reference to prevent it from being garbage collected
|
||||
Py_INCREF(cached_iter_type);
|
||||
}
|
||||
// Use the iterator type directly from namespace (#189 - type not exported to module)
|
||||
PyTypeObject* iterType = &mcrfpydef::PyUIEntityCollectionIterType;
|
||||
|
||||
// Allocate new iterator instance
|
||||
PyUIEntityCollectionIterObject* iterObj = (PyUIEntityCollectionIterObject*)cached_iter_type->tp_alloc(cached_iter_type, 0);
|
||||
PyUIEntityCollectionIterObject* iterObj = (PyUIEntityCollectionIterObject*)iterType->tp_alloc(iterType, 0);
|
||||
|
||||
if (iterObj == NULL) {
|
||||
return NULL; // Failed to allocate memory for the iterator object
|
||||
|
|
|
|||
|
|
@ -330,7 +330,8 @@ namespace mcrfpydef {
|
|||
}
|
||||
};
|
||||
|
||||
static PyTypeObject PyUIEntityCollectionIterType = {
|
||||
// #189 - Use inline instead of static to ensure single instance across translation units
|
||||
inline PyTypeObject PyUIEntityCollectionIterType = {
|
||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||
.tp_name = "mcrfpy.UIEntityCollectionIter",
|
||||
.tp_basicsize = sizeof(PyUIEntityCollectionIterObject),
|
||||
|
|
@ -356,7 +357,8 @@ namespace mcrfpydef {
|
|||
}
|
||||
};
|
||||
|
||||
static PyTypeObject PyUIEntityCollectionType = {
|
||||
// #189 - Use inline instead of static to ensure single instance across translation units
|
||||
inline PyTypeObject PyUIEntityCollectionType = {
|
||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||
.tp_name = "mcrfpy.EntityCollection",
|
||||
.tp_basicsize = sizeof(PyUIEntityCollectionObject),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "UIGrid.h"
|
||||
#include "UIEntity.h" // #114 - for GridPoint.entities
|
||||
#include "GridLayers.h" // #150 - for GridLayerType, ColorLayer, TileLayer
|
||||
#include "McRFPy_Doc.h" // #177 - for MCRF_PROPERTY macro
|
||||
#include <cstring> // #150 - for strcmp
|
||||
|
||||
UIGridPoint::UIGridPoint()
|
||||
|
|
@ -22,19 +23,19 @@ sf::Color PyObject_to_sfColor(PyObject* obj) {
|
|||
PyErr_SetString(PyExc_RuntimeError, "Failed to import mcrfpy module");
|
||||
return sf::Color();
|
||||
}
|
||||
|
||||
|
||||
PyObject* color_type = PyObject_GetAttrString(module, "Color");
|
||||
Py_DECREF(module);
|
||||
|
||||
|
||||
if (!color_type) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to get Color type from mcrfpy module");
|
||||
return sf::Color();
|
||||
}
|
||||
|
||||
|
||||
// Check if it's a mcrfpy.Color object
|
||||
int is_color = PyObject_IsInstance(obj, color_type);
|
||||
Py_DECREF(color_type);
|
||||
|
||||
|
||||
if (is_color == 1) {
|
||||
PyColorObject* color_obj = (PyColorObject*)obj;
|
||||
return color_obj->data;
|
||||
|
|
@ -42,7 +43,7 @@ sf::Color PyObject_to_sfColor(PyObject* obj) {
|
|||
// Error occurred in PyObject_IsInstance
|
||||
return sf::Color();
|
||||
}
|
||||
|
||||
|
||||
// Otherwise try to parse as tuple
|
||||
int r, g, b, a = 255; // Default alpha to fully opaque if not specified
|
||||
if (!PyArg_ParseTuple(obj, "iii|i", &r, &g, &b, &a)) {
|
||||
|
|
@ -80,12 +81,12 @@ int UIGridPoint::set_bool_member(PyUIGridPointObject* self, PyObject* value, voi
|
|||
PyErr_SetString(PyExc_ValueError, "Expected a boolean value");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Sync with TCOD map if parent grid exists
|
||||
if (self->data->parent_grid && self->data->grid_x >= 0 && self->data->grid_y >= 0) {
|
||||
self->data->parent_grid->syncTCODMapCell(self->data->grid_x, self->data->grid_y);
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -133,10 +134,17 @@ PyObject* UIGridPoint::get_entities(PyUIGridPointObject* self, void* closure) {
|
|||
return list;
|
||||
}
|
||||
|
||||
// #177 - Get grid position as tuple
|
||||
PyObject* UIGridPoint::get_grid_pos(PyUIGridPointObject* self, void* closure) {
|
||||
return Py_BuildValue("(ii)", self->data->grid_x, self->data->grid_y);
|
||||
}
|
||||
|
||||
PyGetSetDef UIGridPoint::getsetters[] = {
|
||||
{"walkable", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint walkable", (void*)0},
|
||||
{"transparent", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint transparent", (void*)1},
|
||||
{"entities", (getter)UIGridPoint::get_entities, NULL, "List of entities at this grid cell (read-only)", NULL},
|
||||
{"grid_pos", (getter)UIGridPoint::get_grid_pos, NULL,
|
||||
MCRF_PROPERTY(grid_pos, "Grid coordinates as (x, y) tuple (read-only)."), NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ public:
|
|||
// #114 - entities property: list of entities at this cell
|
||||
static PyObject* get_entities(PyUIGridPointObject* self, void* closure);
|
||||
|
||||
// #177 - grid_pos property: grid coordinates as tuple
|
||||
static PyObject* get_grid_pos(PyUIGridPointObject* self, void* closure);
|
||||
|
||||
// #150 - Dynamic property access for named layers
|
||||
static PyObject* getattro(PyUIGridPointObject* self, PyObject* name);
|
||||
static int setattro(PyUIGridPointObject* self, PyObject* name, PyObject* value);
|
||||
|
|
@ -74,7 +77,8 @@ public:
|
|||
};
|
||||
|
||||
namespace mcrfpydef {
|
||||
static PyTypeObject PyUIGridPointType = {
|
||||
// #189 - Use inline instead of static to ensure single instance across translation units
|
||||
inline PyTypeObject PyUIGridPointType = {
|
||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||
.tp_name = "mcrfpy.GridPoint",
|
||||
.tp_basicsize = sizeof(PyUIGridPointObject),
|
||||
|
|
@ -90,7 +94,8 @@ namespace mcrfpydef {
|
|||
.tp_new = NULL, // Prevent instantiation from Python - Issue #12
|
||||
};
|
||||
|
||||
static PyTypeObject PyUIGridPointStateType = {
|
||||
// #189 - Use inline instead of static to ensure single instance across translation units
|
||||
inline PyTypeObject PyUIGridPointStateType = {
|
||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||
.tp_name = "mcrfpy.GridPointState",
|
||||
.tp_basicsize = sizeof(PyUIGridPointStateObject),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue