#include "UIArc.h" #include "McRFPy_API.h" #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif UIArc::UIArc() : center(0.0f, 0.0f), radius(0.0f), start_angle(0.0f), end_angle(90.0f), color(sf::Color::White), thickness(1.0f), vertices_dirty(true) { position = center; } UIArc::UIArc(sf::Vector2f center, float radius, float startAngle, float endAngle, sf::Color color, float thickness) : center(center), radius(radius), start_angle(startAngle), end_angle(endAngle), color(color), thickness(thickness), vertices_dirty(true) { position = center; } UIArc::UIArc(const UIArc& other) : UIDrawable(other), center(other.center), radius(other.radius), start_angle(other.start_angle), end_angle(other.end_angle), color(other.color), thickness(other.thickness), vertices_dirty(true) { } UIArc& UIArc::operator=(const UIArc& other) { if (this != &other) { UIDrawable::operator=(other); center = other.center; radius = other.radius; start_angle = other.start_angle; end_angle = other.end_angle; color = other.color; thickness = other.thickness; vertices_dirty = true; } return *this; } UIArc::UIArc(UIArc&& other) noexcept : UIDrawable(std::move(other)), center(other.center), radius(other.radius), start_angle(other.start_angle), end_angle(other.end_angle), color(other.color), thickness(other.thickness), vertices(std::move(other.vertices)), vertices_dirty(other.vertices_dirty) { } UIArc& UIArc::operator=(UIArc&& other) noexcept { if (this != &other) { UIDrawable::operator=(std::move(other)); center = other.center; radius = other.radius; start_angle = other.start_angle; end_angle = other.end_angle; color = other.color; thickness = other.thickness; vertices = std::move(other.vertices); vertices_dirty = other.vertices_dirty; } return *this; } void UIArc::rebuildVertices() { vertices.clear(); vertices.setPrimitiveType(sf::TriangleStrip); // Calculate the arc parameters float inner_radius = radius - thickness / 2.0f; float outer_radius = radius + thickness / 2.0f; if (inner_radius < 0) inner_radius = 0; // Normalize angles float start_rad = start_angle * M_PI / 180.0f; float end_rad = end_angle * M_PI / 180.0f; // Calculate number of segments based on arc length float angle_span = end_rad - start_rad; int num_segments = std::max(3, static_cast(std::abs(angle_span * radius) / 5.0f)); num_segments = std::min(num_segments, 100); // Cap at 100 segments float angle_step = angle_span / num_segments; // Apply opacity to color sf::Color render_color = color; render_color.a = static_cast(render_color.a * opacity); // Build the triangle strip for (int i = 0; i <= num_segments; ++i) { float angle = start_rad + i * angle_step; float cos_a = std::cos(angle); float sin_a = std::sin(angle); // Inner vertex sf::Vector2f inner_pos( center.x + inner_radius * cos_a, center.y + inner_radius * sin_a ); vertices.append(sf::Vertex(inner_pos, render_color)); // Outer vertex sf::Vector2f outer_pos( center.x + outer_radius * cos_a, center.y + outer_radius * sin_a ); vertices.append(sf::Vertex(outer_pos, render_color)); } vertices_dirty = false; } void UIArc::render(sf::Vector2f offset, sf::RenderTarget& target) { if (!visible) return; if (vertices_dirty) { rebuildVertices(); } // Apply offset by creating a transformed copy sf::Transform transform; transform.translate(offset); target.draw(vertices, transform); } UIDrawable* UIArc::click_at(sf::Vector2f point) { if (!visible) return nullptr; // Calculate distance from center float dx = point.x - center.x; float dy = point.y - center.y; float dist = std::sqrt(dx * dx + dy * dy); // Check if within the arc's radial range float inner_radius = radius - thickness / 2.0f; float outer_radius = radius + thickness / 2.0f; if (inner_radius < 0) inner_radius = 0; if (dist < inner_radius || dist > outer_radius) { return nullptr; } // Check if within the angle range float angle = std::atan2(dy, dx) * 180.0f / M_PI; // Normalize angle to match start/end angle range float start = start_angle; float end = end_angle; // Handle angle wrapping while (angle < start - 180.0f) angle += 360.0f; while (angle > start + 180.0f) angle -= 360.0f; if ((start <= end && angle >= start && angle <= end) || (start > end && (angle >= start || angle <= end))) { return this; } return nullptr; } PyObjectsEnum UIArc::derived_type() { return PyObjectsEnum::UIARC; } sf::FloatRect UIArc::get_bounds() const { float outer_radius = radius + thickness / 2.0f; return sf::FloatRect( center.x - outer_radius, center.y - outer_radius, outer_radius * 2, outer_radius * 2 ); } void UIArc::move(float dx, float dy) { center.x += dx; center.y += dy; position = center; vertices_dirty = true; } void UIArc::resize(float w, float h) { // Resize by adjusting radius to fit in the given dimensions radius = std::min(w, h) / 2.0f - thickness / 2.0f; if (radius < 0) radius = 0; vertices_dirty = true; } // Property setters bool UIArc::setProperty(const std::string& name, float value) { if (name == "radius") { setRadius(value); markDirty(); // #144 - Content change return true; } else if (name == "start_angle") { setStartAngle(value); markDirty(); // #144 - Content change return true; } else if (name == "end_angle") { setEndAngle(value); markDirty(); // #144 - Content change return true; } else if (name == "thickness") { setThickness(value); markDirty(); // #144 - Content change return true; } else if (name == "x") { center.x = value; position = center; vertices_dirty = true; markCompositeDirty(); // #144 - Position change, texture still valid return true; } else if (name == "y") { center.y = value; position = center; vertices_dirty = true; markCompositeDirty(); // #144 - Position change, texture still valid return true; } return false; } bool UIArc::setProperty(const std::string& name, const sf::Color& value) { if (name == "color") { setColor(value); markDirty(); // #144 - Content change return true; } return false; } bool UIArc::setProperty(const std::string& name, const sf::Vector2f& value) { if (name == "center") { setCenter(value); markCompositeDirty(); // #144 - Position change, texture still valid return true; } return false; } bool UIArc::getProperty(const std::string& name, float& value) const { if (name == "radius") { value = radius; return true; } else if (name == "start_angle") { value = start_angle; return true; } else if (name == "end_angle") { value = end_angle; return true; } else if (name == "thickness") { value = thickness; return true; } else if (name == "x") { value = center.x; return true; } else if (name == "y") { value = center.y; return true; } return false; } bool UIArc::getProperty(const std::string& name, sf::Color& value) const { if (name == "color") { value = color; return true; } return false; } bool UIArc::getProperty(const std::string& name, sf::Vector2f& value) const { if (name == "center") { value = center; return true; } return false; } // Python API implementation PyObject* UIArc::get_center(PyUIArcObject* self, void* closure) { auto center = self->data->getCenter(); auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector"); if (!type) return NULL; PyObject* result = PyObject_CallFunction((PyObject*)type, "ff", center.x, center.y); Py_DECREF(type); return result; } int UIArc::set_center(PyUIArcObject* self, PyObject* value, void* closure) { PyVectorObject* vec = PyVector::from_arg(value); if (!vec) { PyErr_Clear(); PyErr_SetString(PyExc_TypeError, "center must be a Vector or tuple (x, y)"); return -1; } self->data->setCenter(vec->data); return 0; } PyObject* UIArc::get_radius(PyUIArcObject* self, void* closure) { return PyFloat_FromDouble(self->data->getRadius()); } int UIArc::set_radius(PyUIArcObject* self, PyObject* value, void* closure) { if (!PyNumber_Check(value)) { PyErr_SetString(PyExc_TypeError, "radius must be a number"); return -1; } self->data->setRadius(static_cast(PyFloat_AsDouble(value))); return 0; } PyObject* UIArc::get_start_angle(PyUIArcObject* self, void* closure) { return PyFloat_FromDouble(self->data->getStartAngle()); } int UIArc::set_start_angle(PyUIArcObject* self, PyObject* value, void* closure) { if (!PyNumber_Check(value)) { PyErr_SetString(PyExc_TypeError, "start_angle must be a number"); return -1; } self->data->setStartAngle(static_cast(PyFloat_AsDouble(value))); return 0; } PyObject* UIArc::get_end_angle(PyUIArcObject* self, void* closure) { return PyFloat_FromDouble(self->data->getEndAngle()); } int UIArc::set_end_angle(PyUIArcObject* self, PyObject* value, void* closure) { if (!PyNumber_Check(value)) { PyErr_SetString(PyExc_TypeError, "end_angle must be a number"); return -1; } self->data->setEndAngle(static_cast(PyFloat_AsDouble(value))); return 0; } PyObject* UIArc::get_color(PyUIArcObject* self, void* closure) { auto color = self->data->getColor(); auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color"); PyObject* args = Py_BuildValue("(iiii)", color.r, color.g, color.b, color.a); PyObject* obj = PyObject_CallObject((PyObject*)type, args); Py_DECREF(args); Py_DECREF(type); return obj; } int UIArc::set_color(PyUIArcObject* self, PyObject* value, void* closure) { auto color = PyColor::from_arg(value); if (!color) { PyErr_SetString(PyExc_TypeError, "color must be a Color or tuple (r, g, b) or (r, g, b, a)"); return -1; } self->data->setColor(color->data); return 0; } PyObject* UIArc::get_thickness(PyUIArcObject* self, void* closure) { return PyFloat_FromDouble(self->data->getThickness()); } int UIArc::set_thickness(PyUIArcObject* self, PyObject* value, void* closure) { if (!PyNumber_Check(value)) { PyErr_SetString(PyExc_TypeError, "thickness must be a number"); return -1; } self->data->setThickness(static_cast(PyFloat_AsDouble(value))); return 0; } // Required typedef for UIDRAWABLE_GETSETTERS and UIDRAWABLE_METHODS macro templates typedef PyUIArcObject PyObjectType; PyGetSetDef UIArc::getsetters[] = { {"center", (getter)UIArc::get_center, (setter)UIArc::set_center, "Center position of the arc", NULL}, {"radius", (getter)UIArc::get_radius, (setter)UIArc::set_radius, "Arc radius in pixels", NULL}, {"start_angle", (getter)UIArc::get_start_angle, (setter)UIArc::set_start_angle, "Starting angle in degrees", NULL}, {"end_angle", (getter)UIArc::get_end_angle, (setter)UIArc::set_end_angle, "Ending angle in degrees", NULL}, {"color", (getter)UIArc::get_color, (setter)UIArc::set_color, "Arc color", NULL}, {"thickness", (getter)UIArc::get_thickness, (setter)UIArc::set_thickness, "Line thickness", NULL}, {"on_click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Callable executed when arc is clicked.", (void*)PyObjectsEnum::UIARC}, {"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first).", (void*)PyObjectsEnum::UIARC}, {"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name, "Name for finding this element.", (void*)PyObjectsEnum::UIARC}, {"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos, "Position as a Vector (same as center).", (void*)PyObjectsEnum::UIARC}, UIDRAWABLE_GETSETTERS, UIDRAWABLE_PARENT_GETSETTERS(PyObjectsEnum::UIARC), {NULL} }; PyMethodDef UIArc_methods[] = { UIDRAWABLE_METHODS, {NULL} }; PyObject* UIArc::repr(PyUIArcObject* self) { std::ostringstream oss; if (!self->data) { oss << ""; } else { auto center = self->data->getCenter(); auto color = self->data->getColor(); oss << "data->getStartAngle() << ", " << self->data->getEndAngle() << ") " << "color=(" << (int)color.r << ", " << (int)color.g << ", " << (int)color.b << ", " << (int)color.a << ")>"; } std::string repr_str = oss.str(); return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); } int UIArc::init(PyUIArcObject* self, PyObject* args, PyObject* kwds) { // Arguments PyObject* center_obj = nullptr; float radius = 0.0f; float start_angle = 0.0f; float end_angle = 90.0f; PyObject* color_obj = nullptr; float thickness = 1.0f; PyObject* click_handler = nullptr; int visible = 1; float opacity = 1.0f; int z_index = 0; const char* name = nullptr; static const char* kwlist[] = { "center", "radius", "start_angle", "end_angle", "color", "thickness", "click", "visible", "opacity", "z_index", "name", nullptr }; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OfffOfOifiz", const_cast(kwlist), ¢er_obj, &radius, &start_angle, &end_angle, &color_obj, &thickness, &click_handler, &visible, &opacity, &z_index, &name)) { return -1; } // Parse center position sf::Vector2f center(0.0f, 0.0f); if (center_obj) { PyVectorObject* vec = PyVector::from_arg(center_obj); if (vec) { center = vec->data; } else { PyErr_Clear(); PyErr_SetString(PyExc_TypeError, "center must be a Vector or tuple (x, y)"); return -1; } } // Parse color sf::Color color = sf::Color::White; if (color_obj) { auto pycolor = PyColor::from_arg(color_obj); if (pycolor) { color = pycolor->data; } else { PyErr_Clear(); PyErr_SetString(PyExc_TypeError, "color must be a Color or tuple (r, g, b) or (r, g, b, a)"); return -1; } } // Set values self->data->setCenter(center); self->data->setRadius(radius); self->data->setStartAngle(start_angle); self->data->setEndAngle(end_angle); self->data->setColor(color); self->data->setThickness(thickness); // Handle common UIDrawable properties 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); } self->data->visible = (visible != 0); self->data->opacity = opacity; self->data->z_index = z_index; if (name) { self->data->name = name; } return 0; }