feat: Migrate Grid to user-driven layer rendering (closes #150)
- Add `layers` dict parameter to Grid constructor for explicit layer definitions
- `layers={"ground": "color", "terrain": "tile"}` creates named layers
- `layers={}` creates empty grid (entities + pathfinding only)
- Default creates single TileLayer named "tilesprite" for backward compat
- Implement dynamic GridPoint property access via layer names
- `grid.at(x,y).layer_name = value` routes to corresponding layer
- Protected names (walkable, transparent, etc.) still use GridPoint
- Remove base layer rendering from UIGrid::render()
- Layers are now the sole source of grid rendering
- Old chunk_manager remains for GridPoint data access
- FOV overlay unchanged
- Update test to use explicit `layers={}` parameter
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9469c04b01
commit
a258613faa
|
|
@ -5,6 +5,7 @@
|
||||||
#include <SFML/Graphics.hpp>
|
#include <SFML/Graphics.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
class UIGrid;
|
class UIGrid;
|
||||||
|
|
@ -23,6 +24,7 @@ enum class GridLayerType {
|
||||||
class GridLayer {
|
class GridLayer {
|
||||||
public:
|
public:
|
||||||
GridLayerType type;
|
GridLayerType type;
|
||||||
|
std::string name; // #150 - Layer name for GridPoint property access
|
||||||
int z_index; // Negative = below entities, >= 0 = above entities
|
int z_index; // Negative = below entities, >= 0 = above entities
|
||||||
int grid_x, grid_y; // Dimensions
|
int grid_x, grid_y; // Dimensions
|
||||||
UIGrid* parent_grid; // Parent grid reference
|
UIGrid* parent_grid; // Parent grid reference
|
||||||
|
|
|
||||||
158
src/UIGrid.cpp
158
src/UIGrid.cpp
|
|
@ -5,7 +5,8 @@
|
||||||
#include "UIEntity.h"
|
#include "UIEntity.h"
|
||||||
#include "Profiler.h"
|
#include "Profiler.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath> // #142 - for std::floor
|
#include <cmath> // #142 - for std::floor
|
||||||
|
#include <cstring> // #150 - for strcmp
|
||||||
// UIDrawable methods now in UIBase.h
|
// UIDrawable methods now in UIBase.h
|
||||||
|
|
||||||
UIGrid::UIGrid()
|
UIGrid::UIGrid()
|
||||||
|
|
@ -159,84 +160,14 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target)
|
||||||
int left_spritepixels = center_x - (box.getSize().x / 2.0 / zoom);
|
int left_spritepixels = center_x - (box.getSize().x / 2.0 / zoom);
|
||||||
int top_spritepixels = center_y - (box.getSize().y / 2.0 / zoom);
|
int top_spritepixels = center_y - (box.getSize().y / 2.0 / zoom);
|
||||||
|
|
||||||
//sprite.setScale(sf::Vector2f(zoom, zoom));
|
|
||||||
sf::RectangleShape r; // for colors and overlays
|
|
||||||
r.setSize(sf::Vector2f(cell_width * zoom, cell_height * zoom));
|
|
||||||
r.setOutlineThickness(0);
|
|
||||||
|
|
||||||
int x_limit = left_edge + width_sq + 2;
|
int x_limit = left_edge + width_sq + 2;
|
||||||
if (x_limit > grid_x) x_limit = grid_x;
|
if (x_limit > grid_x) x_limit = grid_x;
|
||||||
|
|
||||||
int y_limit = top_edge + height_sq + 2;
|
int y_limit = top_edge + height_sq + 2;
|
||||||
if (y_limit > grid_y) y_limit = grid_y;
|
if (y_limit > grid_y) y_limit = grid_y;
|
||||||
|
|
||||||
// base layer - bottom color, tile sprite ("ground")
|
// #150 - Layers are now the sole source of grid rendering (base layer removed)
|
||||||
int cellsRendered = 0;
|
// Render layers with z_index < 0 (below entities)
|
||||||
|
|
||||||
// #123 - Use chunk-based rendering for large grids
|
|
||||||
if (use_chunks && chunk_manager) {
|
|
||||||
// Get visible chunks based on cell coordinate bounds
|
|
||||||
float right_edge = left_edge + width_sq + 2;
|
|
||||||
float bottom_edge = top_edge + height_sq + 2;
|
|
||||||
auto visible_chunks = chunk_manager->getVisibleChunks(left_edge, top_edge, right_edge, bottom_edge);
|
|
||||||
|
|
||||||
for (auto* chunk : visible_chunks) {
|
|
||||||
// Re-render dirty chunks to their cached textures
|
|
||||||
if (chunk->dirty) {
|
|
||||||
chunk->renderToTexture(cell_width, cell_height, ptex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate pixel position for this chunk's sprite
|
|
||||||
float chunk_pixel_x = (chunk->world_x * cell_width - left_spritepixels) * zoom;
|
|
||||||
float chunk_pixel_y = (chunk->world_y * cell_height - top_spritepixels) * zoom;
|
|
||||||
|
|
||||||
// Set up and draw the chunk sprite
|
|
||||||
chunk->cached_sprite.setPosition(chunk_pixel_x, chunk_pixel_y);
|
|
||||||
chunk->cached_sprite.setScale(zoom, zoom);
|
|
||||||
renderTexture.draw(chunk->cached_sprite);
|
|
||||||
|
|
||||||
cellsRendered += chunk->width * chunk->height;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Original cell-by-cell rendering for small grids
|
|
||||||
for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0);
|
|
||||||
x < x_limit; //x < view_width;
|
|
||||||
x+=1)
|
|
||||||
{
|
|
||||||
//for (float y = (top_edge >= 0 ? top_edge : 0);
|
|
||||||
for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0);
|
|
||||||
y < y_limit; //y < view_height;
|
|
||||||
y+=1)
|
|
||||||
{
|
|
||||||
auto pixel_pos = sf::Vector2f(
|
|
||||||
(x*cell_width - left_spritepixels) * zoom,
|
|
||||||
(y*cell_height - top_spritepixels) * zoom );
|
|
||||||
|
|
||||||
auto gridpoint = at(std::floor(x), std::floor(y));
|
|
||||||
|
|
||||||
//sprite.setPosition(pixel_pos);
|
|
||||||
|
|
||||||
r.setPosition(pixel_pos);
|
|
||||||
r.setFillColor(gridpoint.color);
|
|
||||||
renderTexture.draw(r);
|
|
||||||
|
|
||||||
// tilesprite - only draw if texture is available
|
|
||||||
// if discovered but not visible, set opacity to 90%
|
|
||||||
// if not discovered... just don't draw it?
|
|
||||||
if (ptex && gridpoint.tilesprite != -1) {
|
|
||||||
sprite = ptex->sprite(gridpoint.tilesprite, pixel_pos, sf::Vector2f(zoom, zoom)); //setSprite(gridpoint.tilesprite);;
|
|
||||||
renderTexture.draw(sprite);
|
|
||||||
}
|
|
||||||
|
|
||||||
cellsRendered++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record how many cells were rendered
|
|
||||||
Resources::game->metrics.gridCellsRendered += cellsRendered;
|
|
||||||
|
|
||||||
// #147 - Render dynamic layers with z_index < 0 (below entities)
|
|
||||||
sortLayers();
|
sortLayers();
|
||||||
for (auto& layer : layers) {
|
for (auto& layer : layers) {
|
||||||
if (layer->z_index >= 0) break; // Stop at layers that go above entities
|
if (layer->z_index >= 0) break; // Stop at layers that go above entities
|
||||||
|
|
@ -450,20 +381,42 @@ PyObjectsEnum UIGrid::derived_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
// #147 - Layer management methods
|
// #147 - Layer management methods
|
||||||
std::shared_ptr<ColorLayer> UIGrid::addColorLayer(int z_index) {
|
std::shared_ptr<ColorLayer> UIGrid::addColorLayer(int z_index, const std::string& name) {
|
||||||
auto layer = std::make_shared<ColorLayer>(z_index, grid_x, grid_y, this);
|
auto layer = std::make_shared<ColorLayer>(z_index, grid_x, grid_y, this);
|
||||||
|
layer->name = name;
|
||||||
layers.push_back(layer);
|
layers.push_back(layer);
|
||||||
layers_need_sort = true;
|
layers_need_sort = true;
|
||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<TileLayer> UIGrid::addTileLayer(int z_index, std::shared_ptr<PyTexture> texture) {
|
std::shared_ptr<TileLayer> UIGrid::addTileLayer(int z_index, std::shared_ptr<PyTexture> texture, const std::string& name) {
|
||||||
auto layer = std::make_shared<TileLayer>(z_index, grid_x, grid_y, this, texture);
|
auto layer = std::make_shared<TileLayer>(z_index, grid_x, grid_y, this, texture);
|
||||||
|
layer->name = name;
|
||||||
layers.push_back(layer);
|
layers.push_back(layer);
|
||||||
layers_need_sort = true;
|
layers_need_sort = true;
|
||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<GridLayer> UIGrid::getLayerByName(const std::string& name) {
|
||||||
|
for (auto& layer : layers) {
|
||||||
|
if (layer->name == name) {
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIGrid::isProtectedLayerName(const std::string& name) {
|
||||||
|
// #150 - These names are reserved for GridPoint properties
|
||||||
|
static const std::vector<std::string> protected_names = {
|
||||||
|
"walkable", "transparent", "color", "color_overlay"
|
||||||
|
};
|
||||||
|
for (const auto& pn : protected_names) {
|
||||||
|
if (name == pn) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void UIGrid::removeLayer(std::shared_ptr<GridLayer> layer) {
|
void UIGrid::removeLayer(std::shared_ptr<GridLayer> layer) {
|
||||||
auto it = std::find(layers.begin(), layers.end(), layer);
|
auto it = std::find(layers.begin(), layers.end(), layer);
|
||||||
if (it != layers.end()) {
|
if (it != layers.end()) {
|
||||||
|
|
@ -779,6 +732,7 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
PyObject* textureObj = nullptr;
|
PyObject* textureObj = nullptr;
|
||||||
PyObject* fill_color = nullptr;
|
PyObject* fill_color = nullptr;
|
||||||
PyObject* click_handler = nullptr;
|
PyObject* click_handler = nullptr;
|
||||||
|
PyObject* layers_obj = nullptr; // #150 - layers dict
|
||||||
float center_x = 0.0f, center_y = 0.0f;
|
float center_x = 0.0f, center_y = 0.0f;
|
||||||
float zoom = 1.0f;
|
float zoom = 1.0f;
|
||||||
// perspective is now handled via properties, not init args
|
// perspective is now handled via properties, not init args
|
||||||
|
|
@ -795,14 +749,16 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
// Keyword-only args
|
// Keyword-only args
|
||||||
"fill_color", "click", "center_x", "center_y", "zoom",
|
"fill_color", "click", "center_x", "center_y", "zoom",
|
||||||
"visible", "opacity", "z_index", "name", "x", "y", "w", "h", "grid_x", "grid_y",
|
"visible", "opacity", "z_index", "name", "x", "y", "w", "h", "grid_x", "grid_y",
|
||||||
|
"layers", // #150 - layers dict parameter
|
||||||
nullptr
|
nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse arguments with | for optional positional args
|
// Parse arguments with | for optional positional args
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOOOOfffifizffffii", const_cast<char**>(kwlist),
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOOOOfffifizffffiiO", const_cast<char**>(kwlist),
|
||||||
&pos_obj, &size_obj, &grid_size_obj, &textureObj, // Positional
|
&pos_obj, &size_obj, &grid_size_obj, &textureObj, // Positional
|
||||||
&fill_color, &click_handler, ¢er_x, ¢er_y, &zoom,
|
&fill_color, &click_handler, ¢er_x, ¢er_y, &zoom,
|
||||||
&visible, &opacity, &z_index, &name, &x, &y, &w, &h, &grid_x, &grid_y)) {
|
&visible, &opacity, &z_index, &name, &x, &y, &w, &h, &grid_x, &grid_y,
|
||||||
|
&layers_obj)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -936,6 +892,54 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
self->data->click_register(click_handler);
|
self->data->click_register(click_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #150 - Handle layers dict
|
||||||
|
// Default: {"tilesprite": "tile"} when layers not provided
|
||||||
|
// Empty dict: no rendering layers (entity storage + pathfinding only)
|
||||||
|
if (layers_obj == nullptr) {
|
||||||
|
// Default layer: single TileLayer named "tilesprite"
|
||||||
|
self->data->addTileLayer(0, texture_ptr, "tilesprite");
|
||||||
|
} else if (layers_obj != Py_None) {
|
||||||
|
if (!PyDict_Check(layers_obj)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "layers must be a dict mapping names to types ('color' or 'tile')");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* key;
|
||||||
|
PyObject* value;
|
||||||
|
Py_ssize_t pos = 0;
|
||||||
|
int layer_z = 0; // Auto-increment z_index for each layer
|
||||||
|
|
||||||
|
while (PyDict_Next(layers_obj, &pos, &key, &value)) {
|
||||||
|
if (!PyUnicode_Check(key)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Layer names must be strings");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!PyUnicode_Check(value)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Layer types must be strings ('color' or 'tile')");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* layer_name = PyUnicode_AsUTF8(key);
|
||||||
|
const char* layer_type = PyUnicode_AsUTF8(value);
|
||||||
|
|
||||||
|
// Check for protected names
|
||||||
|
if (UIGrid::isProtectedLayerName(layer_name)) {
|
||||||
|
PyErr_Format(PyExc_ValueError, "Layer name '%s' is reserved", layer_name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(layer_type, "color") == 0) {
|
||||||
|
self->data->addColorLayer(layer_z++, layer_name);
|
||||||
|
} else if (strcmp(layer_type, "tile") == 0) {
|
||||||
|
self->data->addTileLayer(layer_z++, texture_ptr, layer_name);
|
||||||
|
} else {
|
||||||
|
PyErr_Format(PyExc_ValueError, "Unknown layer type '%s' (expected 'color' or 'tile')", layer_type);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// else: layers_obj is Py_None - explicit empty, no layers created
|
||||||
|
|
||||||
// Initialize weak reference list
|
// Initialize weak reference list
|
||||||
self->weakreflist = NULL;
|
self->weakreflist = NULL;
|
||||||
|
|
||||||
|
|
|
||||||
10
src/UIGrid.h
10
src/UIGrid.h
|
|
@ -95,11 +95,15 @@ public:
|
||||||
std::vector<std::shared_ptr<GridLayer>> layers;
|
std::vector<std::shared_ptr<GridLayer>> layers;
|
||||||
bool layers_need_sort = true; // Dirty flag for z_index sorting
|
bool layers_need_sort = true; // Dirty flag for z_index sorting
|
||||||
|
|
||||||
// Layer management
|
// Layer management (#150 - extended with names)
|
||||||
std::shared_ptr<ColorLayer> addColorLayer(int z_index);
|
std::shared_ptr<ColorLayer> addColorLayer(int z_index, const std::string& name = "");
|
||||||
std::shared_ptr<TileLayer> addTileLayer(int z_index, std::shared_ptr<PyTexture> texture = nullptr);
|
std::shared_ptr<TileLayer> addTileLayer(int z_index, std::shared_ptr<PyTexture> texture = nullptr, const std::string& name = "");
|
||||||
void removeLayer(std::shared_ptr<GridLayer> layer);
|
void removeLayer(std::shared_ptr<GridLayer> layer);
|
||||||
void sortLayers();
|
void sortLayers();
|
||||||
|
std::shared_ptr<GridLayer> getLayerByName(const std::string& name);
|
||||||
|
|
||||||
|
// #150 - Protected layer names (reserved for GridPoint properties)
|
||||||
|
static bool isProtectedLayerName(const std::string& name);
|
||||||
|
|
||||||
// Background rendering
|
// Background rendering
|
||||||
sf::Color fill_color;
|
sf::Color fill_color;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#include "UIGridPoint.h"
|
#include "UIGridPoint.h"
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
|
#include "GridLayers.h" // #150 - for GridLayerType, ColorLayer, TileLayer
|
||||||
|
#include <cstring> // #150 - for strcmp
|
||||||
|
|
||||||
UIGridPoint::UIGridPoint()
|
UIGridPoint::UIGridPoint()
|
||||||
: color(1.0f, 1.0f, 1.0f), color_overlay(0.0f, 0.0f, 0.0f), walkable(false), transparent(false),
|
: color(1.0f, 1.0f, 1.0f), color_overlay(0.0f, 0.0f, 0.0f), walkable(false), transparent(false),
|
||||||
|
|
@ -199,3 +201,97 @@ PyObject* UIGridPointState::repr(PyUIGridPointStateObject* self) {
|
||||||
std::string repr_str = ss.str();
|
std::string repr_str = ss.str();
|
||||||
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
|
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #150 - Dynamic attribute access for named layers
|
||||||
|
PyObject* UIGridPoint::getattro(PyUIGridPointObject* self, PyObject* name) {
|
||||||
|
// First try standard attribute lookup (built-in properties)
|
||||||
|
PyObject* result = PyObject_GenericGetAttr((PyObject*)self, name);
|
||||||
|
if (result != nullptr || !PyErr_ExceptionMatches(PyExc_AttributeError)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the AttributeError and check for layer name
|
||||||
|
PyErr_Clear();
|
||||||
|
|
||||||
|
if (!self->grid) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "GridPoint has no parent grid");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* attr_name = PyUnicode_AsUTF8(name);
|
||||||
|
if (!attr_name) return nullptr;
|
||||||
|
|
||||||
|
// Look up layer by name
|
||||||
|
auto layer = self->grid->getLayerByName(attr_name);
|
||||||
|
if (!layer) {
|
||||||
|
PyErr_Format(PyExc_AttributeError, "'GridPoint' object has no attribute '%s'", attr_name);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x = self->data->grid_x;
|
||||||
|
int y = self->data->grid_y;
|
||||||
|
|
||||||
|
// Get value based on layer type
|
||||||
|
if (layer->type == GridLayerType::Color) {
|
||||||
|
auto color_layer = std::static_pointer_cast<ColorLayer>(layer);
|
||||||
|
return sfColor_to_PyObject(color_layer->at(x, y));
|
||||||
|
} else if (layer->type == GridLayerType::Tile) {
|
||||||
|
auto tile_layer = std::static_pointer_cast<TileLayer>(layer);
|
||||||
|
return PyLong_FromLong(tile_layer->at(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Unknown layer type");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UIGridPoint::setattro(PyUIGridPointObject* self, PyObject* name, PyObject* value) {
|
||||||
|
// First try standard attribute setting (built-in properties)
|
||||||
|
// We need to check if this is a known attribute first
|
||||||
|
const char* attr_name = PyUnicode_AsUTF8(name);
|
||||||
|
if (!attr_name) return -1;
|
||||||
|
|
||||||
|
// Check if it's a built-in property (defined in getsetters)
|
||||||
|
for (PyGetSetDef* gsd = UIGridPoint::getsetters; gsd->name != nullptr; gsd++) {
|
||||||
|
if (strcmp(gsd->name, attr_name) == 0) {
|
||||||
|
// It's a built-in property, use standard setter
|
||||||
|
return PyObject_GenericSetAttr((PyObject*)self, name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a built-in property - try layer lookup
|
||||||
|
if (!self->grid) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "GridPoint has no parent grid");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto layer = self->grid->getLayerByName(attr_name);
|
||||||
|
if (!layer) {
|
||||||
|
PyErr_Format(PyExc_AttributeError, "'GridPoint' object has no attribute '%s'", attr_name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x = self->data->grid_x;
|
||||||
|
int y = self->data->grid_y;
|
||||||
|
|
||||||
|
// Set value based on layer type
|
||||||
|
if (layer->type == GridLayerType::Color) {
|
||||||
|
auto color_layer = std::static_pointer_cast<ColorLayer>(layer);
|
||||||
|
sf::Color color = PyObject_to_sfColor(value);
|
||||||
|
if (PyErr_Occurred()) return -1;
|
||||||
|
color_layer->at(x, y) = color;
|
||||||
|
color_layer->markDirty();
|
||||||
|
return 0;
|
||||||
|
} else if (layer->type == GridLayerType::Tile) {
|
||||||
|
auto tile_layer = std::static_pointer_cast<TileLayer>(layer);
|
||||||
|
if (!PyLong_Check(value)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Tile layer values must be integers");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
tile_layer->at(x, y) = PyLong_AsLong(value);
|
||||||
|
tile_layer->markDirty();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Unknown layer type");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,10 @@ public:
|
||||||
static PyObject* get_bool_member(PyUIGridPointObject* self, void* closure);
|
static PyObject* get_bool_member(PyUIGridPointObject* self, void* closure);
|
||||||
static int set_color(PyUIGridPointObject* self, PyObject* value, void* closure);
|
static int set_color(PyUIGridPointObject* self, PyObject* value, void* closure);
|
||||||
static PyObject* repr(PyUIGridPointObject* self);
|
static PyObject* repr(PyUIGridPointObject* self);
|
||||||
|
|
||||||
|
// #150 - Dynamic property access for named layers
|
||||||
|
static PyObject* getattro(PyUIGridPointObject* self, PyObject* name);
|
||||||
|
static int setattro(PyUIGridPointObject* self, PyObject* name, PyObject* value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// UIGridPointState - entity-specific info for each cell
|
// UIGridPointState - entity-specific info for each cell
|
||||||
|
|
@ -73,6 +77,9 @@ namespace mcrfpydef {
|
||||||
.tp_basicsize = sizeof(PyUIGridPointObject),
|
.tp_basicsize = sizeof(PyUIGridPointObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
.tp_repr = (reprfunc)UIGridPoint::repr,
|
.tp_repr = (reprfunc)UIGridPoint::repr,
|
||||||
|
// #150 - Dynamic attribute access for named layers
|
||||||
|
.tp_getattro = (getattrofunc)UIGridPoint::getattro,
|
||||||
|
.tp_setattro = (setattrofunc)UIGridPoint::setattro,
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = "UIGridPoint object",
|
.tp_doc = "UIGridPoint object",
|
||||||
.tp_getset = UIGridPoint::getsetters,
|
.tp_getset = UIGridPoint::getsetters,
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,13 @@ def run_test(runtime):
|
||||||
ui = mcrfpy.sceneUI("test")
|
ui = mcrfpy.sceneUI("test")
|
||||||
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
|
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
|
||||||
|
|
||||||
# Create grid
|
# Create grid with explicit empty layers (#150 migration)
|
||||||
grid = mcrfpy.Grid(pos=(50, 50), size=(400, 300), grid_size=(20, 15), texture=texture)
|
grid = mcrfpy.Grid(pos=(50, 50), size=(400, 300), grid_size=(20, 15), texture=texture, layers={})
|
||||||
ui.append(grid)
|
ui.append(grid)
|
||||||
|
|
||||||
print("\n--- Test 1: Initial state (no layers) ---")
|
print("\n--- Test 1: Initial state (no layers) ---")
|
||||||
if len(grid.layers) == 0:
|
if len(grid.layers) == 0:
|
||||||
print(" PASS: Grid starts with no layers")
|
print(" PASS: Grid starts with no layers (layers={})")
|
||||||
else:
|
else:
|
||||||
print(f" FAIL: Expected 0 layers, got {len(grid.layers)}")
|
print(f" FAIL: Expected 0 layers, got {len(grid.layers)}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue