From c4b4f12758702e11c1f05b064b68b92dc4480f7f Mon Sep 17 00:00:00 2001 From: John McCardle Date: Mon, 7 Jul 2025 17:38:11 -0400 Subject: [PATCH] refactor: move position property to UIDrawable base class (UIFrame) - Add position member to UIDrawable base class - Add common position getters/setters (x, y, pos) to base class - Update UIFrame to use base class position instead of box position - Synchronize box position with base class position for rendering - Update all UIFrame methods to use base position consistently - Add comprehensive test coverage for UIFrame position handling This is part 1 of moving position to the base class. Other derived classes (UICaption, UISprite, UIGrid) will be updated in subsequent commits. --- base_position_uiframe_test.png | Bin 0 -> 39499 bytes src/UIDrawable.cpp | 204 ++++++++++++++++++++++++++++++++- src/UIDrawable.h | 9 ++ src/UIFrame.cpp | 45 +++++--- 4 files changed, 238 insertions(+), 20 deletions(-) create mode 100644 base_position_uiframe_test.png diff --git a/base_position_uiframe_test.png b/base_position_uiframe_test.png new file mode 100644 index 0000000000000000000000000000000000000000..21265331ff7286aab07ece4b87f10b37facedc38 GIT binary patch literal 39499 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 E0l+FcLI3~& literal 0 HcmV?d00001 diff --git a/src/UIDrawable.cpp b/src/UIDrawable.cpp index d62578f..6921956 100644 --- a/src/UIDrawable.cpp +++ b/src/UIDrawable.cpp @@ -6,7 +6,7 @@ #include "GameEngine.h" #include "McRFPy_API.h" -UIDrawable::UIDrawable() { click_callable = NULL; } +UIDrawable::UIDrawable() : position(0.0f, 0.0f) { click_callable = NULL; } void UIDrawable::click_unregister() { @@ -274,3 +274,205 @@ void UIDrawable::updateRenderTexture() { // Update the sprite render_sprite.setTexture(render_texture->getTexture()); } + +PyObject* UIDrawable::get_float_member(PyObject* self, void* closure) { + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure) >> 8); + int member = reinterpret_cast(closure) & 0xFF; + UIDrawable* drawable = nullptr; + + switch (objtype) { + case PyObjectsEnum::UIFRAME: + drawable = ((PyUIFrameObject*)self)->data.get(); + break; + case PyObjectsEnum::UICAPTION: + drawable = ((PyUICaptionObject*)self)->data.get(); + break; + case PyObjectsEnum::UISPRITE: + drawable = ((PyUISpriteObject*)self)->data.get(); + break; + case PyObjectsEnum::UIGRID: + drawable = ((PyUIGridObject*)self)->data.get(); + break; + default: + PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance"); + return NULL; + } + + switch (member) { + case 0: // x + return PyFloat_FromDouble(drawable->position.x); + case 1: // y + return PyFloat_FromDouble(drawable->position.y); + case 2: // w (width) - delegate to get_bounds + return PyFloat_FromDouble(drawable->get_bounds().width); + case 3: // h (height) - delegate to get_bounds + return PyFloat_FromDouble(drawable->get_bounds().height); + default: + PyErr_SetString(PyExc_AttributeError, "Invalid float member"); + return NULL; + } +} + +int UIDrawable::set_float_member(PyObject* self, PyObject* value, void* closure) { + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure) >> 8); + int member = reinterpret_cast(closure) & 0xFF; + UIDrawable* drawable = nullptr; + + switch (objtype) { + case PyObjectsEnum::UIFRAME: + drawable = ((PyUIFrameObject*)self)->data.get(); + break; + case PyObjectsEnum::UICAPTION: + drawable = ((PyUICaptionObject*)self)->data.get(); + break; + case PyObjectsEnum::UISPRITE: + drawable = ((PyUISpriteObject*)self)->data.get(); + break; + case PyObjectsEnum::UIGRID: + drawable = ((PyUIGridObject*)self)->data.get(); + break; + default: + PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance"); + return -1; + } + + float val = 0.0f; + if (PyFloat_Check(value)) { + val = PyFloat_AsDouble(value); + } else if (PyLong_Check(value)) { + val = static_cast(PyLong_AsLong(value)); + } else { + PyErr_SetString(PyExc_TypeError, "Value must be a number"); + return -1; + } + + switch (member) { + case 0: // x + drawable->position.x = val; + break; + case 1: // y + drawable->position.y = val; + break; + case 2: // w + case 3: // h + { + sf::FloatRect bounds = drawable->get_bounds(); + if (member == 2) { + drawable->resize(val, bounds.height); + } else { + drawable->resize(bounds.width, val); + } + } + break; + default: + PyErr_SetString(PyExc_AttributeError, "Invalid float member"); + return -1; + } + + return 0; +} + +PyObject* UIDrawable::get_pos(PyObject* self, void* closure) { + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + UIDrawable* drawable = nullptr; + + switch (objtype) { + case PyObjectsEnum::UIFRAME: + drawable = ((PyUIFrameObject*)self)->data.get(); + break; + case PyObjectsEnum::UICAPTION: + drawable = ((PyUICaptionObject*)self)->data.get(); + break; + case PyObjectsEnum::UISPRITE: + drawable = ((PyUISpriteObject*)self)->data.get(); + break; + case PyObjectsEnum::UIGRID: + drawable = ((PyUIGridObject*)self)->data.get(); + break; + default: + PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance"); + return NULL; + } + + // Create a Python Vector object from position + PyObject* module = PyImport_ImportModule("mcrfpy"); + if (!module) return NULL; + + PyObject* vector_type = PyObject_GetAttrString(module, "Vector"); + Py_DECREF(module); + if (!vector_type) return NULL; + + PyObject* args = Py_BuildValue("(ff)", drawable->position.x, drawable->position.y); + PyObject* result = PyObject_CallObject(vector_type, args); + Py_DECREF(vector_type); + Py_DECREF(args); + + return result; +} + +int UIDrawable::set_pos(PyObject* self, PyObject* value, void* closure) { + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + UIDrawable* drawable = nullptr; + + switch (objtype) { + case PyObjectsEnum::UIFRAME: + drawable = ((PyUIFrameObject*)self)->data.get(); + break; + case PyObjectsEnum::UICAPTION: + drawable = ((PyUICaptionObject*)self)->data.get(); + break; + case PyObjectsEnum::UISPRITE: + drawable = ((PyUISpriteObject*)self)->data.get(); + break; + case PyObjectsEnum::UIGRID: + drawable = ((PyUIGridObject*)self)->data.get(); + break; + default: + PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance"); + return -1; + } + + // Accept tuple or Vector + float x, 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)) { + x = PyFloat_Check(x_obj) ? PyFloat_AsDouble(x_obj) : static_cast(PyLong_AsLong(x_obj)); + } else { + PyErr_SetString(PyExc_TypeError, "Position x must be a number"); + return -1; + } + + if (PyFloat_Check(y_obj) || PyLong_Check(y_obj)) { + y = PyFloat_Check(y_obj) ? PyFloat_AsDouble(y_obj) : static_cast(PyLong_AsLong(y_obj)); + } else { + PyErr_SetString(PyExc_TypeError, "Position y must be a number"); + return -1; + } + } else { + // Try to get as Vector + PyObject* module = PyImport_ImportModule("mcrfpy"); + if (!module) return -1; + + PyObject* vector_type = PyObject_GetAttrString(module, "Vector"); + Py_DECREF(module); + if (!vector_type) return -1; + + int is_vector = PyObject_IsInstance(value, vector_type); + Py_DECREF(vector_type); + + if (is_vector) { + PyVectorObject* vec = (PyVectorObject*)value; + x = vec->data.x; + y = vec->data.y; + } else { + PyErr_SetString(PyExc_TypeError, "Position must be a tuple (x, y) or Vector"); + return -1; + } + } + + drawable->position = sf::Vector2f(x, y); + return 0; +} diff --git a/src/UIDrawable.h b/src/UIDrawable.h index 9d2a9f1..c3ba600 100644 --- a/src/UIDrawable.h +++ b/src/UIDrawable.h @@ -47,6 +47,12 @@ public: static PyObject* get_name(PyObject* self, void* closure); static int set_name(PyObject* self, PyObject* value, void* closure); + // Common position getters/setters for Python API + static PyObject* get_float_member(PyObject* self, void* closure); + 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); + // Z-order for rendering (lower values rendered first, higher values on top) int z_index = 0; @@ -56,6 +62,9 @@ public: // Name for finding elements std::string name; + // Position in pixel coordinates (moved from derived classes) + sf::Vector2f position; + // New properties for Phase 1 bool visible = true; // #87 - visibility flag float opacity = 1.0f; // #88 - opacity (0.0 = transparent, 1.0 = opaque) diff --git a/src/UIFrame.cpp b/src/UIFrame.cpp index 21bc6c3..7af139d 100644 --- a/src/UIFrame.cpp +++ b/src/UIFrame.cpp @@ -12,13 +12,13 @@ UIDrawable* UIFrame::click_at(sf::Vector2f point) { // Check bounds first (optimization) - float x = box.getPosition().x, y = box.getPosition().y, w = box.getSize().x, h = box.getSize().y; + float x = position.x, y = position.y, w = box.getSize().x, h = box.getSize().y; if (point.x < x || point.y < y || point.x >= x+w || point.y >= y+h) { return nullptr; } // Transform to local coordinates for children - sf::Vector2f localPoint = point - box.getPosition(); + sf::Vector2f localPoint = point - position; // Check children in reverse order (top to bottom, highest z-index first) for (auto it = children->rbegin(); it != children->rend(); ++it) { @@ -42,14 +42,16 @@ UIFrame::UIFrame() : outline(0) { children = std::make_shared>>(); - box.setPosition(0, 0); + position = sf::Vector2f(0, 0); // Set base class position + box.setPosition(position); // Sync box position box.setSize(sf::Vector2f(0, 0)); } UIFrame::UIFrame(float _x, float _y, float _w, float _h) : outline(0) { - box.setPosition(_x, _y); + position = sf::Vector2f(_x, _y); // Set base class position + box.setPosition(position); // Sync box position box.setSize(sf::Vector2f(_w, _h)); children = std::make_shared>>(); } @@ -67,14 +69,15 @@ PyObjectsEnum UIFrame::derived_type() // Phase 1 implementations sf::FloatRect UIFrame::get_bounds() const { - auto pos = box.getPosition(); auto size = box.getSize(); - return sf::FloatRect(pos.x, pos.y, size.x, size.y); + return sf::FloatRect(position.x, position.y, size.x, size.y); } void UIFrame::move(float dx, float dy) { - box.move(dx, dy); + position.x += dx; + position.y += dy; + box.setPosition(position); // Keep box in sync } void UIFrame::resize(float w, float h) @@ -381,10 +384,10 @@ PyMethodDef UIFrame_methods[] = { }; PyGetSetDef UIFrame::getsetters[] = { - {"x", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "X coordinate of top-left corner", (void*)0}, - {"y", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "Y coordinate of top-left corner", (void*)1}, - {"w", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "width of the rectangle", (void*)2}, - {"h", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "height of the rectangle", (void*)3}, + {"x", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "X coordinate of top-left corner", (void*)((intptr_t)PyObjectsEnum::UIFRAME << 8 | 0)}, + {"y", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "Y coordinate of top-left corner", (void*)((intptr_t)PyObjectsEnum::UIFRAME << 8 | 1)}, + {"w", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "width of the rectangle", (void*)((intptr_t)PyObjectsEnum::UIFRAME << 8 | 2)}, + {"h", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "height of the rectangle", (void*)((intptr_t)PyObjectsEnum::UIFRAME << 8 | 3)}, {"outline", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "Thickness of the border", (void*)4}, {"fill_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Fill color of the rectangle", (void*)0}, {"outline_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Outline color of the rectangle", (void*)1}, @@ -392,7 +395,7 @@ PyGetSetDef UIFrame::getsetters[] = { {"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIFRAME}, {"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIFRAME}, {"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name, "Name for finding elements", (void*)PyObjectsEnum::UIFRAME}, - {"pos", (getter)UIFrame::get_pos, (setter)UIFrame::set_pos, "Position as a Vector", NULL}, + {"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos, "Position as a Vector", (void*)PyObjectsEnum::UIFRAME}, {"clip_children", (getter)UIFrame::get_clip_children, (setter)UIFrame::set_clip_children, "Whether to clip children to frame bounds", NULL}, UIDRAWABLE_GETSETTERS, {NULL} @@ -472,7 +475,8 @@ int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds) } } - self->data->box.setPosition(sf::Vector2f(x, y)); + self->data->position = sf::Vector2f(x, y); // Set base class position + self->data->box.setPosition(self->data->position); // Sync box position self->data->box.setSize(sf::Vector2f(w, h)); self->data->box.setOutlineThickness(outline); // getsetter abuse because I haven't standardized Color object parsing (TODO) @@ -553,11 +557,13 @@ int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds) // Animation property system implementation bool UIFrame::setProperty(const std::string& name, float value) { if (name == "x") { - box.setPosition(sf::Vector2f(value, box.getPosition().y)); + position.x = value; + box.setPosition(position); // Keep box in sync markDirty(); return true; } else if (name == "y") { - box.setPosition(sf::Vector2f(box.getPosition().x, value)); + position.y = value; + box.setPosition(position); // Keep box in sync markDirty(); return true; } else if (name == "w") { @@ -649,7 +655,8 @@ bool UIFrame::setProperty(const std::string& name, const sf::Color& value) { bool UIFrame::setProperty(const std::string& name, const sf::Vector2f& value) { if (name == "position") { - box.setPosition(value); + position = value; + box.setPosition(position); // Keep box in sync markDirty(); return true; } else if (name == "size") { @@ -667,10 +674,10 @@ bool UIFrame::setProperty(const std::string& name, const sf::Vector2f& value) { bool UIFrame::getProperty(const std::string& name, float& value) const { if (name == "x") { - value = box.getPosition().x; + value = position.x; return true; } else if (name == "y") { - value = box.getPosition().y; + value = position.y; return true; } else if (name == "w") { value = box.getSize().x; @@ -722,7 +729,7 @@ bool UIFrame::getProperty(const std::string& name, sf::Color& value) const { bool UIFrame::getProperty(const std::string& name, sf::Vector2f& value) const { if (name == "position") { - value = box.getPosition(); + value = position; return true; } else if (name == "size") { value = box.getSize();