McRogueFace/src/UIArc.cpp

530 lines
16 KiB
C++

#include "UIArc.h"
#include "McRFPy_API.h"
#include <cmath>
#include <sstream>
#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<int>(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<sf::Uint8>(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<float>(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<float>(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<float>(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<float>(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 << "<Arc (invalid internal object)>";
} else {
auto center = self->data->getCenter();
auto color = self->data->getColor();
oss << "<Arc center=(" << center.x << ", " << center.y << ") "
<< "radius=" << self->data->getRadius() << " "
<< "angles=(" << self->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<char**>(kwlist),
&center_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;
}