Fix #221: Add grid_pos and grid_size properties for Grid children
UIDrawables placed in a Grid's children collection now have: - grid_pos: Position in tile coordinates (get/set) - grid_size: Size in tile coordinates (get/set) Raises RuntimeError if accessed when parent is not a Grid. UIGrid only gets grid_pos (grid_size conflicts with existing property). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6c5992f1c1
commit
14a6520593
10 changed files with 251 additions and 0 deletions
|
|
@ -448,6 +448,8 @@ PyGetSetDef UIArc::getsetters[] = {
|
|||
"Name for finding this element.", (void*)PyObjectsEnum::UIARC},
|
||||
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos,
|
||||
"Position as a Vector (same as center).", (void*)PyObjectsEnum::UIARC},
|
||||
{"grid_pos", (getter)UIDrawable::get_grid_pos, (setter)UIDrawable::set_grid_pos, "Position in grid tile coordinates (only when parent is Grid)", (void*)PyObjectsEnum::UIARC},
|
||||
{"grid_size", (getter)UIDrawable::get_grid_size, (setter)UIDrawable::set_grid_size, "Size in grid tile coordinates (only when parent is Grid)", (void*)PyObjectsEnum::UIARC},
|
||||
UIDRAWABLE_GETSETTERS,
|
||||
UIDRAWABLE_PARENT_GETSETTERS(PyObjectsEnum::UIARC),
|
||||
UIDRAWABLE_ALIGNMENT_GETSETTERS(PyObjectsEnum::UIARC),
|
||||
|
|
|
|||
|
|
@ -285,6 +285,8 @@ PyGetSetDef UICaption::getsetters[] = {
|
|||
{"x", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "X coordinate of top-left corner", (void*)((intptr_t)PyObjectsEnum::UICAPTION << 8 | 0)},
|
||||
{"y", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "Y coordinate of top-left corner", (void*)((intptr_t)PyObjectsEnum::UICAPTION << 8 | 1)},
|
||||
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos, "(x, y) vector", (void*)PyObjectsEnum::UICAPTION},
|
||||
{"grid_pos", (getter)UIDrawable::get_grid_pos, (setter)UIDrawable::set_grid_pos, "Position in grid tile coordinates (only when parent is Grid)", (void*)PyObjectsEnum::UICAPTION},
|
||||
{"grid_size", (getter)UIDrawable::get_grid_size, (setter)UIDrawable::set_grid_size, "Size in grid tile coordinates (only when parent is Grid)", (void*)PyObjectsEnum::UICAPTION},
|
||||
{"size", (getter)UICaption::get_size, NULL, "Text dimensions as Vector (read-only)", NULL},
|
||||
{"w", (getter)UICaption::get_w, NULL, "Text width in pixels (read-only)", NULL},
|
||||
{"h", (getter)UICaption::get_h, NULL, "Text height in pixels (read-only)", NULL},
|
||||
|
|
|
|||
|
|
@ -394,6 +394,8 @@ PyGetSetDef UICircle::getsetters[] = {
|
|||
"Name for finding this element.", (void*)PyObjectsEnum::UICIRCLE},
|
||||
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos,
|
||||
"Position as a Vector (same as center).", (void*)PyObjectsEnum::UICIRCLE},
|
||||
{"grid_pos", (getter)UIDrawable::get_grid_pos, (setter)UIDrawable::set_grid_pos, "Position in grid tile coordinates (only when parent is Grid)", (void*)PyObjectsEnum::UICIRCLE},
|
||||
{"grid_size", (getter)UIDrawable::get_grid_size, (setter)UIDrawable::set_grid_size, "Size in grid tile coordinates (only when parent is Grid)", (void*)PyObjectsEnum::UICIRCLE},
|
||||
UIDRAWABLE_GETSETTERS,
|
||||
UIDRAWABLE_PARENT_GETSETTERS(PyObjectsEnum::UICIRCLE),
|
||||
UIDRAWABLE_ALIGNMENT_GETSETTERS(PyObjectsEnum::UICIRCLE),
|
||||
|
|
|
|||
|
|
@ -578,6 +578,228 @@ int UIDrawable::set_pos(PyObject* self, PyObject* value, void* closure) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// #221 - Grid coordinate properties (only valid when parent is UIGrid)
|
||||
PyObject* UIDrawable::get_grid_pos(PyObject* self, void* closure) {
|
||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
||||
UIDrawable* drawable = extractDrawable(self, objtype);
|
||||
if (!drawable) return NULL;
|
||||
|
||||
// Check if parent is a UIGrid
|
||||
auto parent_ptr = drawable->getParent();
|
||||
if (!parent_ptr) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "drawable is not a child of a Grid");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
UIGrid* grid = dynamic_cast<UIGrid*>(parent_ptr.get());
|
||||
if (!grid) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "drawable is not a direct child of a Grid");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Calculate grid position from pixel position
|
||||
sf::Vector2f cell_size = grid->getEffectiveCellSize();
|
||||
float grid_x = drawable->position.x / cell_size.x;
|
||||
float grid_y = drawable->position.y / cell_size.y;
|
||||
|
||||
// Return as Vector
|
||||
PyObject* vector_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
||||
if (!vector_type) return NULL;
|
||||
|
||||
PyObject* args = Py_BuildValue("(ff)", grid_x, grid_y);
|
||||
PyObject* result = PyObject_CallObject(vector_type, args);
|
||||
Py_DECREF(vector_type);
|
||||
Py_DECREF(args);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int UIDrawable::set_grid_pos(PyObject* self, PyObject* value, void* closure) {
|
||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
||||
UIDrawable* drawable = extractDrawable(self, objtype);
|
||||
if (!drawable) return -1;
|
||||
|
||||
// Check if parent is a UIGrid
|
||||
auto parent_ptr = drawable->getParent();
|
||||
if (!parent_ptr) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "drawable is not a child of a Grid");
|
||||
return -1;
|
||||
}
|
||||
|
||||
UIGrid* grid = dynamic_cast<UIGrid*>(parent_ptr.get());
|
||||
if (!grid) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "drawable is not a direct child of a Grid");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parse the grid position value
|
||||
float grid_x, grid_y;
|
||||
if (PyTuple_Check(value) && PyTuple_Size(value) == 2) {
|
||||
PyObject* x_obj = PyTuple_GetItem(value, 0);
|
||||
PyObject* y_obj = PyTuple_GetItem(value, 1);
|
||||
|
||||
if (PyFloat_Check(x_obj) || PyLong_Check(x_obj)) {
|
||||
grid_x = PyFloat_Check(x_obj) ? PyFloat_AsDouble(x_obj) : static_cast<float>(PyLong_AsLong(x_obj));
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "grid_pos x must be a number");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (PyFloat_Check(y_obj) || PyLong_Check(y_obj)) {
|
||||
grid_y = PyFloat_Check(y_obj) ? PyFloat_AsDouble(y_obj) : static_cast<float>(PyLong_AsLong(y_obj));
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "grid_pos y must be a number");
|
||||
return -1;
|
||||
}
|
||||
} else if (PyObject_HasAttrString(value, "x") && PyObject_HasAttrString(value, "y")) {
|
||||
// Vector-like object
|
||||
PyObject* x_attr = PyObject_GetAttrString(value, "x");
|
||||
PyObject* y_attr = PyObject_GetAttrString(value, "y");
|
||||
|
||||
if (x_attr && (PyFloat_Check(x_attr) || PyLong_Check(x_attr))) {
|
||||
grid_x = PyFloat_Check(x_attr) ? PyFloat_AsDouble(x_attr) : static_cast<float>(PyLong_AsLong(x_attr));
|
||||
} else {
|
||||
Py_XDECREF(x_attr);
|
||||
Py_XDECREF(y_attr);
|
||||
PyErr_SetString(PyExc_TypeError, "grid_pos x must be a number");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (y_attr && (PyFloat_Check(y_attr) || PyLong_Check(y_attr))) {
|
||||
grid_y = PyFloat_Check(y_attr) ? PyFloat_AsDouble(y_attr) : static_cast<float>(PyLong_AsLong(y_attr));
|
||||
} else {
|
||||
Py_XDECREF(x_attr);
|
||||
Py_XDECREF(y_attr);
|
||||
PyErr_SetString(PyExc_TypeError, "grid_pos y must be a number");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_DECREF(x_attr);
|
||||
Py_DECREF(y_attr);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "grid_pos must be a tuple (x, y) or Vector");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Convert grid position to pixel position
|
||||
sf::Vector2f cell_size = grid->getEffectiveCellSize();
|
||||
drawable->position.x = grid_x * cell_size.x;
|
||||
drawable->position.y = grid_y * cell_size.y;
|
||||
drawable->onPositionChanged();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* UIDrawable::get_grid_size(PyObject* self, void* closure) {
|
||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
||||
UIDrawable* drawable = extractDrawable(self, objtype);
|
||||
if (!drawable) return NULL;
|
||||
|
||||
// Check if parent is a UIGrid
|
||||
auto parent_ptr = drawable->getParent();
|
||||
if (!parent_ptr) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "drawable is not a child of a Grid");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
UIGrid* grid = dynamic_cast<UIGrid*>(parent_ptr.get());
|
||||
if (!grid) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "drawable is not a direct child of a Grid");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Calculate grid size from pixel size
|
||||
sf::FloatRect bounds = drawable->get_bounds();
|
||||
sf::Vector2f cell_size = grid->getEffectiveCellSize();
|
||||
float grid_w = bounds.width / cell_size.x;
|
||||
float grid_h = bounds.height / cell_size.y;
|
||||
|
||||
// Return as Vector
|
||||
PyObject* vector_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
||||
if (!vector_type) return NULL;
|
||||
|
||||
PyObject* args = Py_BuildValue("(ff)", grid_w, grid_h);
|
||||
PyObject* result = PyObject_CallObject(vector_type, args);
|
||||
Py_DECREF(vector_type);
|
||||
Py_DECREF(args);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int UIDrawable::set_grid_size(PyObject* self, PyObject* value, void* closure) {
|
||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
||||
UIDrawable* drawable = extractDrawable(self, objtype);
|
||||
if (!drawable) return -1;
|
||||
|
||||
// Check if parent is a UIGrid
|
||||
auto parent_ptr = drawable->getParent();
|
||||
if (!parent_ptr) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "drawable is not a child of a Grid");
|
||||
return -1;
|
||||
}
|
||||
|
||||
UIGrid* grid = dynamic_cast<UIGrid*>(parent_ptr.get());
|
||||
if (!grid) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "drawable is not a direct child of a Grid");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parse the grid size value
|
||||
float grid_w, grid_h;
|
||||
if (PyTuple_Check(value) && PyTuple_Size(value) == 2) {
|
||||
PyObject* w_obj = PyTuple_GetItem(value, 0);
|
||||
PyObject* h_obj = PyTuple_GetItem(value, 1);
|
||||
|
||||
if (PyFloat_Check(w_obj) || PyLong_Check(w_obj)) {
|
||||
grid_w = PyFloat_Check(w_obj) ? PyFloat_AsDouble(w_obj) : static_cast<float>(PyLong_AsLong(w_obj));
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "grid_size width must be a number");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (PyFloat_Check(h_obj) || PyLong_Check(h_obj)) {
|
||||
grid_h = PyFloat_Check(h_obj) ? PyFloat_AsDouble(h_obj) : static_cast<float>(PyLong_AsLong(h_obj));
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "grid_size height must be a number");
|
||||
return -1;
|
||||
}
|
||||
} else if (PyObject_HasAttrString(value, "x") && PyObject_HasAttrString(value, "y")) {
|
||||
// Vector-like object
|
||||
PyObject* x_attr = PyObject_GetAttrString(value, "x");
|
||||
PyObject* y_attr = PyObject_GetAttrString(value, "y");
|
||||
|
||||
if (x_attr && (PyFloat_Check(x_attr) || PyLong_Check(x_attr))) {
|
||||
grid_w = PyFloat_Check(x_attr) ? PyFloat_AsDouble(x_attr) : static_cast<float>(PyLong_AsLong(x_attr));
|
||||
} else {
|
||||
Py_XDECREF(x_attr);
|
||||
Py_XDECREF(y_attr);
|
||||
PyErr_SetString(PyExc_TypeError, "grid_size width must be a number");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (y_attr && (PyFloat_Check(y_attr) || PyLong_Check(y_attr))) {
|
||||
grid_h = PyFloat_Check(y_attr) ? PyFloat_AsDouble(y_attr) : static_cast<float>(PyLong_AsLong(y_attr));
|
||||
} else {
|
||||
Py_XDECREF(x_attr);
|
||||
Py_XDECREF(y_attr);
|
||||
PyErr_SetString(PyExc_TypeError, "grid_size height must be a number");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_DECREF(x_attr);
|
||||
Py_DECREF(y_attr);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "grid_size must be a tuple (w, h) or Vector");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Convert grid size to pixel size and resize
|
||||
sf::Vector2f cell_size = grid->getEffectiveCellSize();
|
||||
drawable->resize(grid_w * cell_size.x, grid_h * cell_size.y);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// #122 - Parent-child hierarchy implementation
|
||||
void UIDrawable::setParent(std::shared_ptr<UIDrawable> new_parent) {
|
||||
parent = new_parent;
|
||||
|
|
|
|||
|
|
@ -92,6 +92,12 @@ public:
|
|||
static int set_float_member(PyObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_pos(PyObject* self, void* closure);
|
||||
static int set_pos(PyObject* self, PyObject* value, void* closure);
|
||||
|
||||
// #221 - Grid coordinate properties (only valid when parent is UIGrid)
|
||||
static PyObject* get_grid_pos(PyObject* self, void* closure);
|
||||
static int set_grid_pos(PyObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_grid_size(PyObject* self, void* closure);
|
||||
static int set_grid_size(PyObject* self, PyObject* value, void* closure);
|
||||
|
||||
// Z-order for rendering (lower values rendered first, higher values on top)
|
||||
int z_index = 0;
|
||||
|
|
|
|||
|
|
@ -464,6 +464,8 @@ PyGetSetDef UIFrame::getsetters[] = {
|
|||
), (void*)PyObjectsEnum::UIFRAME},
|
||||
{"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name, "Name for finding elements", (void*)PyObjectsEnum::UIFRAME},
|
||||
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos, "Position as a Vector", (void*)PyObjectsEnum::UIFRAME},
|
||||
{"grid_pos", (getter)UIDrawable::get_grid_pos, (setter)UIDrawable::set_grid_pos, "Position in grid tile coordinates (only when parent is Grid)", (void*)PyObjectsEnum::UIFRAME},
|
||||
{"grid_size", (getter)UIDrawable::get_grid_size, (setter)UIDrawable::set_grid_size, "Size in grid tile coordinates (only when parent is Grid)", (void*)PyObjectsEnum::UIFRAME},
|
||||
{"clip_children", (getter)UIFrame::get_clip_children, (setter)UIFrame::set_clip_children, "Whether to clip children to frame bounds", NULL},
|
||||
{"cache_subtree", (getter)UIFrame::get_cache_subtree, (setter)UIFrame::set_cache_subtree, "#144: Cache subtree rendering to texture for performance", NULL},
|
||||
UIDRAWABLE_GETSETTERS,
|
||||
|
|
|
|||
|
|
@ -2177,6 +2177,7 @@ PyGetSetDef UIGrid::getsetters[] = {
|
|||
{"grid_h", (getter)UIGrid::get_grid_h, NULL, "Grid height in cells", NULL},
|
||||
{"position", (getter)UIGrid::get_position, (setter)UIGrid::set_position, "Position of the grid (x, y)", NULL},
|
||||
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos, "Position of the grid as Vector", (void*)PyObjectsEnum::UIGRID},
|
||||
{"grid_pos", (getter)UIDrawable::get_grid_pos, (setter)UIDrawable::set_grid_pos, "Position in parent grid's tile coordinates (only when parent is Grid)", (void*)PyObjectsEnum::UIGRID},
|
||||
{"size", (getter)UIGrid::get_size, (setter)UIGrid::set_size, "Size of the grid as Vector (width, height)", NULL},
|
||||
{"center", (getter)UIGrid::get_center, (setter)UIGrid::set_center, "Grid coordinate at the center of the Grid's view (pan)", NULL},
|
||||
|
||||
|
|
@ -2383,6 +2384,13 @@ std::optional<sf::Vector2i> UIGrid::screenToCell(sf::Vector2f screen_pos) const
|
|||
return sf::Vector2i(cell_x, cell_y);
|
||||
}
|
||||
|
||||
// #221 - Get effective cell size (texture size * zoom)
|
||||
sf::Vector2f UIGrid::getEffectiveCellSize() const {
|
||||
float cell_w = ptex ? static_cast<float>(ptex->sprite_width) : static_cast<float>(DEFAULT_CELL_WIDTH);
|
||||
float cell_h = ptex ? static_cast<float>(ptex->sprite_height) : static_cast<float>(DEFAULT_CELL_HEIGHT);
|
||||
return sf::Vector2f(cell_w * zoom, cell_h * zoom);
|
||||
}
|
||||
|
||||
// #142 - Update cell hover state and fire callbacks
|
||||
void UIGrid::updateCellHover(sf::Vector2f mousepos) {
|
||||
auto new_cell = screenToCell(mousepos);
|
||||
|
|
|
|||
|
|
@ -135,6 +135,9 @@ public:
|
|||
// #142 - Cell coordinate conversion (screen pos -> cell coords)
|
||||
std::optional<sf::Vector2i> screenToCell(sf::Vector2f screen_pos) const;
|
||||
|
||||
// #221 - Get effective cell size (texture size * zoom)
|
||||
sf::Vector2f getEffectiveCellSize() const;
|
||||
|
||||
// #142 - Update cell hover state (called from PyScene)
|
||||
void updateCellHover(sf::Vector2f mousepos);
|
||||
|
||||
|
|
|
|||
|
|
@ -464,6 +464,8 @@ PyGetSetDef UILine::getsetters[] = {
|
|||
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos,
|
||||
MCRF_PROPERTY(pos, "Position as a Vector (midpoint of line)."),
|
||||
(void*)PyObjectsEnum::UILINE},
|
||||
{"grid_pos", (getter)UIDrawable::get_grid_pos, (setter)UIDrawable::set_grid_pos, "Position in grid tile coordinates (only when parent is Grid)", (void*)PyObjectsEnum::UILINE},
|
||||
{"grid_size", (getter)UIDrawable::get_grid_size, (setter)UIDrawable::set_grid_size, "Size in grid tile coordinates (only when parent is Grid)", (void*)PyObjectsEnum::UILINE},
|
||||
UIDRAWABLE_GETSETTERS,
|
||||
UIDRAWABLE_PARENT_GETSETTERS(PyObjectsEnum::UILINE),
|
||||
UIDRAWABLE_ALIGNMENT_GETSETTERS(PyObjectsEnum::UILINE),
|
||||
|
|
|
|||
|
|
@ -354,6 +354,8 @@ PyGetSetDef UISprite::getsetters[] = {
|
|||
), (void*)PyObjectsEnum::UISPRITE},
|
||||
{"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name, "Name for finding elements", (void*)PyObjectsEnum::UISPRITE},
|
||||
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos, "Position as a Vector", (void*)PyObjectsEnum::UISPRITE},
|
||||
{"grid_pos", (getter)UIDrawable::get_grid_pos, (setter)UIDrawable::set_grid_pos, "Position in grid tile coordinates (only when parent is Grid)", (void*)PyObjectsEnum::UISPRITE},
|
||||
{"grid_size", (getter)UIDrawable::get_grid_size, (setter)UIDrawable::set_grid_size, "Size in grid tile coordinates (only when parent is Grid)", (void*)PyObjectsEnum::UISPRITE},
|
||||
UIDRAWABLE_GETSETTERS,
|
||||
UIDRAWABLE_PARENT_GETSETTERS(PyObjectsEnum::UISPRITE),
|
||||
UIDRAWABLE_ALIGNMENT_GETSETTERS(PyObjectsEnum::UISPRITE),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue