Implement complete Python Sequence Protocol for collections (closes #69)
Major implementation of the full sequence protocol for both UICollection and UIEntityCollection, making them behave like proper Python sequences. Core Features Implemented: - __setitem__ (collection[i] = value) with type validation - __delitem__ (del collection[i]) with proper cleanup - __contains__ (item in collection) by C++ pointer comparison - __add__ (collection + other) returns Python list - __iadd__ (collection += other) with full validation before modification - Negative indexing support throughout - Complete slice support (getting, setting, deletion) - Extended slices with step \!= 1 - index() and count() methods - Type safety enforced for all operations UICollection specifics: - Accepts Frame, Caption, Sprite, and Grid objects only - Preserves z_index when replacing items - Auto-assigns z_index on append (existing behavior maintained) UIEntityCollection specifics: - Accepts Entity objects only - Manages grid references on add/remove/replace - Uses std::list iteration with std::advance() Also includes: - Default value support for constructors: - Caption accepts None for font (uses default_font) - Grid accepts None for texture (uses default_texture) - Sprite accepts None for texture (uses default_texture) - Entity accepts None for texture (uses default_texture) This completes Issue #69, removing it as an Alpha Blocker. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
38d44777f5
commit
e4482e7189
|
@ -236,7 +236,7 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
|||
|
||||
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf",
|
||||
// const_cast<char**>(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline))
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|zOOOf",
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "Oz|OOOf",
|
||||
const_cast<char**>(keywords), &pos, &text, &font, &fill_color, &outline_color, &outline))
|
||||
{
|
||||
return -1;
|
||||
|
@ -252,10 +252,10 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
|||
// check types for font, fill_color, outline_color
|
||||
|
||||
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl;
|
||||
if (font != NULL && !PyObject_IsInstance(font, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font")/*(PyObject*)&PyFontType)*/)){
|
||||
PyErr_SetString(PyExc_TypeError, "font must be a mcrfpy.Font instance");
|
||||
if (font != NULL && font != Py_None && !PyObject_IsInstance(font, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font")/*(PyObject*)&PyFontType)*/)){
|
||||
PyErr_SetString(PyExc_TypeError, "font must be a mcrfpy.Font instance or None");
|
||||
return -1;
|
||||
} else if (font != NULL)
|
||||
} else if (font != NULL && font != Py_None)
|
||||
{
|
||||
auto font_obj = (PyFontObject*)font;
|
||||
self->data->text.setFont(font_obj->data->font);
|
||||
|
@ -263,8 +263,16 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
|||
Py_INCREF(font);
|
||||
} else
|
||||
{
|
||||
// default font
|
||||
//self->data->text.setFont(Resources::game->getFont());
|
||||
// Use default font when None or not provided
|
||||
if (McRFPy_API::default_font) {
|
||||
self->data->text.setFont(McRFPy_API::default_font->font);
|
||||
// Store reference to default font
|
||||
PyObject* default_font_obj = PyObject_GetAttrString(McRFPy_API::mcrf_module, "default_font");
|
||||
if (default_font_obj) {
|
||||
self->font = default_font_obj;
|
||||
// Don't need to DECREF since we're storing it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self->data->text.setString((std::string)text);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "McRFPy_API.h"
|
||||
#include "PyObjectUtils.h"
|
||||
#include <climits>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace mcrfpydef;
|
||||
|
||||
|
@ -149,15 +150,384 @@ PyObject* UICollection::getitem(PyUICollectionObject* self, Py_ssize_t index) {
|
|||
|
||||
}
|
||||
|
||||
int UICollection::setitem(PyUICollectionObject* self, Py_ssize_t index, PyObject* value) {
|
||||
auto vec = self->data.get();
|
||||
if (!vec) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Handle negative indexing
|
||||
while (index < 0) index += self->data->size();
|
||||
|
||||
// Bounds check
|
||||
if (index >= self->data->size()) {
|
||||
PyErr_SetString(PyExc_IndexError, "UICollection assignment index out of range");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Handle deletion
|
||||
if (value == NULL) {
|
||||
self->data->erase(self->data->begin() + index);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Type checking - must be a UIDrawable subclass
|
||||
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
||||
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
||||
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
|
||||
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||
PyErr_SetString(PyExc_TypeError, "UICollection can only contain Frame, Caption, Sprite, and Grid objects");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get the C++ object from the Python object
|
||||
std::shared_ptr<UIDrawable> new_drawable = nullptr;
|
||||
int old_z_index = (*vec)[index]->z_index; // Preserve the z_index
|
||||
|
||||
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
|
||||
PyUIFrameObject* frame = (PyUIFrameObject*)value;
|
||||
new_drawable = frame->data;
|
||||
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
|
||||
PyUICaptionObject* caption = (PyUICaptionObject*)value;
|
||||
new_drawable = caption->data;
|
||||
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
|
||||
PyUISpriteObject* sprite = (PyUISpriteObject*)value;
|
||||
new_drawable = sprite->data;
|
||||
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||
PyUIGridObject* grid = (PyUIGridObject*)value;
|
||||
new_drawable = grid->data;
|
||||
}
|
||||
|
||||
if (!new_drawable) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to extract C++ object from Python object");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Preserve the z_index of the replaced element
|
||||
new_drawable->z_index = old_z_index;
|
||||
|
||||
// Replace the element
|
||||
(*vec)[index] = new_drawable;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int UICollection::contains(PyUICollectionObject* self, PyObject* value) {
|
||||
auto vec = self->data.get();
|
||||
if (!vec) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Type checking - must be a UIDrawable subclass
|
||||
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
||||
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
||||
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
|
||||
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||
// Not a valid type, so it can't be in the collection
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the C++ object from the Python object
|
||||
std::shared_ptr<UIDrawable> search_drawable = nullptr;
|
||||
|
||||
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
|
||||
PyUIFrameObject* frame = (PyUIFrameObject*)value;
|
||||
search_drawable = frame->data;
|
||||
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
|
||||
PyUICaptionObject* caption = (PyUICaptionObject*)value;
|
||||
search_drawable = caption->data;
|
||||
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
|
||||
PyUISpriteObject* sprite = (PyUISpriteObject*)value;
|
||||
search_drawable = sprite->data;
|
||||
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||
PyUIGridObject* grid = (PyUIGridObject*)value;
|
||||
search_drawable = grid->data;
|
||||
}
|
||||
|
||||
if (!search_drawable) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Search for the object by comparing C++ pointers
|
||||
for (const auto& drawable : *vec) {
|
||||
if (drawable.get() == search_drawable.get()) {
|
||||
return 1; // Found
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // Not found
|
||||
}
|
||||
|
||||
PyObject* UICollection::concat(PyUICollectionObject* self, PyObject* other) {
|
||||
// Create a new Python list containing elements from both collections
|
||||
if (!PySequence_Check(other)) {
|
||||
PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to UICollection");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_ssize_t self_len = self->data->size();
|
||||
Py_ssize_t other_len = PySequence_Length(other);
|
||||
if (other_len == -1) {
|
||||
return NULL; // Error already set
|
||||
}
|
||||
|
||||
PyObject* result_list = PyList_New(self_len + other_len);
|
||||
if (!result_list) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add all elements from self
|
||||
for (Py_ssize_t i = 0; i < self_len; i++) {
|
||||
PyObject* item = convertDrawableToPython((*self->data)[i]);
|
||||
if (!item) {
|
||||
Py_DECREF(result_list);
|
||||
return NULL;
|
||||
}
|
||||
PyList_SET_ITEM(result_list, i, item); // Steals reference
|
||||
}
|
||||
|
||||
// Add all elements from other
|
||||
for (Py_ssize_t i = 0; i < other_len; i++) {
|
||||
PyObject* item = PySequence_GetItem(other, i);
|
||||
if (!item) {
|
||||
Py_DECREF(result_list);
|
||||
return NULL;
|
||||
}
|
||||
PyList_SET_ITEM(result_list, self_len + i, item); // Steals reference
|
||||
}
|
||||
|
||||
return result_list;
|
||||
}
|
||||
|
||||
PyObject* UICollection::inplace_concat(PyUICollectionObject* self, PyObject* other) {
|
||||
if (!PySequence_Check(other)) {
|
||||
PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to UICollection");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// First, validate ALL items in the sequence before modifying anything
|
||||
Py_ssize_t other_len = PySequence_Length(other);
|
||||
if (other_len == -1) {
|
||||
return NULL; // Error already set
|
||||
}
|
||||
|
||||
// Validate all items first
|
||||
for (Py_ssize_t i = 0; i < other_len; i++) {
|
||||
PyObject* item = PySequence_GetItem(other, i);
|
||||
if (!item) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Type check
|
||||
if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
||||
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
||||
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
|
||||
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||
Py_DECREF(item);
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"UICollection can only contain Frame, Caption, Sprite, and Grid objects; "
|
||||
"got %s at index %zd", Py_TYPE(item)->tp_name, i);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(item);
|
||||
}
|
||||
|
||||
// All items validated, now we can safely add them
|
||||
for (Py_ssize_t i = 0; i < other_len; i++) {
|
||||
PyObject* item = PySequence_GetItem(other, i);
|
||||
if (!item) {
|
||||
return NULL; // Shouldn't happen, but be safe
|
||||
}
|
||||
|
||||
// Use the existing append method which handles z_index assignment
|
||||
PyObject* result = append(self, item);
|
||||
Py_DECREF(item);
|
||||
|
||||
if (!result) {
|
||||
return NULL; // append() failed
|
||||
}
|
||||
Py_DECREF(result); // append returns Py_None
|
||||
}
|
||||
|
||||
Py_INCREF(self);
|
||||
return (PyObject*)self;
|
||||
}
|
||||
|
||||
PyObject* UICollection::subscript(PyUICollectionObject* self, PyObject* key) {
|
||||
if (PyLong_Check(key)) {
|
||||
// Single index - delegate to sq_item
|
||||
Py_ssize_t index = PyLong_AsSsize_t(key);
|
||||
if (index == -1 && PyErr_Occurred()) {
|
||||
return NULL;
|
||||
}
|
||||
return getitem(self, index);
|
||||
} else if (PySlice_Check(key)) {
|
||||
// Handle slice
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
|
||||
if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject* result_list = PyList_New(slicelength);
|
||||
if (!result_list) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
|
||||
PyObject* item = convertDrawableToPython((*self->data)[cur]);
|
||||
if (!item) {
|
||||
Py_DECREF(result_list);
|
||||
return NULL;
|
||||
}
|
||||
PyList_SET_ITEM(result_list, i, item); // Steals reference
|
||||
}
|
||||
|
||||
return result_list;
|
||||
} else {
|
||||
PyErr_Format(PyExc_TypeError, "UICollection indices must be integers or slices, not %.200s",
|
||||
Py_TYPE(key)->tp_name);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int UICollection::ass_subscript(PyUICollectionObject* self, PyObject* key, PyObject* value) {
|
||||
if (PyLong_Check(key)) {
|
||||
// Single index - delegate to sq_ass_item
|
||||
Py_ssize_t index = PyLong_AsSsize_t(key);
|
||||
if (index == -1 && PyErr_Occurred()) {
|
||||
return -1;
|
||||
}
|
||||
return setitem(self, index, value);
|
||||
} else if (PySlice_Check(key)) {
|
||||
// Handle slice assignment/deletion
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
|
||||
if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (value == NULL) {
|
||||
// Deletion
|
||||
if (step != 1) {
|
||||
// For non-contiguous slices, delete from highest to lowest to maintain indices
|
||||
std::vector<Py_ssize_t> indices;
|
||||
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
|
||||
indices.push_back(cur);
|
||||
}
|
||||
// Sort in descending order and delete
|
||||
std::sort(indices.begin(), indices.end(), std::greater<Py_ssize_t>());
|
||||
for (Py_ssize_t idx : indices) {
|
||||
self->data->erase(self->data->begin() + idx);
|
||||
}
|
||||
} else {
|
||||
// Contiguous slice - can delete in one go
|
||||
self->data->erase(self->data->begin() + start, self->data->begin() + stop);
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
// Assignment
|
||||
if (!PySequence_Check(value)) {
|
||||
PyErr_SetString(PyExc_TypeError, "can only assign sequence to slice");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_ssize_t value_len = PySequence_Length(value);
|
||||
if (value_len == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Validate all items first
|
||||
std::vector<std::shared_ptr<UIDrawable>> new_items;
|
||||
for (Py_ssize_t i = 0; i < value_len; i++) {
|
||||
PyObject* item = PySequence_GetItem(value, i);
|
||||
if (!item) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Type check and extract C++ object
|
||||
std::shared_ptr<UIDrawable> drawable = nullptr;
|
||||
|
||||
if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
|
||||
drawable = ((PyUIFrameObject*)item)->data;
|
||||
} else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
|
||||
drawable = ((PyUICaptionObject*)item)->data;
|
||||
} else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
|
||||
drawable = ((PyUISpriteObject*)item)->data;
|
||||
} else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||
drawable = ((PyUIGridObject*)item)->data;
|
||||
} else {
|
||||
Py_DECREF(item);
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"UICollection can only contain Frame, Caption, Sprite, and Grid objects; "
|
||||
"got %s at index %zd", Py_TYPE(item)->tp_name, i);
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_DECREF(item);
|
||||
new_items.push_back(drawable);
|
||||
}
|
||||
|
||||
// Now perform the assignment
|
||||
if (step == 1) {
|
||||
// Contiguous slice
|
||||
if (slicelength != value_len) {
|
||||
// Need to resize
|
||||
auto it_start = self->data->begin() + start;
|
||||
auto it_stop = self->data->begin() + stop;
|
||||
self->data->erase(it_start, it_stop);
|
||||
self->data->insert(self->data->begin() + start, new_items.begin(), new_items.end());
|
||||
} else {
|
||||
// Same size, just replace
|
||||
for (Py_ssize_t i = 0; i < slicelength; i++) {
|
||||
// Preserve z_index
|
||||
new_items[i]->z_index = (*self->data)[start + i]->z_index;
|
||||
(*self->data)[start + i] = new_items[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Extended slice
|
||||
if (slicelength != value_len) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"attempt to assign sequence of size %zd to extended slice of size %zd",
|
||||
value_len, slicelength);
|
||||
return -1;
|
||||
}
|
||||
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
|
||||
// Preserve z_index
|
||||
new_items[i]->z_index = (*self->data)[cur]->z_index;
|
||||
(*self->data)[cur] = new_items[i];
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
PyErr_Format(PyExc_TypeError, "UICollection indices must be integers or slices, not %.200s",
|
||||
Py_TYPE(key)->tp_name);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
PyMappingMethods UICollection::mpmethods = {
|
||||
.mp_length = (lenfunc)UICollection::len,
|
||||
.mp_subscript = (binaryfunc)UICollection::subscript,
|
||||
.mp_ass_subscript = (objobjargproc)UICollection::ass_subscript
|
||||
};
|
||||
|
||||
PySequenceMethods UICollection::sqmethods = {
|
||||
.sq_length = (lenfunc)UICollection::len,
|
||||
.sq_concat = (binaryfunc)UICollection::concat,
|
||||
.sq_repeat = NULL,
|
||||
.sq_item = (ssizeargfunc)UICollection::getitem,
|
||||
//.sq_item_by_index = PyUICollection_getitem
|
||||
//.sq_slice - return a subset of the iterable
|
||||
//.sq_ass_item - called when `o[x] = y` is executed (x is any object type)
|
||||
//.sq_ass_slice - cool; no thanks, for now
|
||||
//.sq_contains - called when `x in o` is executed
|
||||
//.sq_ass_item_by_index - called when `o[x] = y` is executed (x is explictly an integer)
|
||||
.was_sq_slice = NULL,
|
||||
.sq_ass_item = (ssizeobjargproc)UICollection::setitem,
|
||||
.was_sq_ass_slice = NULL,
|
||||
.sq_contains = (objobjproc)UICollection::contains,
|
||||
.sq_inplace_concat = (binaryfunc)UICollection::inplace_concat,
|
||||
.sq_inplace_repeat = NULL
|
||||
};
|
||||
|
||||
/* Idiomatic way to fetch complete types from the API rather than referencing their PyTypeObject struct
|
||||
|
@ -240,16 +610,15 @@ PyObject* UICollection::remove(PyUICollectionObject* self, PyObject* o)
|
|||
return NULL;
|
||||
}
|
||||
long index = PyLong_AsLong(o);
|
||||
|
||||
// Handle negative indexing
|
||||
while (index < 0) index += self->data->size();
|
||||
|
||||
if (index >= self->data->size())
|
||||
{
|
||||
PyErr_SetString(PyExc_ValueError, "Index out of range");
|
||||
return NULL;
|
||||
}
|
||||
else if (index < 0)
|
||||
{
|
||||
PyErr_SetString(PyExc_NotImplementedError, "reverse indexing is not implemented.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// release the shared pointer at self->data[index];
|
||||
self->data->erase(self->data->begin() + index);
|
||||
|
@ -257,10 +626,101 @@ PyObject* UICollection::remove(PyUICollectionObject* self, PyObject* o)
|
|||
return Py_None;
|
||||
}
|
||||
|
||||
PyObject* UICollection::index_method(PyUICollectionObject* self, PyObject* value) {
|
||||
auto vec = self->data.get();
|
||||
if (!vec) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Type checking - must be a UIDrawable subclass
|
||||
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
||||
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
||||
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
|
||||
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||
PyErr_SetString(PyExc_TypeError, "UICollection.index requires a Frame, Caption, Sprite, or Grid object");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get the C++ object from the Python object
|
||||
std::shared_ptr<UIDrawable> search_drawable = nullptr;
|
||||
|
||||
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
|
||||
search_drawable = ((PyUIFrameObject*)value)->data;
|
||||
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
|
||||
search_drawable = ((PyUICaptionObject*)value)->data;
|
||||
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
|
||||
search_drawable = ((PyUISpriteObject*)value)->data;
|
||||
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||
search_drawable = ((PyUIGridObject*)value)->data;
|
||||
}
|
||||
|
||||
if (!search_drawable) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to extract C++ object from Python object");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Search for the object
|
||||
for (size_t i = 0; i < vec->size(); i++) {
|
||||
if ((*vec)[i].get() == search_drawable.get()) {
|
||||
return PyLong_FromSsize_t(i);
|
||||
}
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_ValueError, "value not in UICollection");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject* UICollection::count(PyUICollectionObject* self, PyObject* value) {
|
||||
auto vec = self->data.get();
|
||||
if (!vec) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Type checking - must be a UIDrawable subclass
|
||||
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
||||
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
||||
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
|
||||
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||
// Not a valid type, so count is 0
|
||||
return PyLong_FromLong(0);
|
||||
}
|
||||
|
||||
// Get the C++ object from the Python object
|
||||
std::shared_ptr<UIDrawable> search_drawable = nullptr;
|
||||
|
||||
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
|
||||
search_drawable = ((PyUIFrameObject*)value)->data;
|
||||
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
|
||||
search_drawable = ((PyUICaptionObject*)value)->data;
|
||||
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
|
||||
search_drawable = ((PyUISpriteObject*)value)->data;
|
||||
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||
search_drawable = ((PyUIGridObject*)value)->data;
|
||||
}
|
||||
|
||||
if (!search_drawable) {
|
||||
return PyLong_FromLong(0);
|
||||
}
|
||||
|
||||
// Count occurrences
|
||||
Py_ssize_t count = 0;
|
||||
for (const auto& drawable : *vec) {
|
||||
if (drawable.get() == search_drawable.get()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return PyLong_FromSsize_t(count);
|
||||
}
|
||||
|
||||
PyMethodDef UICollection::methods[] = {
|
||||
{"append", (PyCFunction)UICollection::append, METH_O},
|
||||
//{"extend", (PyCFunction)PyUICollection_extend, METH_O}, // TODO
|
||||
{"remove", (PyCFunction)UICollection::remove, METH_O},
|
||||
{"index", (PyCFunction)UICollection::index_method, METH_O},
|
||||
{"count", (PyCFunction)UICollection::count, METH_O},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
|
|
|
@ -19,9 +19,18 @@ class UICollection
|
|||
public:
|
||||
static Py_ssize_t len(PyUICollectionObject* self);
|
||||
static PyObject* getitem(PyUICollectionObject* self, Py_ssize_t index);
|
||||
static int setitem(PyUICollectionObject* self, Py_ssize_t index, PyObject* value);
|
||||
static int contains(PyUICollectionObject* self, PyObject* value);
|
||||
static PyObject* concat(PyUICollectionObject* self, PyObject* other);
|
||||
static PyObject* inplace_concat(PyUICollectionObject* self, PyObject* other);
|
||||
static PySequenceMethods sqmethods;
|
||||
static PyMappingMethods mpmethods;
|
||||
static PyObject* subscript(PyUICollectionObject* self, PyObject* key);
|
||||
static int ass_subscript(PyUICollectionObject* self, PyObject* key, PyObject* value);
|
||||
static PyObject* append(PyUICollectionObject* self, PyObject* o);
|
||||
static PyObject* remove(PyUICollectionObject* self, PyObject* o);
|
||||
static PyObject* index_method(PyUICollectionObject* self, PyObject* value);
|
||||
static PyObject* count(PyUICollectionObject* self, PyObject* value);
|
||||
static PyMethodDef methods[];
|
||||
static PyObject* repr(PyUICollectionObject* self);
|
||||
static int init(PyUICollectionObject* self, PyObject* args, PyObject* kwds);
|
||||
|
@ -71,6 +80,7 @@ namespace mcrfpydef {
|
|||
},
|
||||
.tp_repr = (reprfunc)UICollection::repr,
|
||||
.tp_as_sequence = &UICollection::sqmethods,
|
||||
.tp_as_mapping = &UICollection::mpmethods,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_doc = PyDoc_STR("Iterable, indexable collection of UI objects"),
|
||||
.tp_iter = (getiterfunc)UICollection::iter,
|
||||
|
|
|
@ -75,7 +75,7 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
|||
|
||||
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O",
|
||||
// const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &grid))
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOi|O",
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OiO",
|
||||
const_cast<char**>(keywords), &pos, &texture, &sprite_index, &grid))
|
||||
{
|
||||
return -1;
|
||||
|
@ -90,33 +90,37 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
|||
|
||||
// check types for texture
|
||||
//
|
||||
// Set Texture
|
||||
// Set Texture - allow None or use default
|
||||
//
|
||||
if (texture != NULL && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){
|
||||
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance");
|
||||
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
||||
if (texture != NULL && texture != Py_None && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){
|
||||
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance or None");
|
||||
return -1;
|
||||
} /*else if (texture != NULL) // this section needs to go; texture isn't optional and isn't managed by the UI objects anymore
|
||||
{
|
||||
self->texture = texture;
|
||||
Py_INCREF(texture);
|
||||
} else
|
||||
{
|
||||
// default tex?
|
||||
}*/
|
||||
} else if (texture != NULL && texture != Py_None) {
|
||||
auto pytexture = (PyTextureObject*)texture;
|
||||
texture_ptr = pytexture->data;
|
||||
} else {
|
||||
// Use default texture when None or not provided
|
||||
texture_ptr = McRFPy_API::default_texture;
|
||||
}
|
||||
|
||||
if (!texture_ptr) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "No texture provided and no default texture available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (grid != NULL && !PyObject_IsInstance(grid, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||
PyErr_SetString(PyExc_TypeError, "grid must be a mcrfpy.Grid instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto pytexture = (PyTextureObject*)texture;
|
||||
if (grid == NULL)
|
||||
self->data = std::make_shared<UIEntity>();
|
||||
else
|
||||
self->data = std::make_shared<UIEntity>(*((PyUIGridObject*)grid)->data);
|
||||
|
||||
// TODO - PyTextureObjects and IndexTextures are a little bit of a mess with shared/unshared pointers
|
||||
self->data->sprite = UISprite(pytexture->data, sprite_index, sf::Vector2f(0,0), 1.0);
|
||||
self->data->sprite = UISprite(texture_ptr, sprite_index, sf::Vector2f(0,0), 1.0);
|
||||
self->data->position = pos_result->data;
|
||||
if (grid != NULL) {
|
||||
PyUIGridObject* pygrid = (PyUIGridObject*)grid;
|
||||
|
|
535
src/UIGrid.cpp
535
src/UIGrid.cpp
|
@ -1,6 +1,7 @@
|
|||
#include "UIGrid.h"
|
||||
#include "GameEngine.h"
|
||||
#include "McRFPy_API.h"
|
||||
#include <algorithm>
|
||||
|
||||
UIGrid::UIGrid() {}
|
||||
|
||||
|
@ -218,35 +219,74 @@ UIDrawable* UIGrid::click_at(sf::Vector2f point)
|
|||
|
||||
int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||
int grid_x, grid_y;
|
||||
PyObject* textureObj;
|
||||
PyObject* textureObj = Py_None;
|
||||
//float box_x, box_y, box_w, box_h;
|
||||
PyObject* pos, *size;
|
||||
PyObject* pos = NULL;
|
||||
PyObject* size = NULL;
|
||||
|
||||
//if (!PyArg_ParseTuple(args, "iiOffff", &grid_x, &grid_y, &textureObj, &box_x, &box_y, &box_w, &box_h)) {
|
||||
if (!PyArg_ParseTuple(args, "iiOOO", &grid_x, &grid_y, &textureObj, &pos, &size)) {
|
||||
if (!PyArg_ParseTuple(args, "ii|OOO", &grid_x, &grid_y, &textureObj, &pos, &size)) {
|
||||
return -1; // If parsing fails, return an error
|
||||
}
|
||||
|
||||
PyVectorObject* pos_result = PyVector::from_arg(pos);
|
||||
// Default position and size if not provided
|
||||
PyVectorObject* pos_result = NULL;
|
||||
PyVectorObject* size_result = NULL;
|
||||
|
||||
if (pos) {
|
||||
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 {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
PyVectorObject* size_result = PyVector::from_arg(size);
|
||||
if (size) {
|
||||
size_result = PyVector::from_arg(size);
|
||||
if (!size_result)
|
||||
{
|
||||
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
||||
PyErr_SetString(PyExc_TypeError, "size must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
// Default size based on grid dimensions
|
||||
float default_w = grid_x * 16.0f; // Assuming 16 pixel tiles
|
||||
float default_h = grid_y * 16.0f;
|
||||
PyObject* vector_class = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
||||
if (vector_class) {
|
||||
PyObject* size_obj = PyObject_CallFunction(vector_class, "ff", default_w, default_h);
|
||||
Py_DECREF(vector_class);
|
||||
if (size_obj) {
|
||||
size_result = (PyVectorObject*)size_obj;
|
||||
}
|
||||
}
|
||||
if (!size_result) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to create default size vector");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert PyObject texture to IndexTexture*
|
||||
// This requires the texture object to have been initialized similar to UISprite's texture handling
|
||||
|
||||
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
||||
|
||||
// Allow None for texture
|
||||
// Allow None for texture - use default texture in that case
|
||||
if (textureObj != Py_None) {
|
||||
//if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) {
|
||||
if (!PyObject_IsInstance(textureObj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) {
|
||||
|
@ -255,6 +295,9 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
|||
}
|
||||
PyTextureObject* pyTexture = reinterpret_cast<PyTextureObject*>(textureObj);
|
||||
texture_ptr = pyTexture->data;
|
||||
} else {
|
||||
// Use default texture when None is provided
|
||||
texture_ptr = McRFPy_API::default_texture;
|
||||
}
|
||||
|
||||
// Initialize UIGrid - texture_ptr will be nullptr if texture was None
|
||||
|
@ -582,15 +625,196 @@ return NULL;
|
|||
|
||||
}
|
||||
|
||||
int UIEntityCollection::setitem(PyUIEntityCollectionObject* self, Py_ssize_t index, PyObject* value) {
|
||||
auto list = self->data.get();
|
||||
if (!list) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Handle negative indexing
|
||||
while (index < 0) index += list->size();
|
||||
|
||||
// Bounds check
|
||||
if (index >= list->size()) {
|
||||
PyErr_SetString(PyExc_IndexError, "EntityCollection assignment index out of range");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get iterator to the target position
|
||||
auto it = list->begin();
|
||||
std::advance(it, index);
|
||||
|
||||
// Handle deletion
|
||||
if (value == NULL) {
|
||||
// Clear grid reference from the entity being removed
|
||||
(*it)->grid = nullptr;
|
||||
list->erase(it);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Type checking - must be an Entity
|
||||
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||
PyErr_SetString(PyExc_TypeError, "EntityCollection can only contain Entity objects");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get the C++ object from the Python object
|
||||
PyUIEntityObject* entity = (PyUIEntityObject*)value;
|
||||
if (!entity->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Invalid Entity object");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Clear grid reference from the old entity
|
||||
(*it)->grid = nullptr;
|
||||
|
||||
// Replace the element and set grid reference
|
||||
*it = entity->data;
|
||||
entity->data->grid = self->grid;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int UIEntityCollection::contains(PyUIEntityCollectionObject* self, PyObject* value) {
|
||||
auto list = self->data.get();
|
||||
if (!list) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Type checking - must be an Entity
|
||||
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||
// Not an Entity, so it can't be in the collection
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the C++ object from the Python object
|
||||
PyUIEntityObject* entity = (PyUIEntityObject*)value;
|
||||
if (!entity->data) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Search for the object by comparing C++ pointers
|
||||
for (const auto& ent : *list) {
|
||||
if (ent.get() == entity->data.get()) {
|
||||
return 1; // Found
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // Not found
|
||||
}
|
||||
|
||||
PyObject* UIEntityCollection::concat(PyUIEntityCollectionObject* self, PyObject* other) {
|
||||
// Create a new Python list containing elements from both collections
|
||||
if (!PySequence_Check(other)) {
|
||||
PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to EntityCollection");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_ssize_t self_len = self->data->size();
|
||||
Py_ssize_t other_len = PySequence_Length(other);
|
||||
if (other_len == -1) {
|
||||
return NULL; // Error already set
|
||||
}
|
||||
|
||||
PyObject* result_list = PyList_New(self_len + other_len);
|
||||
if (!result_list) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add all elements from self
|
||||
Py_ssize_t idx = 0;
|
||||
for (const auto& entity : *self->data) {
|
||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
|
||||
auto obj = (PyUIEntityObject*)type->tp_alloc(type, 0);
|
||||
if (obj) {
|
||||
obj->data = entity;
|
||||
PyList_SET_ITEM(result_list, idx, (PyObject*)obj); // Steals reference
|
||||
} else {
|
||||
Py_DECREF(result_list);
|
||||
Py_DECREF(type);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(type);
|
||||
idx++;
|
||||
}
|
||||
|
||||
// Add all elements from other
|
||||
for (Py_ssize_t i = 0; i < other_len; i++) {
|
||||
PyObject* item = PySequence_GetItem(other, i);
|
||||
if (!item) {
|
||||
Py_DECREF(result_list);
|
||||
return NULL;
|
||||
}
|
||||
PyList_SET_ITEM(result_list, self_len + i, item); // Steals reference
|
||||
}
|
||||
|
||||
return result_list;
|
||||
}
|
||||
|
||||
PyObject* UIEntityCollection::inplace_concat(PyUIEntityCollectionObject* self, PyObject* other) {
|
||||
if (!PySequence_Check(other)) {
|
||||
PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to EntityCollection");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// First, validate ALL items in the sequence before modifying anything
|
||||
Py_ssize_t other_len = PySequence_Length(other);
|
||||
if (other_len == -1) {
|
||||
return NULL; // Error already set
|
||||
}
|
||||
|
||||
// Validate all items first
|
||||
for (Py_ssize_t i = 0; i < other_len; i++) {
|
||||
PyObject* item = PySequence_GetItem(other, i);
|
||||
if (!item) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Type check
|
||||
if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||
Py_DECREF(item);
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"EntityCollection can only contain Entity objects; "
|
||||
"got %s at index %zd", Py_TYPE(item)->tp_name, i);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(item);
|
||||
}
|
||||
|
||||
// All items validated, now we can safely add them
|
||||
for (Py_ssize_t i = 0; i < other_len; i++) {
|
||||
PyObject* item = PySequence_GetItem(other, i);
|
||||
if (!item) {
|
||||
return NULL; // Shouldn't happen, but be safe
|
||||
}
|
||||
|
||||
// Use the existing append method which handles grid references
|
||||
PyObject* result = append(self, item);
|
||||
Py_DECREF(item);
|
||||
|
||||
if (!result) {
|
||||
return NULL; // append() failed
|
||||
}
|
||||
Py_DECREF(result); // append returns Py_None
|
||||
}
|
||||
|
||||
Py_INCREF(self);
|
||||
return (PyObject*)self;
|
||||
}
|
||||
|
||||
PySequenceMethods UIEntityCollection::sqmethods = {
|
||||
.sq_length = (lenfunc)UIEntityCollection::len,
|
||||
.sq_concat = (binaryfunc)UIEntityCollection::concat,
|
||||
.sq_repeat = NULL,
|
||||
.sq_item = (ssizeargfunc)UIEntityCollection::getitem,
|
||||
//.sq_item_by_index = UIEntityCollection::getitem
|
||||
//.sq_slice - return a subset of the iterable
|
||||
//.sq_ass_item - called when `o[x] = y` is executed (x is any object type)
|
||||
//.sq_ass_slice - cool; no thanks, for now
|
||||
//.sq_contains - called when `x in o` is executed
|
||||
//.sq_ass_item_by_index - called when `o[x] = y` is executed (x is explictly an integer)
|
||||
.was_sq_slice = NULL,
|
||||
.sq_ass_item = (ssizeobjargproc)UIEntityCollection::setitem,
|
||||
.was_sq_ass_slice = NULL,
|
||||
.sq_contains = (objobjproc)UIEntityCollection::contains,
|
||||
.sq_inplace_concat = (binaryfunc)UIEntityCollection::inplace_concat,
|
||||
.sq_inplace_repeat = NULL
|
||||
};
|
||||
|
||||
PyObject* UIEntityCollection::append(PyUIEntityCollectionObject* self, PyObject* o)
|
||||
|
@ -617,23 +841,29 @@ PyObject* UIEntityCollection::remove(PyUIEntityCollectionObject* self, PyObject*
|
|||
{
|
||||
if (!PyLong_Check(o))
|
||||
{
|
||||
PyErr_SetString(PyExc_TypeError, "UICollection.remove requires an integer index to remove");
|
||||
PyErr_SetString(PyExc_TypeError, "EntityCollection.remove requires an integer index to remove");
|
||||
return NULL;
|
||||
}
|
||||
long index = PyLong_AsLong(o);
|
||||
|
||||
// Handle negative indexing
|
||||
while (index < 0) index += self->data->size();
|
||||
|
||||
if (index >= self->data->size())
|
||||
{
|
||||
PyErr_SetString(PyExc_ValueError, "Index out of range");
|
||||
return NULL;
|
||||
}
|
||||
else if (index < 0)
|
||||
{
|
||||
PyErr_SetString(PyExc_NotImplementedError, "reverse indexing is not implemented.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get iterator to the entity to remove
|
||||
auto it = self->data->begin();
|
||||
std::advance(it, index);
|
||||
|
||||
// Clear grid reference before removing
|
||||
(*it)->grid = nullptr;
|
||||
|
||||
// release the shared pointer at correct part of the list
|
||||
self->data->erase(std::next(self->data->begin(), index));
|
||||
self->data->erase(it);
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
@ -676,10 +906,275 @@ PyObject* UIEntityCollection::extend(PyUIEntityCollectionObject* self, PyObject*
|
|||
return Py_None;
|
||||
}
|
||||
|
||||
PyObject* UIEntityCollection::index_method(PyUIEntityCollectionObject* self, PyObject* value) {
|
||||
auto list = self->data.get();
|
||||
if (!list) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Type checking - must be an Entity
|
||||
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||
PyErr_SetString(PyExc_TypeError, "EntityCollection.index requires an Entity object");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get the C++ object from the Python object
|
||||
PyUIEntityObject* entity = (PyUIEntityObject*)value;
|
||||
if (!entity->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Invalid Entity object");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Search for the object
|
||||
Py_ssize_t idx = 0;
|
||||
for (const auto& ent : *list) {
|
||||
if (ent.get() == entity->data.get()) {
|
||||
return PyLong_FromSsize_t(idx);
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_ValueError, "Entity not in EntityCollection");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject* UIEntityCollection::count(PyUIEntityCollectionObject* self, PyObject* value) {
|
||||
auto list = self->data.get();
|
||||
if (!list) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Type checking - must be an Entity
|
||||
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||
// Not an Entity, so count is 0
|
||||
return PyLong_FromLong(0);
|
||||
}
|
||||
|
||||
// Get the C++ object from the Python object
|
||||
PyUIEntityObject* entity = (PyUIEntityObject*)value;
|
||||
if (!entity->data) {
|
||||
return PyLong_FromLong(0);
|
||||
}
|
||||
|
||||
// Count occurrences
|
||||
Py_ssize_t count = 0;
|
||||
for (const auto& ent : *list) {
|
||||
if (ent.get() == entity->data.get()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return PyLong_FromSsize_t(count);
|
||||
}
|
||||
|
||||
PyObject* UIEntityCollection::subscript(PyUIEntityCollectionObject* self, PyObject* key) {
|
||||
if (PyLong_Check(key)) {
|
||||
// Single index - delegate to sq_item
|
||||
Py_ssize_t index = PyLong_AsSsize_t(key);
|
||||
if (index == -1 && PyErr_Occurred()) {
|
||||
return NULL;
|
||||
}
|
||||
return getitem(self, index);
|
||||
} else if (PySlice_Check(key)) {
|
||||
// Handle slice
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
|
||||
if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject* result_list = PyList_New(slicelength);
|
||||
if (!result_list) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Iterate through the list with slice parameters
|
||||
auto it = self->data->begin();
|
||||
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
|
||||
auto cur_it = it;
|
||||
std::advance(cur_it, cur);
|
||||
|
||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
|
||||
auto obj = (PyUIEntityObject*)type->tp_alloc(type, 0);
|
||||
if (obj) {
|
||||
obj->data = *cur_it;
|
||||
PyList_SET_ITEM(result_list, i, (PyObject*)obj); // Steals reference
|
||||
} else {
|
||||
Py_DECREF(result_list);
|
||||
Py_DECREF(type);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(type);
|
||||
}
|
||||
|
||||
return result_list;
|
||||
} else {
|
||||
PyErr_Format(PyExc_TypeError, "EntityCollection indices must be integers or slices, not %.200s",
|
||||
Py_TYPE(key)->tp_name);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int UIEntityCollection::ass_subscript(PyUIEntityCollectionObject* self, PyObject* key, PyObject* value) {
|
||||
if (PyLong_Check(key)) {
|
||||
// Single index - delegate to sq_ass_item
|
||||
Py_ssize_t index = PyLong_AsSsize_t(key);
|
||||
if (index == -1 && PyErr_Occurred()) {
|
||||
return -1;
|
||||
}
|
||||
return setitem(self, index, value);
|
||||
} else if (PySlice_Check(key)) {
|
||||
// Handle slice assignment/deletion
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
|
||||
if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (value == NULL) {
|
||||
// Deletion
|
||||
if (step != 1) {
|
||||
// For non-contiguous slices, delete from highest to lowest to maintain indices
|
||||
std::vector<Py_ssize_t> indices;
|
||||
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
|
||||
indices.push_back(cur);
|
||||
}
|
||||
// Sort in descending order
|
||||
std::sort(indices.begin(), indices.end(), std::greater<Py_ssize_t>());
|
||||
|
||||
// Delete each index
|
||||
for (Py_ssize_t idx : indices) {
|
||||
auto it = self->data->begin();
|
||||
std::advance(it, idx);
|
||||
(*it)->grid = nullptr; // Clear grid reference
|
||||
self->data->erase(it);
|
||||
}
|
||||
} else {
|
||||
// Contiguous slice - delete range
|
||||
auto it_start = self->data->begin();
|
||||
auto it_stop = self->data->begin();
|
||||
std::advance(it_start, start);
|
||||
std::advance(it_stop, stop);
|
||||
|
||||
// Clear grid references
|
||||
for (auto it = it_start; it != it_stop; ++it) {
|
||||
(*it)->grid = nullptr;
|
||||
}
|
||||
|
||||
self->data->erase(it_start, it_stop);
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
// Assignment
|
||||
if (!PySequence_Check(value)) {
|
||||
PyErr_SetString(PyExc_TypeError, "can only assign sequence to slice");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_ssize_t value_len = PySequence_Length(value);
|
||||
if (value_len == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Validate all items first
|
||||
std::vector<std::shared_ptr<UIEntity>> new_items;
|
||||
for (Py_ssize_t i = 0; i < value_len; i++) {
|
||||
PyObject* item = PySequence_GetItem(value, i);
|
||||
if (!item) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Type check
|
||||
if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||
Py_DECREF(item);
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"EntityCollection can only contain Entity objects; "
|
||||
"got %s at index %zd", Py_TYPE(item)->tp_name, i);
|
||||
return -1;
|
||||
}
|
||||
|
||||
PyUIEntityObject* entity = (PyUIEntityObject*)item;
|
||||
Py_DECREF(item);
|
||||
new_items.push_back(entity->data);
|
||||
}
|
||||
|
||||
// Now perform the assignment
|
||||
if (step == 1) {
|
||||
// Contiguous slice
|
||||
if (slicelength != value_len) {
|
||||
// Need to resize - remove old items and insert new ones
|
||||
auto it_start = self->data->begin();
|
||||
auto it_stop = self->data->begin();
|
||||
std::advance(it_start, start);
|
||||
std::advance(it_stop, stop);
|
||||
|
||||
// Clear grid references from old items
|
||||
for (auto it = it_start; it != it_stop; ++it) {
|
||||
(*it)->grid = nullptr;
|
||||
}
|
||||
|
||||
// Erase old range
|
||||
it_start = self->data->erase(it_start, it_stop);
|
||||
|
||||
// Insert new items
|
||||
for (const auto& entity : new_items) {
|
||||
entity->grid = self->grid;
|
||||
it_start = self->data->insert(it_start, entity);
|
||||
++it_start;
|
||||
}
|
||||
} else {
|
||||
// Same size, just replace
|
||||
auto it = self->data->begin();
|
||||
std::advance(it, start);
|
||||
for (const auto& entity : new_items) {
|
||||
(*it)->grid = nullptr; // Clear old grid ref
|
||||
*it = entity;
|
||||
entity->grid = self->grid; // Set new grid ref
|
||||
++it;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Extended slice
|
||||
if (slicelength != value_len) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"attempt to assign sequence of size %zd to extended slice of size %zd",
|
||||
value_len, slicelength);
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto list_it = self->data->begin();
|
||||
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
|
||||
auto cur_it = list_it;
|
||||
std::advance(cur_it, cur);
|
||||
(*cur_it)->grid = nullptr; // Clear old grid ref
|
||||
*cur_it = new_items[i];
|
||||
new_items[i]->grid = self->grid; // Set new grid ref
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
PyErr_Format(PyExc_TypeError, "EntityCollection indices must be integers or slices, not %.200s",
|
||||
Py_TYPE(key)->tp_name);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
PyMappingMethods UIEntityCollection::mpmethods = {
|
||||
.mp_length = (lenfunc)UIEntityCollection::len,
|
||||
.mp_subscript = (binaryfunc)UIEntityCollection::subscript,
|
||||
.mp_ass_subscript = (objobjargproc)UIEntityCollection::ass_subscript
|
||||
};
|
||||
|
||||
PyMethodDef UIEntityCollection::methods[] = {
|
||||
{"append", (PyCFunction)UIEntityCollection::append, METH_O},
|
||||
{"extend", (PyCFunction)UIEntityCollection::extend, METH_O},
|
||||
{"remove", (PyCFunction)UIEntityCollection::remove, METH_O},
|
||||
{"index", (PyCFunction)UIEntityCollection::index_method, METH_O},
|
||||
{"count", (PyCFunction)UIEntityCollection::count, METH_O},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
|
|
10
src/UIGrid.h
10
src/UIGrid.h
|
@ -82,15 +82,24 @@ typedef struct {
|
|||
class UIEntityCollection {
|
||||
public:
|
||||
static PySequenceMethods sqmethods;
|
||||
static PyMappingMethods mpmethods;
|
||||
static PyObject* append(PyUIEntityCollectionObject* self, PyObject* o);
|
||||
static PyObject* extend(PyUIEntityCollectionObject* self, PyObject* o);
|
||||
static PyObject* remove(PyUIEntityCollectionObject* self, PyObject* o);
|
||||
static PyObject* index_method(PyUIEntityCollectionObject* self, PyObject* value);
|
||||
static PyObject* count(PyUIEntityCollectionObject* self, PyObject* value);
|
||||
static PyMethodDef methods[];
|
||||
static PyObject* repr(PyUIEntityCollectionObject* self);
|
||||
static int init(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* iter(PyUIEntityCollectionObject* self);
|
||||
static Py_ssize_t len(PyUIEntityCollectionObject* self);
|
||||
static PyObject* getitem(PyUIEntityCollectionObject* self, Py_ssize_t index);
|
||||
static int setitem(PyUIEntityCollectionObject* self, Py_ssize_t index, PyObject* value);
|
||||
static int contains(PyUIEntityCollectionObject* self, PyObject* value);
|
||||
static PyObject* concat(PyUIEntityCollectionObject* self, PyObject* other);
|
||||
static PyObject* inplace_concat(PyUIEntityCollectionObject* self, PyObject* other);
|
||||
static PyObject* subscript(PyUIEntityCollectionObject* self, PyObject* key);
|
||||
static int ass_subscript(PyUIEntityCollectionObject* self, PyObject* key, PyObject* value);
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
|
@ -180,6 +189,7 @@ namespace mcrfpydef {
|
|||
},
|
||||
.tp_repr = (reprfunc)UIEntityCollection::repr,
|
||||
.tp_as_sequence = &UIEntityCollection::sqmethods,
|
||||
.tp_as_mapping = &UIEntityCollection::mpmethods,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_doc = PyDoc_STR("Iterable, indexable collection of Entities"),
|
||||
.tp_iter = (getiterfunc)UIEntityCollection::iter,
|
||||
|
|
|
@ -225,8 +225,8 @@ int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds)
|
|||
//std::cout << "Init called\n";
|
||||
static const char* keywords[] = { "x", "y", "texture", "sprite_index", "scale", nullptr };
|
||||
float x = 0.0f, y = 0.0f, scale = 1.0f;
|
||||
int sprite_index;
|
||||
PyObject* texture;
|
||||
int sprite_index = 0;
|
||||
PyObject* texture = NULL;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffOif",
|
||||
const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &scale))
|
||||
|
@ -234,14 +234,25 @@ int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds)
|
|||
return -1;
|
||||
}
|
||||
|
||||
// check types for texture
|
||||
//if (texture != NULL && !PyObject_IsInstance(texture, (PyObject*)&PyTextureType)){
|
||||
if (texture != NULL && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){
|
||||
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance");
|
||||
// Handle texture - allow None or use default
|
||||
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
||||
if (texture != NULL && texture != Py_None && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){
|
||||
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance or None");
|
||||
return -1;
|
||||
} else if (texture != NULL && texture != Py_None) {
|
||||
auto pytexture = (PyTextureObject*)texture;
|
||||
texture_ptr = pytexture->data;
|
||||
} else {
|
||||
// Use default texture when None or not provided
|
||||
texture_ptr = McRFPy_API::default_texture;
|
||||
}
|
||||
|
||||
if (!texture_ptr) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "No texture provided and no default texture available");
|
||||
return -1;
|
||||
}
|
||||
auto pytexture = (PyTextureObject*)texture;
|
||||
self->data = std::make_shared<UISprite>(pytexture->data, 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));
|
||||
|
||||
return 0;
|
||||
|
|
Loading…
Reference in New Issue