diff --git a/base_position_uiframe_test.png b/base_position_uiframe_test.png new file mode 100644 index 0000000..2126533 Binary files /dev/null and b/base_position_uiframe_test.png differ 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();