diff --git a/src/UICaption.cpp b/src/UICaption.cpp index c4926b3..c8c0199 100644 --- a/src/UICaption.cpp +++ b/src/UICaption.cpp @@ -236,7 +236,7 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds) //if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf", // const_cast(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(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); diff --git a/src/UICollection.cpp b/src/UICollection.cpp index 9bffff0..d39e815 100644 --- a/src/UICollection.cpp +++ b/src/UICollection.cpp @@ -7,6 +7,7 @@ #include "McRFPy_API.h" #include "PyObjectUtils.h" #include +#include 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 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 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 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()); + 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> 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 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 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 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} }; diff --git a/src/UICollection.h b/src/UICollection.h index 886fdd0..a1b5d42 100644 --- a/src/UICollection.h +++ b/src/UICollection.h @@ -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, diff --git a/src/UIEntity.cpp b/src/UIEntity.cpp index 6a7b828..2ac1d4d 100644 --- a/src/UIEntity.cpp +++ b/src/UIEntity.cpp @@ -75,7 +75,7 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) { //if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O", // const_cast(keywords), &x, &y, &texture, &sprite_index, &grid)) - if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOi|O", + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OiO", const_cast(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 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(); else self->data = std::make_shared(*((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; diff --git a/src/UIGrid.cpp b/src/UIGrid.cpp index e616b16..e13fbcd 100644 --- a/src/UIGrid.cpp +++ b/src/UIGrid.cpp @@ -1,6 +1,7 @@ #include "UIGrid.h" #include "GameEngine.h" #include "McRFPy_API.h" +#include UIGrid::UIGrid() {} @@ -218,27 +219,66 @@ 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); - if (!pos_result) - { - PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__"); - return -1; + // 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_result) - { - PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__"); - return -1; + if (size) { + size_result = PyVector::from_arg(size); + if (!size_result) + { + 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* @@ -246,7 +286,7 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) { std::shared_ptr 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(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 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()); + + // 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> 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} }; diff --git a/src/UIGrid.h b/src/UIGrid.h index 7c288b8..a167c0b 100644 --- a/src/UIGrid.h +++ b/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, diff --git a/src/UISprite.cpp b/src/UISprite.cpp index fa800d6..87b9f2d 100644 --- a/src/UISprite.cpp +++ b/src/UISprite.cpp @@ -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(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 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(pytexture->data, sprite_index, sf::Vector2f(x, y), scale); + + self->data = std::make_shared(texture_ptr, sprite_index, sf::Vector2f(x, y), scale); self->data->setPosition(sf::Vector2f(x, y)); return 0;