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.
This commit is contained in:
parent
419f7d716a
commit
c4b4f12758
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
|
@ -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<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure) >> 8);
|
||||
int member = reinterpret_cast<intptr_t>(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<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure) >> 8);
|
||||
int member = reinterpret_cast<intptr_t>(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<float>(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<PyObjectsEnum>(reinterpret_cast<long>(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<PyObjectsEnum>(reinterpret_cast<long>(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<float>(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<float>(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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<std::vector<std::shared_ptr<UIDrawable>>>();
|
||||
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<std::vector<std::shared_ptr<UIDrawable>>>();
|
||||
}
|
||||
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue