#include "UIFrame.h" #include "UICollection.h" #include "GameEngine.h" #include "PyVector.h" #include "UICaption.h" #include "UISprite.h" #include "UIGrid.h" #include "McRFPy_API.h" #include "PythonObjectCache.h" // UIDrawable methods now in UIBase.h UIDrawable* UIFrame::click_at(sf::Vector2f point) { // Check bounds first (optimization) 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 - position; // Check children in reverse order (top to bottom, highest z-index first) for (auto it = children->rbegin(); it != children->rend(); ++it) { auto& child = *it; if (!child->visible) continue; if (auto target = child->click_at(localPoint)) { return target; } } // No child handled it, check if we have a handler if (click_callable) { return this; } return nullptr; } UIFrame::UIFrame() : outline(0) { children = std::make_shared>>(); 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) { 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>>(); } UIFrame::~UIFrame() { children.reset(); } PyObjectsEnum UIFrame::derived_type() { return PyObjectsEnum::UIFRAME; } // Phase 1 implementations sf::FloatRect UIFrame::get_bounds() const { auto size = box.getSize(); return sf::FloatRect(position.x, position.y, size.x, size.y); } void UIFrame::move(float dx, float dy) { position.x += dx; position.y += dy; box.setPosition(position); // Keep box in sync } void UIFrame::resize(float w, float h) { box.setSize(sf::Vector2f(w, h)); } void UIFrame::onPositionChanged() { // Sync box position with base class position box.setPosition(position); } void UIFrame::render(sf::Vector2f offset, sf::RenderTarget& target) { // Check visibility if (!visible) return; // TODO: Apply opacity when SFML supports it on shapes // Check if we need to use RenderTexture for clipping if (clip_children && !children->empty()) { // Enable RenderTexture if not already enabled if (!use_render_texture) { auto size = box.getSize(); enableRenderTexture(static_cast(size.x), static_cast(size.y)); } // Update RenderTexture if dirty if (use_render_texture && render_dirty) { // Clear the RenderTexture render_texture->clear(sf::Color::Transparent); // Draw the frame box to RenderTexture box.setPosition(0, 0); // Render at origin in texture render_texture->draw(box); // Sort children by z_index if needed if (children_need_sort && !children->empty()) { std::sort(children->begin(), children->end(), [](const std::shared_ptr& a, const std::shared_ptr& b) { return a->z_index < b->z_index; }); children_need_sort = false; } // Render children to RenderTexture at local coordinates for (auto drawable : *children) { drawable->render(sf::Vector2f(0, 0), *render_texture); } // Finalize the RenderTexture render_texture->display(); // Update sprite render_sprite.setTexture(render_texture->getTexture()); render_dirty = false; } // Draw the RenderTexture sprite if (use_render_texture) { render_sprite.setPosition(offset + box.getPosition()); target.draw(render_sprite); } } else { // Standard rendering without clipping box.move(offset); target.draw(box); box.move(-offset); // Sort children by z_index if needed if (children_need_sort && !children->empty()) { std::sort(children->begin(), children->end(), [](const std::shared_ptr& a, const std::shared_ptr& b) { return a->z_index < b->z_index; }); children_need_sort = false; } for (auto drawable : *children) { drawable->render(offset + box.getPosition(), target); } } } PyObject* UIFrame::get_children(PyUIFrameObject* self, void* closure) { // create PyUICollection instance pointing to self->data->children auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UICollection"); auto o = (PyUICollectionObject*)type->tp_alloc(type, 0); Py_DECREF(type); if (o) { o->data = self->data->children; o->owner = self->data; // #122: Set owner for parent tracking } return (PyObject*)o; } PyObject* UIFrame::get_float_member(PyUIFrameObject* self, void* closure) { auto member_ptr = reinterpret_cast(closure); if (member_ptr == 0) return PyFloat_FromDouble(self->data->box.getPosition().x); else if (member_ptr == 1) return PyFloat_FromDouble(self->data->box.getPosition().y); else if (member_ptr == 2) return PyFloat_FromDouble(self->data->box.getSize().x); else if (member_ptr == 3) return PyFloat_FromDouble(self->data->box.getSize().y); else if (member_ptr == 4) return PyFloat_FromDouble(self->data->box.getOutlineThickness()); else { PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); return nullptr; } } int UIFrame::set_float_member(PyUIFrameObject* self, PyObject* value, void* closure) { float val; auto member_ptr = reinterpret_cast(closure); if (PyFloat_Check(value)) { val = PyFloat_AsDouble(value); } else if (PyLong_Check(value)) { val = PyLong_AsLong(value); } else { PyErr_SetString(PyExc_TypeError, "Value must be a number (int or float)"); return -1; } if (member_ptr == 0) { //x self->data->box.setPosition(val, self->data->box.getPosition().y); self->data->markDirty(); } else if (member_ptr == 1) { //y self->data->box.setPosition(self->data->box.getPosition().x, val); self->data->markDirty(); } else if (member_ptr == 2) { //w self->data->box.setSize(sf::Vector2f(val, self->data->box.getSize().y)); if (self->data->use_render_texture) { // Need to recreate RenderTexture with new size self->data->enableRenderTexture(static_cast(self->data->box.getSize().x), static_cast(self->data->box.getSize().y)); } self->data->markDirty(); } else if (member_ptr == 3) { //h self->data->box.setSize(sf::Vector2f(self->data->box.getSize().x, val)); if (self->data->use_render_texture) { // Need to recreate RenderTexture with new size self->data->enableRenderTexture(static_cast(self->data->box.getSize().x), static_cast(self->data->box.getSize().y)); } self->data->markDirty(); } else if (member_ptr == 4) { //outline self->data->box.setOutlineThickness(val); self->data->markDirty(); } return 0; } PyObject* UIFrame::get_color_member(PyUIFrameObject* self, void* closure) { // validate closure (should be impossible to be wrong, but it's thorough) auto member_ptr = reinterpret_cast(closure); if (member_ptr != 0 && member_ptr != 1) { PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); return nullptr; } //PyTypeObject* colorType = &PyColorType; auto colorType = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color"); PyObject* pyColor = colorType->tp_alloc(colorType, 0); if (pyColor == NULL) { std::cout << "failure to allocate mcrfpy.Color / PyColorType" << std::endl; return NULL; } PyColorObject* pyColorObj = reinterpret_cast(pyColor); // fetch correct member data sf::Color color; if (member_ptr == 0) { color = self->data->box.getFillColor(); //return Py_BuildValue("(iii)", color.r, color.g, color.b); } else if (member_ptr == 1) { color = self->data->box.getOutlineColor(); //return Py_BuildValue("(iii)", color.r, color.g, color.b); } return PyColor(color).pyObject(); } int UIFrame::set_color_member(PyUIFrameObject* self, PyObject* value, void* closure) { //TODO: this logic of (PyColor instance OR tuple -> sf::color) should be encapsulated for reuse auto member_ptr = reinterpret_cast(closure); int r, g, b, a; if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color"))) { sf::Color c = ((PyColorObject*)value)->data; r = c.r; g = c.g; b = c.b; a = c.a; } else if (!PyTuple_Check(value) || PyTuple_Size(value) < 3 || PyTuple_Size(value) > 4) { // reject non-Color, non-tuple value PyErr_SetString(PyExc_TypeError, "Value must be a tuple of 3 or 4 integers or an mcrfpy.Color object."); return -1; } else // get value from tuples { r = PyLong_AsLong(PyTuple_GetItem(value, 0)); g = PyLong_AsLong(PyTuple_GetItem(value, 1)); b = PyLong_AsLong(PyTuple_GetItem(value, 2)); a = 255; if (PyTuple_Size(value) == 4) { a = PyLong_AsLong(PyTuple_GetItem(value, 3)); } } if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 255) { PyErr_SetString(PyExc_ValueError, "Color values must be between 0 and 255."); return -1; } if (member_ptr == 0) { self->data->box.setFillColor(sf::Color(r, g, b, a)); self->data->markDirty(); } else if (member_ptr == 1) { self->data->box.setOutlineColor(sf::Color(r, g, b, a)); self->data->markDirty(); } else { PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); return -1; } return 0; } PyObject* UIFrame::get_pos(PyUIFrameObject* self, void* closure) { auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector"); auto obj = (PyVectorObject*)type->tp_alloc(type, 0); if (obj) { auto pos = self->data->box.getPosition(); obj->data = sf::Vector2f(pos.x, pos.y); } return (PyObject*)obj; } int UIFrame::set_pos(PyUIFrameObject* self, PyObject* value, void* closure) { PyVectorObject* vec = PyVector::from_arg(value); if (!vec) { PyErr_SetString(PyExc_TypeError, "pos must be a Vector or convertible to Vector"); return -1; } self->data->box.setPosition(vec->data); self->data->markDirty(); return 0; } PyObject* UIFrame::get_clip_children(PyUIFrameObject* self, void* closure) { return PyBool_FromLong(self->data->clip_children); } int UIFrame::set_clip_children(PyUIFrameObject* self, PyObject* value, void* closure) { if (!PyBool_Check(value)) { PyErr_SetString(PyExc_TypeError, "clip_children must be a boolean"); return -1; } bool new_clip = PyObject_IsTrue(value); if (new_clip != self->data->clip_children) { self->data->clip_children = new_clip; self->data->markDirty(); // Mark as needing redraw } return 0; } // Define the PyObjectType alias for the macros typedef PyUIFrameObject PyObjectType; // Method definitions PyMethodDef UIFrame_methods[] = { UIDRAWABLE_METHODS, {NULL} // Sentinel }; PyGetSetDef UIFrame::getsetters[] = { {"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}, {"children", (getter)UIFrame::get_children, NULL, "UICollection of objects on top of this one", NULL}, {"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, MCRF_PROPERTY(click, "Callable executed when object is clicked. " "Function receives (x, y) coordinates of click." ), (void*)PyObjectsEnum::UIFRAME}, {"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, MCRF_PROPERTY(z_index, "Z-order for rendering (lower values rendered first). " "Automatically triggers scene resort when changed." ), (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}, {"clip_children", (getter)UIFrame::get_clip_children, (setter)UIFrame::set_clip_children, "Whether to clip children to frame bounds", NULL}, UIDRAWABLE_GETSETTERS, UIDRAWABLE_PARENT_GETSETTERS(PyObjectsEnum::UIFRAME), {NULL} }; PyObject* UIFrame::repr(PyUIFrameObject* self) { std::ostringstream ss; if (!self->data) ss << ""; else { auto box = self->data->box; auto fc = box.getFillColor(); auto oc = box.getOutlineColor(); ss << "data->children->size() << " child objects" << ")>"; } std::string repr_str = ss.str(); return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); } int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds) { // Initialize children first self->data->children = std::make_shared>>(); // Initialize weak reference list self->weakreflist = NULL; // Define all parameters with defaults PyObject* pos_obj = nullptr; PyObject* size_obj = nullptr; PyObject* fill_color = nullptr; PyObject* outline_color = nullptr; float outline = 0.0f; PyObject* children_arg = nullptr; PyObject* click_handler = nullptr; int visible = 1; float opacity = 1.0f; int z_index = 0; const char* name = nullptr; float x = 0.0f, y = 0.0f, w = 0.0f, h = 0.0f; int clip_children = 0; // Keywords list matches the new spec: positional args first, then all keyword args static const char* kwlist[] = { "pos", "size", // Positional args (as per spec) // Keyword-only args "fill_color", "outline_color", "outline", "children", "click", "visible", "opacity", "z_index", "name", "x", "y", "w", "h", "clip_children", nullptr }; // Parse arguments with | for optional positional args if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOOfOOifizffffi", const_cast(kwlist), &pos_obj, &size_obj, // Positional &fill_color, &outline_color, &outline, &children_arg, &click_handler, &visible, &opacity, &z_index, &name, &x, &y, &w, &h, &clip_children)) { return -1; } // Handle position argument (can be tuple, Vector, or use x/y keywords) if (pos_obj) { PyVectorObject* vec = PyVector::from_arg(pos_obj); if (vec) { x = vec->data.x; y = vec->data.y; Py_DECREF(vec); } else { PyErr_Clear(); if (PyTuple_Check(pos_obj) && PyTuple_Size(pos_obj) == 2) { PyObject* x_val = PyTuple_GetItem(pos_obj, 0); PyObject* y_val = PyTuple_GetItem(pos_obj, 1); if ((PyFloat_Check(x_val) || PyLong_Check(x_val)) && (PyFloat_Check(y_val) || PyLong_Check(y_val))) { x = PyFloat_Check(x_val) ? PyFloat_AsDouble(x_val) : PyLong_AsLong(x_val); y = PyFloat_Check(y_val) ? PyFloat_AsDouble(y_val) : PyLong_AsLong(y_val); } else { PyErr_SetString(PyExc_TypeError, "pos tuple must contain numbers"); return -1; } } else { PyErr_SetString(PyExc_TypeError, "pos must be a tuple (x, y) or Vector"); return -1; } } } // If no pos_obj but x/y keywords were provided, they're already in x, y variables // Handle size argument (can be tuple or use w/h keywords) if (size_obj) { if (PyTuple_Check(size_obj) && PyTuple_Size(size_obj) == 2) { PyObject* w_val = PyTuple_GetItem(size_obj, 0); PyObject* h_val = PyTuple_GetItem(size_obj, 1); if ((PyFloat_Check(w_val) || PyLong_Check(w_val)) && (PyFloat_Check(h_val) || PyLong_Check(h_val))) { w = PyFloat_Check(w_val) ? PyFloat_AsDouble(w_val) : PyLong_AsLong(w_val); h = PyFloat_Check(h_val) ? PyFloat_AsDouble(h_val) : PyLong_AsLong(h_val); } else { PyErr_SetString(PyExc_TypeError, "size tuple must contain numbers"); return -1; } } else { PyErr_SetString(PyExc_TypeError, "size must be a tuple (w, h)"); return -1; } } // If no size_obj but w/h keywords were provided, they're already in w, h variables // Set the position and size self->data->position = sf::Vector2f(x, y); self->data->box.setPosition(self->data->position); self->data->box.setSize(sf::Vector2f(w, h)); self->data->box.setOutlineThickness(outline); // Handle fill_color if (fill_color && fill_color != Py_None) { PyColorObject* color_obj = PyColor::from_arg(fill_color); if (!color_obj) { PyErr_SetString(PyExc_TypeError, "fill_color must be a Color or color tuple"); return -1; } self->data->box.setFillColor(color_obj->data); Py_DECREF(color_obj); } else { self->data->box.setFillColor(sf::Color(0, 0, 0, 128)); // Default: semi-transparent black } // Handle outline_color if (outline_color && outline_color != Py_None) { PyColorObject* color_obj = PyColor::from_arg(outline_color); if (!color_obj) { PyErr_SetString(PyExc_TypeError, "outline_color must be a Color or color tuple"); return -1; } self->data->box.setOutlineColor(color_obj->data); Py_DECREF(color_obj); } else { self->data->box.setOutlineColor(sf::Color(255, 255, 255, 255)); // Default: white } // Set other properties self->data->visible = visible; self->data->opacity = opacity; self->data->z_index = z_index; self->data->clip_children = clip_children; if (name) { self->data->name = std::string(name); } // Handle click handler if (click_handler && click_handler != Py_None) { if (!PyCallable_Check(click_handler)) { PyErr_SetString(PyExc_TypeError, "click must be callable"); return -1; } self->data->click_register(click_handler); } // Process children argument if provided if (children_arg && children_arg != Py_None) { if (!PySequence_Check(children_arg)) { PyErr_SetString(PyExc_TypeError, "children must be a sequence"); return -1; } Py_ssize_t len = PySequence_Length(children_arg); for (Py_ssize_t i = 0; i < len; i++) { PyObject* child = PySequence_GetItem(children_arg, i); if (!child) return -1; // Check if it's a UIDrawable (Frame, Caption, Sprite, or Grid) PyObject* frame_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"); PyObject* caption_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"); PyObject* sprite_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"); PyObject* grid_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"); if (!PyObject_IsInstance(child, frame_type) && !PyObject_IsInstance(child, caption_type) && !PyObject_IsInstance(child, sprite_type) && !PyObject_IsInstance(child, grid_type)) { Py_DECREF(child); PyErr_SetString(PyExc_TypeError, "children must contain only Frame, Caption, Sprite, or Grid objects"); return -1; } // Get the shared_ptr and add to children std::shared_ptr drawable = nullptr; if (PyObject_IsInstance(child, frame_type)) { drawable = ((PyUIFrameObject*)child)->data; } else if (PyObject_IsInstance(child, caption_type)) { drawable = ((PyUICaptionObject*)child)->data; } else if (PyObject_IsInstance(child, sprite_type)) { drawable = ((PyUISpriteObject*)child)->data; } else if (PyObject_IsInstance(child, grid_type)) { drawable = ((PyUIGridObject*)child)->data; } // Clean up type references Py_DECREF(frame_type); Py_DECREF(caption_type); Py_DECREF(sprite_type); Py_DECREF(grid_type); if (drawable) { self->data->children->push_back(drawable); self->data->children_need_sort = true; } Py_DECREF(child); } } // Process click handler if provided if (click_handler && click_handler != Py_None) { if (!PyCallable_Check(click_handler)) { PyErr_SetString(PyExc_TypeError, "click must be callable"); return -1; } self->data->click_register(click_handler); } // Register in Python object cache if (self->data->serial_number == 0) { self->data->serial_number = PythonObjectCache::getInstance().assignSerial(); PyObject* weakref = PyWeakref_NewRef((PyObject*)self, NULL); if (weakref) { PythonObjectCache::getInstance().registerObject(self->data->serial_number, weakref); Py_DECREF(weakref); // Cache owns the reference now } } return 0; } // Animation property system implementation bool UIFrame::setProperty(const std::string& name, float value) { if (name == "x") { position.x = value; box.setPosition(position); // Keep box in sync markDirty(); return true; } else if (name == "y") { position.y = value; box.setPosition(position); // Keep box in sync markDirty(); return true; } else if (name == "w") { box.setSize(sf::Vector2f(value, box.getSize().y)); if (use_render_texture) { // Need to recreate RenderTexture with new size enableRenderTexture(static_cast(box.getSize().x), static_cast(box.getSize().y)); } markDirty(); return true; } else if (name == "h") { box.setSize(sf::Vector2f(box.getSize().x, value)); if (use_render_texture) { // Need to recreate RenderTexture with new size enableRenderTexture(static_cast(box.getSize().x), static_cast(box.getSize().y)); } markDirty(); return true; } else if (name == "outline") { box.setOutlineThickness(value); markDirty(); return true; } else if (name == "fill_color.r") { auto color = box.getFillColor(); color.r = std::clamp(static_cast(value), 0, 255); box.setFillColor(color); markDirty(); return true; } else if (name == "fill_color.g") { auto color = box.getFillColor(); color.g = std::clamp(static_cast(value), 0, 255); box.setFillColor(color); markDirty(); return true; } else if (name == "fill_color.b") { auto color = box.getFillColor(); color.b = std::clamp(static_cast(value), 0, 255); box.setFillColor(color); markDirty(); return true; } else if (name == "fill_color.a") { auto color = box.getFillColor(); color.a = std::clamp(static_cast(value), 0, 255); box.setFillColor(color); markDirty(); return true; } else if (name == "outline_color.r") { auto color = box.getOutlineColor(); color.r = std::clamp(static_cast(value), 0, 255); box.setOutlineColor(color); markDirty(); return true; } else if (name == "outline_color.g") { auto color = box.getOutlineColor(); color.g = std::clamp(static_cast(value), 0, 255); box.setOutlineColor(color); markDirty(); return true; } else if (name == "outline_color.b") { auto color = box.getOutlineColor(); color.b = std::clamp(static_cast(value), 0, 255); box.setOutlineColor(color); markDirty(); return true; } else if (name == "outline_color.a") { auto color = box.getOutlineColor(); color.a = std::clamp(static_cast(value), 0, 255); box.setOutlineColor(color); markDirty(); return true; } return false; } bool UIFrame::setProperty(const std::string& name, const sf::Color& value) { if (name == "fill_color") { box.setFillColor(value); markDirty(); return true; } else if (name == "outline_color") { box.setOutlineColor(value); markDirty(); return true; } return false; } bool UIFrame::setProperty(const std::string& name, const sf::Vector2f& value) { if (name == "position") { position = value; box.setPosition(position); // Keep box in sync markDirty(); return true; } else if (name == "size") { box.setSize(value); if (use_render_texture) { // Need to recreate RenderTexture with new size enableRenderTexture(static_cast(value.x), static_cast(value.y)); } markDirty(); return true; } return false; } bool UIFrame::getProperty(const std::string& name, float& value) const { if (name == "x") { value = position.x; return true; } else if (name == "y") { value = position.y; return true; } else if (name == "w") { value = box.getSize().x; return true; } else if (name == "h") { value = box.getSize().y; return true; } else if (name == "outline") { value = box.getOutlineThickness(); return true; } else if (name == "fill_color.r") { value = box.getFillColor().r; return true; } else if (name == "fill_color.g") { value = box.getFillColor().g; return true; } else if (name == "fill_color.b") { value = box.getFillColor().b; return true; } else if (name == "fill_color.a") { value = box.getFillColor().a; return true; } else if (name == "outline_color.r") { value = box.getOutlineColor().r; return true; } else if (name == "outline_color.g") { value = box.getOutlineColor().g; return true; } else if (name == "outline_color.b") { value = box.getOutlineColor().b; return true; } else if (name == "outline_color.a") { value = box.getOutlineColor().a; return true; } return false; } bool UIFrame::getProperty(const std::string& name, sf::Color& value) const { if (name == "fill_color") { value = box.getFillColor(); return true; } else if (name == "outline_color") { value = box.getOutlineColor(); return true; } return false; } bool UIFrame::getProperty(const std::string& name, sf::Vector2f& value) const { if (name == "position") { value = position; return true; } else if (name == "size") { value = box.getSize(); return true; } return false; }