#include "UISprite.h" #include "GameEngine.h" #include "PyVector.h" #include "PythonObjectCache.h" // UIDrawable methods now in UIBase.h UIDrawable* UISprite::click_at(sf::Vector2f point) { if (click_callable) { if(sprite.getGlobalBounds().contains(point)) return this; } return NULL; } UISprite::UISprite() : sprite_index(0), ptex(nullptr) { // Initialize sprite to safe defaults position = sf::Vector2f(0.0f, 0.0f); // Set base class position sprite.setPosition(position); // Sync sprite position sprite.setScale(1.0f, 1.0f); } UISprite::UISprite(std::shared_ptr _ptex, int _sprite_index, sf::Vector2f _pos, float _scale) : ptex(_ptex), sprite_index(_sprite_index) { position = _pos; // Set base class position sprite = ptex->sprite(sprite_index, position, sf::Vector2f(_scale, _scale)); } UISprite::UISprite(const UISprite& other) : UIDrawable(other), sprite_index(other.sprite_index), sprite(other.sprite), ptex(other.ptex) { } UISprite& UISprite::operator=(const UISprite& other) { if (this != &other) { UIDrawable::operator=(other); sprite_index = other.sprite_index; sprite = other.sprite; ptex = other.ptex; } return *this; } UISprite::UISprite(UISprite&& other) noexcept : UIDrawable(std::move(other)), sprite_index(other.sprite_index), sprite(std::move(other.sprite)), ptex(std::move(other.ptex)) { } UISprite& UISprite::operator=(UISprite&& other) noexcept { if (this != &other) { UIDrawable::operator=(std::move(other)); sprite_index = other.sprite_index; sprite = std::move(other.sprite); ptex = std::move(other.ptex); } return *this; } /* void UISprite::render(sf::Vector2f offset) { sprite.move(offset); Resources::game->getWindow().draw(sprite); sprite.move(-offset); } */ void UISprite::render(sf::Vector2f offset, sf::RenderTarget& target) { // Check visibility if (!visible) return; // Apply opacity auto color = sprite.getColor(); color.a = static_cast(255 * opacity); sprite.setColor(color); sprite.move(offset); target.draw(sprite); sprite.move(-offset); // Restore original alpha color.a = 255; sprite.setColor(color); } void UISprite::setPosition(sf::Vector2f pos) { position = pos; // Update base class position sprite.setPosition(position); // Sync sprite position } void UISprite::setScale(sf::Vector2f s) { sprite.setScale(s); } void UISprite::setTexture(std::shared_ptr _ptex, int _sprite_index) { ptex = _ptex; if (_sprite_index != -1) // if you are changing textures, there's a good chance you need a new index too sprite_index = _sprite_index; sprite = ptex->sprite(sprite_index, position, sprite.getScale()); // Use base class position } void UISprite::setSpriteIndex(int _sprite_index) { sprite_index = _sprite_index; sprite = ptex->sprite(sprite_index, position, sprite.getScale()); // Use base class position } sf::Vector2f UISprite::getScale() const { return sprite.getScale(); } sf::Vector2f UISprite::getPosition() { return position; // Return base class position } std::shared_ptr UISprite::getTexture() { return ptex; } int UISprite::getSpriteIndex() { return sprite_index; } PyObjectsEnum UISprite::derived_type() { return PyObjectsEnum::UISPRITE; } // Phase 1 implementations sf::FloatRect UISprite::get_bounds() const { return sprite.getGlobalBounds(); } void UISprite::move(float dx, float dy) { position.x += dx; position.y += dy; sprite.setPosition(position); // Keep sprite in sync } void UISprite::resize(float w, float h) { // Calculate scale factors to achieve target size while preserving aspect ratio auto bounds = sprite.getLocalBounds(); if (bounds.width > 0 && bounds.height > 0) { float scaleX = w / bounds.width; float scaleY = h / bounds.height; // Use the smaller scale factor to maintain aspect ratio // This ensures the sprite fits within the given bounds float scale = std::min(scaleX, scaleY); // Apply uniform scaling to preserve aspect ratio sprite.setScale(scale, scale); } } void UISprite::onPositionChanged() { // Sync sprite position with base class position sprite.setPosition(position); } PyObject* UISprite::get_float_member(PyUISpriteObject* self, void* closure) { auto member_ptr = reinterpret_cast(closure); if (member_ptr == 0) return PyFloat_FromDouble(self->data->getPosition().x); else if (member_ptr == 1) return PyFloat_FromDouble(self->data->getPosition().y); else if (member_ptr == 2) return PyFloat_FromDouble(self->data->getScale().x); // scale X and Y are identical, presently else if (member_ptr == 3) return PyFloat_FromDouble(self->data->getScale().x); // scale_x else if (member_ptr == 4) return PyFloat_FromDouble(self->data->getScale().y); // scale_y else { PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); return nullptr; } } int UISprite::set_float_member(PyUISpriteObject* 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->setPosition(sf::Vector2f(val, self->data->getPosition().y)); else if (member_ptr == 1) //y self->data->setPosition(sf::Vector2f(self->data->getPosition().x, val)); else if (member_ptr == 2) // scale (uniform) self->data->setScale(sf::Vector2f(val, val)); else if (member_ptr == 3) // scale_x self->data->setScale(sf::Vector2f(val, self->data->getScale().y)); else if (member_ptr == 4) // scale_y self->data->setScale(sf::Vector2f(self->data->getScale().x, val)); return 0; } PyObject* UISprite::get_int_member(PyUISpriteObject* self, void* closure) { auto member_ptr = reinterpret_cast(closure); if (true) {} else { PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); return nullptr; } return PyLong_FromDouble(self->data->getSpriteIndex()); } int UISprite::set_int_member(PyUISpriteObject* self, PyObject* value, void* closure) { int val; auto member_ptr = reinterpret_cast(closure); if (PyLong_Check(value)) { val = PyLong_AsLong(value); } else { PyErr_SetString(PyExc_TypeError, "sprite_index must be an integer"); return -1; } // Validate sprite index is within texture bounds auto texture = self->data->getTexture(); if (texture) { int sprite_count = texture->getSpriteCount(); if (val < 0 || val >= sprite_count) { PyErr_Format(PyExc_ValueError, "Sprite index %d out of range. Texture has %d sprites (0-%d)", val, sprite_count, sprite_count - 1); return -1; } } self->data->setSpriteIndex(val); return 0; } PyObject* UISprite::get_texture(PyUISpriteObject* self, void* closure) { return self->data->getTexture()->pyObject(); } int UISprite::set_texture(PyUISpriteObject* self, PyObject* value, void* closure) { // Check if value is a Texture instance if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) { PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance"); return -1; } // Get the texture from the Python object auto pytexture = (PyTextureObject*)value; if (!pytexture->data) { PyErr_SetString(PyExc_ValueError, "Invalid texture object"); return -1; } // Update the sprite's texture self->data->setTexture(pytexture->data); return 0; } PyObject* UISprite::get_pos(PyUISpriteObject* 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->getPosition(); obj->data = sf::Vector2f(pos.x, pos.y); } return (PyObject*)obj; } int UISprite::set_pos(PyUISpriteObject* 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->setPosition(vec->data); return 0; } // Define the PyObjectType alias for the macros typedef PyUISpriteObject PyObjectType; // Method definitions PyMethodDef UISprite_methods[] = { UIDRAWABLE_METHODS, {NULL} // Sentinel }; PyGetSetDef UISprite::getsetters[] = { {"x", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "X coordinate of top-left corner", (void*)((intptr_t)PyObjectsEnum::UISPRITE << 8 | 0)}, {"y", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "Y coordinate of top-left corner", (void*)((intptr_t)PyObjectsEnum::UISPRITE << 8 | 1)}, {"scale", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Uniform size factor", (void*)2}, {"scale_x", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Horizontal scale factor", (void*)3}, {"scale_y", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Vertical scale factor", (void*)4}, {"sprite_index", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Which sprite on the texture is shown", NULL}, {"sprite_number", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Sprite index (DEPRECATED: use sprite_index instead)", NULL}, {"texture", (getter)UISprite::get_texture, (setter)UISprite::set_texture, "Texture object", NULL}, {"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UISPRITE}, {"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (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}, UIDRAWABLE_GETSETTERS, {NULL} }; PyObject* UISprite::repr(PyUISpriteObject* self) { std::ostringstream ss; if (!self->data) ss << ""; else { //auto sprite = self->data->sprite; ss << ""; } std::string repr_str = ss.str(); return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); } int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds) { // Define all parameters with defaults PyObject* pos_obj = nullptr; PyObject* texture = nullptr; int sprite_index = 0; float scale = 1.0f; float scale_x = 1.0f; float scale_y = 1.0f; 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; // Keywords list matches the new spec: positional args first, then all keyword args static const char* kwlist[] = { "pos", "texture", "sprite_index", // Positional args (as per spec) // Keyword-only args "scale", "scale_x", "scale_y", "click", "visible", "opacity", "z_index", "name", "x", "y", nullptr }; // Parse arguments with | for optional positional args if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOifffOifizff", const_cast(kwlist), &pos_obj, &texture, &sprite_index, // Positional &scale, &scale_x, &scale_y, &click_handler, &visible, &opacity, &z_index, &name, &x, &y)) { 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; } } } // Handle texture - allow None or use default std::shared_ptr texture_ptr = nullptr; if (texture && texture != Py_None) { if (!PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) { PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance or None"); return -1; } auto pytexture = (PyTextureObject*)texture; texture_ptr = pytexture->data; } else { // Use default texture when None or not provided texture_ptr = McRFPy_API::default_texture; } if (!texture_ptr) { PyErr_SetString(PyExc_RuntimeError, "No texture provided and no default texture available"); return -1; } // Create the sprite self->data = std::make_shared(texture_ptr, sprite_index, sf::Vector2f(x, y), scale); // Set scale properties if (scale_x != 1.0f || scale_y != 1.0f) { // If scale_x or scale_y were explicitly set, use them self->data->setScale(sf::Vector2f(scale_x, scale_y)); } else if (scale != 1.0f) { // Otherwise use uniform scale self->data->setScale(sf::Vector2f(scale, scale)); } // Set other properties self->data->visible = visible; self->data->opacity = opacity; self->data->z_index = z_index; 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); } // Initialize weak reference list self->weakreflist = NULL; // 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; } // Property system implementation for animations bool UISprite::setProperty(const std::string& name, float value) { if (name == "x") { position.x = value; sprite.setPosition(position); // Keep sprite in sync return true; } else if (name == "y") { position.y = value; sprite.setPosition(position); // Keep sprite in sync return true; } else if (name == "scale") { sprite.setScale(sf::Vector2f(value, value)); return true; } else if (name == "scale_x") { sprite.setScale(sf::Vector2f(value, sprite.getScale().y)); return true; } else if (name == "scale_y") { sprite.setScale(sf::Vector2f(sprite.getScale().x, value)); return true; } else if (name == "z_index") { z_index = static_cast(value); return true; } return false; } bool UISprite::setProperty(const std::string& name, int value) { if (name == "sprite_index" || name == "sprite_number") { setSpriteIndex(value); return true; } else if (name == "z_index") { z_index = value; return true; } return false; } bool UISprite::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 == "scale") { value = sprite.getScale().x; // Assuming uniform scale return true; } else if (name == "scale_x") { value = sprite.getScale().x; return true; } else if (name == "scale_y") { value = sprite.getScale().y; return true; } else if (name == "z_index") { value = static_cast(z_index); return true; } return false; } bool UISprite::getProperty(const std::string& name, int& value) const { if (name == "sprite_index" || name == "sprite_number") { value = sprite_index; return true; } else if (name == "z_index") { value = z_index; return true; } return false; }