diff --git a/src/UIArc.cpp b/src/UIArc.cpp index a92d7ef..2425d48 100644 --- a/src/UIArc.cpp +++ b/src/UIArc.cpp @@ -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), diff --git a/src/UICaption.cpp b/src/UICaption.cpp index 44d0e84..9270f2e 100644 --- a/src/UICaption.cpp +++ b/src/UICaption.cpp @@ -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}, diff --git a/src/UICircle.cpp b/src/UICircle.cpp index 1e3451b..44f950b 100644 --- a/src/UICircle.cpp +++ b/src/UICircle.cpp @@ -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), diff --git a/src/UIDrawable.cpp b/src/UIDrawable.cpp index 1accce5..a43fe59 100644 --- a/src/UIDrawable.cpp +++ b/src/UIDrawable.cpp @@ -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(reinterpret_cast(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(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(reinterpret_cast(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(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(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(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(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(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(reinterpret_cast(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(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(reinterpret_cast(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(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(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(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(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(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 new_parent) { parent = new_parent; diff --git a/src/UIDrawable.h b/src/UIDrawable.h index f627697..fe43215 100644 --- a/src/UIDrawable.h +++ b/src/UIDrawable.h @@ -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; diff --git a/src/UIFrame.cpp b/src/UIFrame.cpp index 4189ad0..98fda0f 100644 --- a/src/UIFrame.cpp +++ b/src/UIFrame.cpp @@ -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, diff --git a/src/UIGrid.cpp b/src/UIGrid.cpp index 9ecd402..683db31 100644 --- a/src/UIGrid.cpp +++ b/src/UIGrid.cpp @@ -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 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(ptex->sprite_width) : static_cast(DEFAULT_CELL_WIDTH); + float cell_h = ptex ? static_cast(ptex->sprite_height) : static_cast(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); diff --git a/src/UIGrid.h b/src/UIGrid.h index 9cc35ec..eb2bb04 100644 --- a/src/UIGrid.h +++ b/src/UIGrid.h @@ -135,6 +135,9 @@ public: // #142 - Cell coordinate conversion (screen pos -> cell coords) std::optional 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); diff --git a/src/UILine.cpp b/src/UILine.cpp index fa58216..cf5f89d 100644 --- a/src/UILine.cpp +++ b/src/UILine.cpp @@ -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), diff --git a/src/UISprite.cpp b/src/UISprite.cpp index 7461017..27b7c3f 100644 --- a/src/UISprite.cpp +++ b/src/UISprite.cpp @@ -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),