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",
|
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf",
|
||||||
// const_cast<char**>(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline))
|
// const_cast<char**>(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline))
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|zOOOf",
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "Oz|OOOf",
|
||||||
const_cast<char**>(keywords), &pos, &text, &font, &fill_color, &outline_color, &outline))
|
const_cast<char**>(keywords), &pos, &text, &font, &fill_color, &outline_color, &outline))
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -252,10 +252,10 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
||||||
// check types for font, fill_color, outline_color
|
// check types for font, fill_color, outline_color
|
||||||
|
|
||||||
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl;
|
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl;
|
||||||
if (font != NULL && !PyObject_IsInstance(font, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font")/*(PyObject*)&PyFontType)*/)){
|
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");
|
PyErr_SetString(PyExc_TypeError, "font must be a mcrfpy.Font instance or None");
|
||||||
return -1;
|
return -1;
|
||||||
} else if (font != NULL)
|
} else if (font != NULL && font != Py_None)
|
||||||
{
|
{
|
||||||
auto font_obj = (PyFontObject*)font;
|
auto font_obj = (PyFontObject*)font;
|
||||||
self->data->text.setFont(font_obj->data->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);
|
Py_INCREF(font);
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
// default font
|
// Use default font when None or not provided
|
||||||
//self->data->text.setFont(Resources::game->getFont());
|
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);
|
self->data->text.setString((std::string)text);
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PyObjectUtils.h"
|
#include "PyObjectUtils.h"
|
||||||
#include <climits>
|
#include <climits>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
using namespace mcrfpydef;
|
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 = {
|
PySequenceMethods UICollection::sqmethods = {
|
||||||
.sq_length = (lenfunc)UICollection::len,
|
.sq_length = (lenfunc)UICollection::len,
|
||||||
|
.sq_concat = (binaryfunc)UICollection::concat,
|
||||||
|
.sq_repeat = NULL,
|
||||||
.sq_item = (ssizeargfunc)UICollection::getitem,
|
.sq_item = (ssizeargfunc)UICollection::getitem,
|
||||||
//.sq_item_by_index = PyUICollection_getitem
|
.was_sq_slice = NULL,
|
||||||
//.sq_slice - return a subset of the iterable
|
.sq_ass_item = (ssizeobjargproc)UICollection::setitem,
|
||||||
//.sq_ass_item - called when `o[x] = y` is executed (x is any object type)
|
.was_sq_ass_slice = NULL,
|
||||||
//.sq_ass_slice - cool; no thanks, for now
|
.sq_contains = (objobjproc)UICollection::contains,
|
||||||
//.sq_contains - called when `x in o` is executed
|
.sq_inplace_concat = (binaryfunc)UICollection::inplace_concat,
|
||||||
//.sq_ass_item_by_index - called when `o[x] = y` is executed (x is explictly an integer)
|
.sq_inplace_repeat = NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Idiomatic way to fetch complete types from the API rather than referencing their PyTypeObject struct
|
/* 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;
|
return NULL;
|
||||||
}
|
}
|
||||||
long index = PyLong_AsLong(o);
|
long index = PyLong_AsLong(o);
|
||||||
|
|
||||||
|
// Handle negative indexing
|
||||||
|
while (index < 0) index += self->data->size();
|
||||||
|
|
||||||
if (index >= self->data->size())
|
if (index >= self->data->size())
|
||||||
{
|
{
|
||||||
PyErr_SetString(PyExc_ValueError, "Index out of range");
|
PyErr_SetString(PyExc_ValueError, "Index out of range");
|
||||||
return NULL;
|
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];
|
// release the shared pointer at self->data[index];
|
||||||
self->data->erase(self->data->begin() + index);
|
self->data->erase(self->data->begin() + index);
|
||||||
|
@ -257,10 +626,101 @@ PyObject* UICollection::remove(PyUICollectionObject* self, PyObject* o)
|
||||||
return Py_None;
|
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[] = {
|
PyMethodDef UICollection::methods[] = {
|
||||||
{"append", (PyCFunction)UICollection::append, METH_O},
|
{"append", (PyCFunction)UICollection::append, METH_O},
|
||||||
//{"extend", (PyCFunction)PyUICollection_extend, METH_O}, // TODO
|
//{"extend", (PyCFunction)PyUICollection_extend, METH_O}, // TODO
|
||||||
{"remove", (PyCFunction)UICollection::remove, METH_O},
|
{"remove", (PyCFunction)UICollection::remove, METH_O},
|
||||||
|
{"index", (PyCFunction)UICollection::index_method, METH_O},
|
||||||
|
{"count", (PyCFunction)UICollection::count, METH_O},
|
||||||
{NULL, NULL, 0, NULL}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,18 @@ class UICollection
|
||||||
public:
|
public:
|
||||||
static Py_ssize_t len(PyUICollectionObject* self);
|
static Py_ssize_t len(PyUICollectionObject* self);
|
||||||
static PyObject* getitem(PyUICollectionObject* self, Py_ssize_t index);
|
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 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* append(PyUICollectionObject* self, PyObject* o);
|
||||||
static PyObject* remove(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 PyMethodDef methods[];
|
||||||
static PyObject* repr(PyUICollectionObject* self);
|
static PyObject* repr(PyUICollectionObject* self);
|
||||||
static int init(PyUICollectionObject* self, PyObject* args, PyObject* kwds);
|
static int init(PyUICollectionObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
@ -71,6 +80,7 @@ namespace mcrfpydef {
|
||||||
},
|
},
|
||||||
.tp_repr = (reprfunc)UICollection::repr,
|
.tp_repr = (reprfunc)UICollection::repr,
|
||||||
.tp_as_sequence = &UICollection::sqmethods,
|
.tp_as_sequence = &UICollection::sqmethods,
|
||||||
|
.tp_as_mapping = &UICollection::mpmethods,
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("Iterable, indexable collection of UI objects"),
|
.tp_doc = PyDoc_STR("Iterable, indexable collection of UI objects"),
|
||||||
.tp_iter = (getiterfunc)UICollection::iter,
|
.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",
|
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O",
|
||||||
// const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &grid))
|
// 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))
|
const_cast<char**>(keywords), &pos, &texture, &sprite_index, &grid))
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -90,33 +90,37 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
|
||||||
// check types for texture
|
// 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"))){
|
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
||||||
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance");
|
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;
|
return -1;
|
||||||
} /*else if (texture != NULL) // this section needs to go; texture isn't optional and isn't managed by the UI objects anymore
|
} else if (texture != NULL && texture != Py_None) {
|
||||||
{
|
auto pytexture = (PyTextureObject*)texture;
|
||||||
self->texture = texture;
|
texture_ptr = pytexture->data;
|
||||||
Py_INCREF(texture);
|
} else {
|
||||||
} else
|
// Use default texture when None or not provided
|
||||||
{
|
texture_ptr = McRFPy_API::default_texture;
|
||||||
// default tex?
|
}
|
||||||
}*/
|
|
||||||
|
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"))) {
|
if (grid != NULL && !PyObject_IsInstance(grid, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||||
PyErr_SetString(PyExc_TypeError, "grid must be a mcrfpy.Grid instance");
|
PyErr_SetString(PyExc_TypeError, "grid must be a mcrfpy.Grid instance");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pytexture = (PyTextureObject*)texture;
|
|
||||||
if (grid == NULL)
|
if (grid == NULL)
|
||||||
self->data = std::make_shared<UIEntity>();
|
self->data = std::make_shared<UIEntity>();
|
||||||
else
|
else
|
||||||
self->data = std::make_shared<UIEntity>(*((PyUIGridObject*)grid)->data);
|
self->data = std::make_shared<UIEntity>(*((PyUIGridObject*)grid)->data);
|
||||||
|
|
||||||
// TODO - PyTextureObjects and IndexTextures are a little bit of a mess with shared/unshared pointers
|
// 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;
|
self->data->position = pos_result->data;
|
||||||
if (grid != NULL) {
|
if (grid != NULL) {
|
||||||
PyUIGridObject* pygrid = (PyUIGridObject*)grid;
|
PyUIGridObject* pygrid = (PyUIGridObject*)grid;
|
||||||
|
|
549
src/UIGrid.cpp
549
src/UIGrid.cpp
|
@ -1,6 +1,7 @@
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
UIGrid::UIGrid() {}
|
UIGrid::UIGrid() {}
|
||||||
|
|
||||||
|
@ -218,27 +219,66 @@ UIDrawable* UIGrid::click_at(sf::Vector2f point)
|
||||||
|
|
||||||
int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
int grid_x, grid_y;
|
int grid_x, grid_y;
|
||||||
PyObject* textureObj;
|
PyObject* textureObj = Py_None;
|
||||||
//float box_x, box_y, box_w, box_h;
|
//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, "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
|
return -1; // If parsing fails, return an error
|
||||||
}
|
}
|
||||||
|
|
||||||
PyVectorObject* pos_result = PyVector::from_arg(pos);
|
// Default position and size if not provided
|
||||||
if (!pos_result)
|
PyVectorObject* pos_result = NULL;
|
||||||
{
|
PyVectorObject* size_result = NULL;
|
||||||
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
|
||||||
return -1;
|
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) {
|
||||||
if (!size_result)
|
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;
|
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*
|
// Convert PyObject texture to IndexTexture*
|
||||||
|
@ -246,7 +286,7 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
|
||||||
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
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 (textureObj != Py_None) {
|
||||||
//if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) {
|
//if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) {
|
||||||
if (!PyObject_IsInstance(textureObj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) {
|
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);
|
PyTextureObject* pyTexture = reinterpret_cast<PyTextureObject*>(textureObj);
|
||||||
texture_ptr = pyTexture->data;
|
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
|
// 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 = {
|
PySequenceMethods UIEntityCollection::sqmethods = {
|
||||||
.sq_length = (lenfunc)UIEntityCollection::len,
|
.sq_length = (lenfunc)UIEntityCollection::len,
|
||||||
|
.sq_concat = (binaryfunc)UIEntityCollection::concat,
|
||||||
|
.sq_repeat = NULL,
|
||||||
.sq_item = (ssizeargfunc)UIEntityCollection::getitem,
|
.sq_item = (ssizeargfunc)UIEntityCollection::getitem,
|
||||||
//.sq_item_by_index = UIEntityCollection::getitem
|
.was_sq_slice = NULL,
|
||||||
//.sq_slice - return a subset of the iterable
|
.sq_ass_item = (ssizeobjargproc)UIEntityCollection::setitem,
|
||||||
//.sq_ass_item - called when `o[x] = y` is executed (x is any object type)
|
.was_sq_ass_slice = NULL,
|
||||||
//.sq_ass_slice - cool; no thanks, for now
|
.sq_contains = (objobjproc)UIEntityCollection::contains,
|
||||||
//.sq_contains - called when `x in o` is executed
|
.sq_inplace_concat = (binaryfunc)UIEntityCollection::inplace_concat,
|
||||||
//.sq_ass_item_by_index - called when `o[x] = y` is executed (x is explictly an integer)
|
.sq_inplace_repeat = NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
PyObject* UIEntityCollection::append(PyUIEntityCollectionObject* self, PyObject* o)
|
PyObject* UIEntityCollection::append(PyUIEntityCollectionObject* self, PyObject* o)
|
||||||
|
@ -617,23 +841,29 @@ PyObject* UIEntityCollection::remove(PyUIEntityCollectionObject* self, PyObject*
|
||||||
{
|
{
|
||||||
if (!PyLong_Check(o))
|
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;
|
return NULL;
|
||||||
}
|
}
|
||||||
long index = PyLong_AsLong(o);
|
long index = PyLong_AsLong(o);
|
||||||
|
|
||||||
|
// Handle negative indexing
|
||||||
|
while (index < 0) index += self->data->size();
|
||||||
|
|
||||||
if (index >= self->data->size())
|
if (index >= self->data->size())
|
||||||
{
|
{
|
||||||
PyErr_SetString(PyExc_ValueError, "Index out of range");
|
PyErr_SetString(PyExc_ValueError, "Index out of range");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
else if (index < 0)
|
|
||||||
{
|
// Get iterator to the entity to remove
|
||||||
PyErr_SetString(PyExc_NotImplementedError, "reverse indexing is not implemented.");
|
auto it = self->data->begin();
|
||||||
return NULL;
|
std::advance(it, index);
|
||||||
}
|
|
||||||
|
// Clear grid reference before removing
|
||||||
|
(*it)->grid = nullptr;
|
||||||
|
|
||||||
// release the shared pointer at correct part of the list
|
// 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);
|
Py_INCREF(Py_None);
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
@ -676,10 +906,275 @@ PyObject* UIEntityCollection::extend(PyUIEntityCollectionObject* self, PyObject*
|
||||||
return Py_None;
|
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[] = {
|
PyMethodDef UIEntityCollection::methods[] = {
|
||||||
{"append", (PyCFunction)UIEntityCollection::append, METH_O},
|
{"append", (PyCFunction)UIEntityCollection::append, METH_O},
|
||||||
{"extend", (PyCFunction)UIEntityCollection::extend, METH_O},
|
{"extend", (PyCFunction)UIEntityCollection::extend, METH_O},
|
||||||
{"remove", (PyCFunction)UIEntityCollection::remove, 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}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
10
src/UIGrid.h
10
src/UIGrid.h
|
@ -82,15 +82,24 @@ typedef struct {
|
||||||
class UIEntityCollection {
|
class UIEntityCollection {
|
||||||
public:
|
public:
|
||||||
static PySequenceMethods sqmethods;
|
static PySequenceMethods sqmethods;
|
||||||
|
static PyMappingMethods mpmethods;
|
||||||
static PyObject* append(PyUIEntityCollectionObject* self, PyObject* o);
|
static PyObject* append(PyUIEntityCollectionObject* self, PyObject* o);
|
||||||
static PyObject* extend(PyUIEntityCollectionObject* self, PyObject* o);
|
static PyObject* extend(PyUIEntityCollectionObject* self, PyObject* o);
|
||||||
static PyObject* remove(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 PyMethodDef methods[];
|
||||||
static PyObject* repr(PyUIEntityCollectionObject* self);
|
static PyObject* repr(PyUIEntityCollectionObject* self);
|
||||||
static int init(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds);
|
static int init(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds);
|
||||||
static PyObject* iter(PyUIEntityCollectionObject* self);
|
static PyObject* iter(PyUIEntityCollectionObject* self);
|
||||||
static Py_ssize_t len(PyUIEntityCollectionObject* self);
|
static Py_ssize_t len(PyUIEntityCollectionObject* self);
|
||||||
static PyObject* getitem(PyUIEntityCollectionObject* self, Py_ssize_t index);
|
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 {
|
typedef struct {
|
||||||
|
@ -180,6 +189,7 @@ namespace mcrfpydef {
|
||||||
},
|
},
|
||||||
.tp_repr = (reprfunc)UIEntityCollection::repr,
|
.tp_repr = (reprfunc)UIEntityCollection::repr,
|
||||||
.tp_as_sequence = &UIEntityCollection::sqmethods,
|
.tp_as_sequence = &UIEntityCollection::sqmethods,
|
||||||
|
.tp_as_mapping = &UIEntityCollection::mpmethods,
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("Iterable, indexable collection of Entities"),
|
.tp_doc = PyDoc_STR("Iterable, indexable collection of Entities"),
|
||||||
.tp_iter = (getiterfunc)UIEntityCollection::iter,
|
.tp_iter = (getiterfunc)UIEntityCollection::iter,
|
||||||
|
|
|
@ -225,8 +225,8 @@ int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds)
|
||||||
//std::cout << "Init called\n";
|
//std::cout << "Init called\n";
|
||||||
static const char* keywords[] = { "x", "y", "texture", "sprite_index", "scale", nullptr };
|
static const char* keywords[] = { "x", "y", "texture", "sprite_index", "scale", nullptr };
|
||||||
float x = 0.0f, y = 0.0f, scale = 1.0f;
|
float x = 0.0f, y = 0.0f, scale = 1.0f;
|
||||||
int sprite_index;
|
int sprite_index = 0;
|
||||||
PyObject* texture;
|
PyObject* texture = NULL;
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffOif",
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffOif",
|
||||||
const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &scale))
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check types for texture
|
// Handle texture - allow None or use default
|
||||||
//if (texture != NULL && !PyObject_IsInstance(texture, (PyObject*)&PyTextureType)){
|
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
||||||
if (texture != NULL && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){
|
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");
|
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;
|
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));
|
self->data->setPosition(sf::Vector2f(x, y));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
Loading…
Reference in New Issue