Grid/GridView API unification: mcrfpy.Grid now returns GridView, closes #252
mcrfpy.Grid() now creates a GridView that internally owns a GridData (UIGrid). The old UIGrid type is renamed to _GridData (internal). Attribute access on Grid delegates to the underlying UIGrid via tp_getattro/tp_setattro, so all existing Grid properties (grid_w, grid_h, entities, cells, layers, etc.) work transparently. Key changes: - GridView init has two modes: factory (Grid(grid_size=...)) and explicit view (Grid(grid=existing_grid, ...)) for future multi-view support - Entity.grid getter returns GridView wrapper via owning_view back-reference - Entity.grid setter accepts GridView objects - GridLayer set_grid handles GridView (extracts underlying UIGrid) - UIDrawable::removeFromParent handles UIGRIDVIEW type correctly - UIFrame children init accepts GridView objects - Animation system supports GridView (center, zoom, shader.* properties) - PythonObjectCache registration preserves subclass identity - All 263 tests pass (100%) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a61f05229f
commit
109bc21d90
10 changed files with 616 additions and 146 deletions
|
|
@ -1,5 +1,6 @@
|
|||
#include "GridLayers.h"
|
||||
#include "UIGrid.h"
|
||||
#include "UIGridView.h"
|
||||
#include "UIEntity.h"
|
||||
#include "PyColor.h"
|
||||
#include "PyTexture.h"
|
||||
|
|
@ -1677,25 +1678,31 @@ int PyGridLayerAPI::ColorLayer_set_grid(PyColorLayerObject* self, PyObject* valu
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Validate it's a Grid
|
||||
auto* mcrfpy_module = PyImport_ImportModule("mcrfpy");
|
||||
if (!mcrfpy_module) return -1;
|
||||
|
||||
auto* grid_type = PyObject_GetAttrString(mcrfpy_module, "Grid");
|
||||
Py_DECREF(mcrfpy_module);
|
||||
if (!grid_type) return -1;
|
||||
|
||||
if (!PyObject_IsInstance(value, grid_type)) {
|
||||
Py_DECREF(grid_type);
|
||||
// Validate it's a Grid (GridView) or internal _GridData
|
||||
if (!PyObject_IsInstance(value, (PyObject*)&mcrfpydef::PyUIGridViewType) &&
|
||||
!PyObject_IsInstance(value, (PyObject*)&mcrfpydef::PyUIGridType)) {
|
||||
PyErr_SetString(PyExc_TypeError, "grid must be a Grid object or None");
|
||||
return -1;
|
||||
}
|
||||
Py_DECREF(grid_type);
|
||||
|
||||
PyUIGridObject* py_grid = (PyUIGridObject*)value;
|
||||
// Extract UIGrid shared_ptr from Grid (UIGridView) or _GridData (UIGrid)
|
||||
std::shared_ptr<UIGrid> target_grid;
|
||||
if (PyObject_IsInstance(value, (PyObject*)&mcrfpydef::PyUIGridViewType)) {
|
||||
auto* pyview = (PyUIGridViewObject*)value;
|
||||
if (pyview->data->grid_data) {
|
||||
// GridView's grid_data is aliased from a UIGrid, cast back
|
||||
target_grid = std::static_pointer_cast<UIGrid>(pyview->data->grid_data);
|
||||
}
|
||||
} else if (PyObject_IsInstance(value, (PyObject*)&mcrfpydef::PyUIGridType)) {
|
||||
target_grid = ((PyUIGridObject*)value)->data;
|
||||
}
|
||||
if (!target_grid) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Grid has no data");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if already attached to this grid
|
||||
if (self->grid.get() == py_grid->data.get()) {
|
||||
if (self->grid.get() == target_grid.get()) {
|
||||
return 0; // Nothing to do
|
||||
}
|
||||
|
||||
|
|
@ -1714,31 +1721,31 @@ int PyGridLayerAPI::ColorLayer_set_grid(PyColorLayerObject* self, PyObject* valu
|
|||
|
||||
// Handle name collision - unlink existing layer with same name
|
||||
if (!self->data->name.empty()) {
|
||||
auto existing = py_grid->data->getLayerByName(self->data->name);
|
||||
auto existing = target_grid->getLayerByName(self->data->name);
|
||||
if (existing && existing.get() != self->data.get()) {
|
||||
existing->parent_grid = nullptr;
|
||||
py_grid->data->removeLayer(existing);
|
||||
target_grid->removeLayer(existing);
|
||||
}
|
||||
}
|
||||
|
||||
// Lazy allocation: resize if layer is (0,0)
|
||||
if (self->data->grid_x == 0 && self->data->grid_y == 0) {
|
||||
self->data->resize(py_grid->data->grid_w, py_grid->data->grid_h);
|
||||
} else if (self->data->grid_x != py_grid->data->grid_w ||
|
||||
self->data->grid_y != py_grid->data->grid_h) {
|
||||
self->data->resize(target_grid->grid_w, target_grid->grid_h);
|
||||
} else if (self->data->grid_x != target_grid->grid_w ||
|
||||
self->data->grid_y != target_grid->grid_h) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"Layer size (%d, %d) does not match Grid size (%d, %d)",
|
||||
self->data->grid_x, self->data->grid_y,
|
||||
py_grid->data->grid_w, py_grid->data->grid_h);
|
||||
target_grid->grid_w, target_grid->grid_h);
|
||||
self->grid.reset();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Link to new grid
|
||||
self->data->parent_grid = py_grid->data.get();
|
||||
py_grid->data->layers.push_back(self->data);
|
||||
py_grid->data->layers_need_sort = true;
|
||||
self->grid = py_grid->data;
|
||||
self->data->parent_grid = target_grid.get();
|
||||
target_grid->layers.push_back(self->data);
|
||||
target_grid->layers_need_sort = true;
|
||||
self->grid = target_grid;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -2326,25 +2333,30 @@ int PyGridLayerAPI::TileLayer_set_grid(PyTileLayerObject* self, PyObject* value,
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Validate it's a Grid
|
||||
auto* mcrfpy_module = PyImport_ImportModule("mcrfpy");
|
||||
if (!mcrfpy_module) return -1;
|
||||
|
||||
auto* grid_type = PyObject_GetAttrString(mcrfpy_module, "Grid");
|
||||
Py_DECREF(mcrfpy_module);
|
||||
if (!grid_type) return -1;
|
||||
|
||||
if (!PyObject_IsInstance(value, grid_type)) {
|
||||
Py_DECREF(grid_type);
|
||||
// Validate it's a Grid (GridView) or internal _GridData
|
||||
if (!PyObject_IsInstance(value, (PyObject*)&mcrfpydef::PyUIGridViewType) &&
|
||||
!PyObject_IsInstance(value, (PyObject*)&mcrfpydef::PyUIGridType)) {
|
||||
PyErr_SetString(PyExc_TypeError, "grid must be a Grid object or None");
|
||||
return -1;
|
||||
}
|
||||
Py_DECREF(grid_type);
|
||||
|
||||
PyUIGridObject* py_grid = (PyUIGridObject*)value;
|
||||
// Extract UIGrid shared_ptr from Grid (UIGridView) or _GridData (UIGrid)
|
||||
std::shared_ptr<UIGrid> target_grid;
|
||||
if (PyObject_IsInstance(value, (PyObject*)&mcrfpydef::PyUIGridViewType)) {
|
||||
auto* pyview = (PyUIGridViewObject*)value;
|
||||
if (pyview->data->grid_data) {
|
||||
target_grid = std::static_pointer_cast<UIGrid>(pyview->data->grid_data);
|
||||
}
|
||||
} else if (PyObject_IsInstance(value, (PyObject*)&mcrfpydef::PyUIGridType)) {
|
||||
target_grid = ((PyUIGridObject*)value)->data;
|
||||
}
|
||||
if (!target_grid) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Grid has no data");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if already attached to this grid
|
||||
if (self->grid.get() == py_grid->data.get()) {
|
||||
if (self->grid.get() == target_grid.get()) {
|
||||
return 0; // Nothing to do
|
||||
}
|
||||
|
||||
|
|
@ -2363,35 +2375,35 @@ int PyGridLayerAPI::TileLayer_set_grid(PyTileLayerObject* self, PyObject* value,
|
|||
|
||||
// Handle name collision - unlink existing layer with same name
|
||||
if (!self->data->name.empty()) {
|
||||
auto existing = py_grid->data->getLayerByName(self->data->name);
|
||||
auto existing = target_grid->getLayerByName(self->data->name);
|
||||
if (existing && existing.get() != self->data.get()) {
|
||||
existing->parent_grid = nullptr;
|
||||
py_grid->data->removeLayer(existing);
|
||||
target_grid->removeLayer(existing);
|
||||
}
|
||||
}
|
||||
|
||||
// Lazy allocation: resize if layer is (0,0)
|
||||
if (self->data->grid_x == 0 && self->data->grid_y == 0) {
|
||||
self->data->resize(py_grid->data->grid_w, py_grid->data->grid_h);
|
||||
} else if (self->data->grid_x != py_grid->data->grid_w ||
|
||||
self->data->grid_y != py_grid->data->grid_h) {
|
||||
self->data->resize(target_grid->grid_w, target_grid->grid_h);
|
||||
} else if (self->data->grid_x != target_grid->grid_w ||
|
||||
self->data->grid_y != target_grid->grid_h) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"Layer size (%d, %d) does not match Grid size (%d, %d)",
|
||||
self->data->grid_x, self->data->grid_y,
|
||||
py_grid->data->grid_w, py_grid->data->grid_h);
|
||||
target_grid->grid_w, target_grid->grid_h);
|
||||
self->grid.reset();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Link to new grid
|
||||
self->data->parent_grid = py_grid->data.get();
|
||||
py_grid->data->layers.push_back(std::static_pointer_cast<GridLayer>(self->data));
|
||||
py_grid->data->layers_need_sort = true;
|
||||
self->grid = py_grid->data;
|
||||
self->data->parent_grid = target_grid.get();
|
||||
target_grid->layers.push_back(std::static_pointer_cast<GridLayer>(self->data));
|
||||
target_grid->layers_need_sort = true;
|
||||
self->grid = target_grid;
|
||||
|
||||
// Inherit grid texture if TileLayer has none (#254)
|
||||
if (!self->data->texture) {
|
||||
self->data->texture = py_grid->data->getTexture();
|
||||
self->data->texture = target_grid->getTexture();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue