491 lines
16 KiB
C++
491 lines
16 KiB
C++
#include "UICircle.h"
|
|
#include "GameEngine.h"
|
|
#include "McRFPy_API.h"
|
|
#include "PyVector.h"
|
|
#include "PyColor.h"
|
|
#include "PythonObjectCache.h"
|
|
#include <cmath>
|
|
|
|
UICircle::UICircle()
|
|
: radius(10.0f),
|
|
fill_color(sf::Color::White),
|
|
outline_color(sf::Color::Transparent),
|
|
outline_thickness(0.0f)
|
|
{
|
|
position = sf::Vector2f(0.0f, 0.0f);
|
|
shape.setRadius(radius);
|
|
shape.setFillColor(fill_color);
|
|
shape.setOutlineColor(outline_color);
|
|
shape.setOutlineThickness(outline_thickness);
|
|
shape.setOrigin(radius, radius); // Center the origin
|
|
}
|
|
|
|
UICircle::UICircle(float radius, sf::Vector2f center, sf::Color fillColor,
|
|
sf::Color outlineColor, float outlineThickness)
|
|
: radius(radius),
|
|
fill_color(fillColor),
|
|
outline_color(outlineColor),
|
|
outline_thickness(outlineThickness)
|
|
{
|
|
position = center;
|
|
shape.setRadius(radius);
|
|
shape.setFillColor(fill_color);
|
|
shape.setOutlineColor(outline_color);
|
|
shape.setOutlineThickness(outline_thickness);
|
|
shape.setOrigin(radius, radius); // Center the origin
|
|
}
|
|
|
|
UICircle::UICircle(const UICircle& other)
|
|
: UIDrawable(other),
|
|
radius(other.radius),
|
|
fill_color(other.fill_color),
|
|
outline_color(other.outline_color),
|
|
outline_thickness(other.outline_thickness)
|
|
{
|
|
shape.setRadius(radius);
|
|
shape.setFillColor(fill_color);
|
|
shape.setOutlineColor(outline_color);
|
|
shape.setOutlineThickness(outline_thickness);
|
|
shape.setOrigin(radius, radius);
|
|
}
|
|
|
|
UICircle& UICircle::operator=(const UICircle& other) {
|
|
if (this != &other) {
|
|
UIDrawable::operator=(other);
|
|
radius = other.radius;
|
|
fill_color = other.fill_color;
|
|
outline_color = other.outline_color;
|
|
outline_thickness = other.outline_thickness;
|
|
shape.setRadius(radius);
|
|
shape.setFillColor(fill_color);
|
|
shape.setOutlineColor(outline_color);
|
|
shape.setOutlineThickness(outline_thickness);
|
|
shape.setOrigin(radius, radius);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
UICircle::UICircle(UICircle&& other) noexcept
|
|
: UIDrawable(std::move(other)),
|
|
shape(std::move(other.shape)),
|
|
radius(other.radius),
|
|
fill_color(other.fill_color),
|
|
outline_color(other.outline_color),
|
|
outline_thickness(other.outline_thickness)
|
|
{
|
|
}
|
|
|
|
UICircle& UICircle::operator=(UICircle&& other) noexcept {
|
|
if (this != &other) {
|
|
UIDrawable::operator=(std::move(other));
|
|
shape = std::move(other.shape);
|
|
radius = other.radius;
|
|
fill_color = other.fill_color;
|
|
outline_color = other.outline_color;
|
|
outline_thickness = other.outline_thickness;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
void UICircle::setRadius(float r) {
|
|
radius = r;
|
|
shape.setRadius(r);
|
|
shape.setOrigin(r, r); // Keep origin at center
|
|
}
|
|
|
|
void UICircle::setFillColor(sf::Color c) {
|
|
fill_color = c;
|
|
shape.setFillColor(c);
|
|
}
|
|
|
|
void UICircle::setOutlineColor(sf::Color c) {
|
|
outline_color = c;
|
|
shape.setOutlineColor(c);
|
|
}
|
|
|
|
void UICircle::setOutline(float t) {
|
|
outline_thickness = t;
|
|
shape.setOutlineThickness(t);
|
|
}
|
|
|
|
void UICircle::render(sf::Vector2f offset, sf::RenderTarget& target) {
|
|
if (!visible) return;
|
|
|
|
// Apply position and offset
|
|
shape.setPosition(position + offset);
|
|
|
|
// Apply opacity to colors
|
|
sf::Color render_fill = fill_color;
|
|
render_fill.a = static_cast<sf::Uint8>(fill_color.a * opacity);
|
|
shape.setFillColor(render_fill);
|
|
|
|
sf::Color render_outline = outline_color;
|
|
render_outline.a = static_cast<sf::Uint8>(outline_color.a * opacity);
|
|
shape.setOutlineColor(render_outline);
|
|
|
|
target.draw(shape);
|
|
}
|
|
|
|
UIDrawable* UICircle::click_at(sf::Vector2f point) {
|
|
if (!click_callable) return nullptr;
|
|
|
|
// Check if point is within the circle (including outline)
|
|
float dx = point.x - position.x;
|
|
float dy = point.y - position.y;
|
|
float distance = std::sqrt(dx * dx + dy * dy);
|
|
|
|
float effective_radius = radius + outline_thickness;
|
|
if (distance <= effective_radius) {
|
|
return this;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
PyObjectsEnum UICircle::derived_type() {
|
|
return PyObjectsEnum::UICIRCLE;
|
|
}
|
|
|
|
sf::FloatRect UICircle::get_bounds() const {
|
|
float effective_radius = radius + outline_thickness;
|
|
return sf::FloatRect(
|
|
position.x - effective_radius,
|
|
position.y - effective_radius,
|
|
effective_radius * 2,
|
|
effective_radius * 2
|
|
);
|
|
}
|
|
|
|
void UICircle::move(float dx, float dy) {
|
|
position.x += dx;
|
|
position.y += dy;
|
|
}
|
|
|
|
void UICircle::resize(float w, float h) {
|
|
// For circles, use the average of w and h as diameter
|
|
radius = (w + h) / 4.0f; // Average of w and h, then divide by 2 for radius
|
|
shape.setRadius(radius);
|
|
shape.setOrigin(radius, radius);
|
|
}
|
|
|
|
// Property system for animations
|
|
bool UICircle::setProperty(const std::string& name, float value) {
|
|
if (name == "radius") {
|
|
setRadius(value);
|
|
return true;
|
|
} else if (name == "outline") {
|
|
setOutline(value);
|
|
return true;
|
|
} else if (name == "x") {
|
|
position.x = value;
|
|
return true;
|
|
} else if (name == "y") {
|
|
position.y = value;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool UICircle::setProperty(const std::string& name, const sf::Color& value) {
|
|
if (name == "fill_color") {
|
|
setFillColor(value);
|
|
return true;
|
|
} else if (name == "outline_color") {
|
|
setOutlineColor(value);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool UICircle::setProperty(const std::string& name, const sf::Vector2f& value) {
|
|
if (name == "center" || name == "position") {
|
|
position = value;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool UICircle::getProperty(const std::string& name, float& value) const {
|
|
if (name == "radius") {
|
|
value = radius;
|
|
return true;
|
|
} else if (name == "outline") {
|
|
value = outline_thickness;
|
|
return true;
|
|
} else if (name == "x") {
|
|
value = position.x;
|
|
return true;
|
|
} else if (name == "y") {
|
|
value = position.y;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool UICircle::getProperty(const std::string& name, sf::Color& value) const {
|
|
if (name == "fill_color") {
|
|
value = fill_color;
|
|
return true;
|
|
} else if (name == "outline_color") {
|
|
value = outline_color;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool UICircle::getProperty(const std::string& name, sf::Vector2f& value) const {
|
|
if (name == "center" || name == "position") {
|
|
value = position;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Python API implementations
|
|
PyObject* UICircle::get_radius(PyUICircleObject* self, void* closure) {
|
|
return PyFloat_FromDouble(self->data->getRadius());
|
|
}
|
|
|
|
int UICircle::set_radius(PyUICircleObject* 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* UICircle::get_center(PyUICircleObject* self, void* closure) {
|
|
sf::Vector2f 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 UICircle::set_center(PyUICircleObject* 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* UICircle::get_fill_color(PyUICircleObject* self, void* closure) {
|
|
sf::Color c = self->data->getFillColor();
|
|
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color");
|
|
if (!type) return NULL;
|
|
PyObject* result = PyObject_CallFunction((PyObject*)type, "iiii", c.r, c.g, c.b, c.a);
|
|
Py_DECREF(type);
|
|
return result;
|
|
}
|
|
|
|
int UICircle::set_fill_color(PyUICircleObject* self, PyObject* value, void* closure) {
|
|
sf::Color color;
|
|
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color"))) {
|
|
auto pyColor = (PyColorObject*)value;
|
|
color = pyColor->data;
|
|
} else if (PyTuple_Check(value)) {
|
|
int r, g, b, a = 255;
|
|
if (!PyArg_ParseTuple(value, "iii|i", &r, &g, &b, &a)) {
|
|
PyErr_SetString(PyExc_TypeError, "color tuple must be (r, g, b) or (r, g, b, a)");
|
|
return -1;
|
|
}
|
|
color = sf::Color(r, g, b, a);
|
|
} else {
|
|
PyErr_SetString(PyExc_TypeError, "fill_color must be a Color or tuple");
|
|
return -1;
|
|
}
|
|
self->data->setFillColor(color);
|
|
return 0;
|
|
}
|
|
|
|
PyObject* UICircle::get_outline_color(PyUICircleObject* self, void* closure) {
|
|
sf::Color c = self->data->getOutlineColor();
|
|
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color");
|
|
if (!type) return NULL;
|
|
PyObject* result = PyObject_CallFunction((PyObject*)type, "iiii", c.r, c.g, c.b, c.a);
|
|
Py_DECREF(type);
|
|
return result;
|
|
}
|
|
|
|
int UICircle::set_outline_color(PyUICircleObject* self, PyObject* value, void* closure) {
|
|
sf::Color color;
|
|
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color"))) {
|
|
auto pyColor = (PyColorObject*)value;
|
|
color = pyColor->data;
|
|
} else if (PyTuple_Check(value)) {
|
|
int r, g, b, a = 255;
|
|
if (!PyArg_ParseTuple(value, "iii|i", &r, &g, &b, &a)) {
|
|
PyErr_SetString(PyExc_TypeError, "color tuple must be (r, g, b) or (r, g, b, a)");
|
|
return -1;
|
|
}
|
|
color = sf::Color(r, g, b, a);
|
|
} else {
|
|
PyErr_SetString(PyExc_TypeError, "outline_color must be a Color or tuple");
|
|
return -1;
|
|
}
|
|
self->data->setOutlineColor(color);
|
|
return 0;
|
|
}
|
|
|
|
PyObject* UICircle::get_outline(PyUICircleObject* self, void* closure) {
|
|
return PyFloat_FromDouble(self->data->getOutline());
|
|
}
|
|
|
|
int UICircle::set_outline(PyUICircleObject* self, PyObject* value, void* closure) {
|
|
if (!PyNumber_Check(value)) {
|
|
PyErr_SetString(PyExc_TypeError, "outline must be a number");
|
|
return -1;
|
|
}
|
|
self->data->setOutline(static_cast<float>(PyFloat_AsDouble(value)));
|
|
return 0;
|
|
}
|
|
|
|
// Required typedef for UIDRAWABLE_GETSETTERS and UIDRAWABLE_METHODS macro templates
|
|
typedef PyUICircleObject PyObjectType;
|
|
|
|
PyGetSetDef UICircle::getsetters[] = {
|
|
{"radius", (getter)UICircle::get_radius, (setter)UICircle::set_radius,
|
|
"Circle radius in pixels", NULL},
|
|
{"center", (getter)UICircle::get_center, (setter)UICircle::set_center,
|
|
"Center position of the circle", NULL},
|
|
{"fill_color", (getter)UICircle::get_fill_color, (setter)UICircle::set_fill_color,
|
|
"Fill color of the circle", NULL},
|
|
{"outline_color", (getter)UICircle::get_outline_color, (setter)UICircle::set_outline_color,
|
|
"Outline color of the circle", NULL},
|
|
{"outline", (getter)UICircle::get_outline, (setter)UICircle::set_outline,
|
|
"Outline thickness (0 for no outline)", NULL},
|
|
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
|
"Callable executed when circle is clicked.", (void*)PyObjectsEnum::UICIRCLE},
|
|
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int,
|
|
"Z-order for rendering (lower values rendered first).", (void*)PyObjectsEnum::UICIRCLE},
|
|
{"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name,
|
|
"Name for finding this element.", (void*)PyObjectsEnum::UICIRCLE},
|
|
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos,
|
|
"Position as a Vector (same as center).", (void*)PyObjectsEnum::UICIRCLE},
|
|
UIDRAWABLE_GETSETTERS,
|
|
{NULL}
|
|
};
|
|
|
|
PyMethodDef UICircle_methods[] = {
|
|
UIDRAWABLE_METHODS,
|
|
{NULL}
|
|
};
|
|
|
|
PyObject* UICircle::repr(PyUICircleObject* self) {
|
|
std::ostringstream oss;
|
|
auto& circle = self->data;
|
|
sf::Vector2f center = circle->getCenter();
|
|
sf::Color fc = circle->getFillColor();
|
|
oss << "<Circle center=(" << center.x << ", " << center.y << ") "
|
|
<< "radius=" << circle->getRadius() << " "
|
|
<< "fill_color=(" << (int)fc.r << ", " << (int)fc.g << ", "
|
|
<< (int)fc.b << ", " << (int)fc.a << ")>";
|
|
return PyUnicode_FromString(oss.str().c_str());
|
|
}
|
|
|
|
int UICircle::init(PyUICircleObject* self, PyObject* args, PyObject* kwds) {
|
|
static const char* kwlist[] = {
|
|
"radius", "center", "fill_color", "outline_color", "outline",
|
|
"click", "visible", "opacity", "z_index", "name", NULL
|
|
};
|
|
|
|
float radius = 10.0f;
|
|
PyObject* center_obj = NULL;
|
|
PyObject* fill_color_obj = NULL;
|
|
PyObject* outline_color_obj = NULL;
|
|
float outline = 0.0f;
|
|
|
|
// Common UIDrawable kwargs
|
|
PyObject* click_obj = NULL;
|
|
int visible = 1;
|
|
float opacity_val = 1.0f;
|
|
int z_index = 0;
|
|
const char* name = NULL;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|fOOOfOpfis", (char**)kwlist,
|
|
&radius, ¢er_obj, &fill_color_obj, &outline_color_obj, &outline,
|
|
&click_obj, &visible, &opacity_val, &z_index, &name)) {
|
|
return -1;
|
|
}
|
|
|
|
// Set radius
|
|
self->data->setRadius(radius);
|
|
|
|
// Set center if provided
|
|
if (center_obj && center_obj != Py_None) {
|
|
PyVectorObject* vec = PyVector::from_arg(center_obj);
|
|
if (!vec) {
|
|
PyErr_Clear();
|
|
PyErr_SetString(PyExc_TypeError, "center must be a Vector or tuple (x, y)");
|
|
return -1;
|
|
}
|
|
self->data->setCenter(vec->data);
|
|
}
|
|
|
|
// Set fill color if provided
|
|
if (fill_color_obj && fill_color_obj != Py_None) {
|
|
sf::Color color;
|
|
if (PyObject_IsInstance(fill_color_obj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color"))) {
|
|
color = ((PyColorObject*)fill_color_obj)->data;
|
|
} else if (PyTuple_Check(fill_color_obj)) {
|
|
int r, g, b, a = 255;
|
|
if (!PyArg_ParseTuple(fill_color_obj, "iii|i", &r, &g, &b, &a)) {
|
|
PyErr_SetString(PyExc_TypeError, "fill_color tuple must be (r, g, b) or (r, g, b, a)");
|
|
return -1;
|
|
}
|
|
color = sf::Color(r, g, b, a);
|
|
} else {
|
|
PyErr_SetString(PyExc_TypeError, "fill_color must be a Color or tuple");
|
|
return -1;
|
|
}
|
|
self->data->setFillColor(color);
|
|
}
|
|
|
|
// Set outline color if provided
|
|
if (outline_color_obj && outline_color_obj != Py_None) {
|
|
sf::Color color;
|
|
if (PyObject_IsInstance(outline_color_obj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color"))) {
|
|
color = ((PyColorObject*)outline_color_obj)->data;
|
|
} else if (PyTuple_Check(outline_color_obj)) {
|
|
int r, g, b, a = 255;
|
|
if (!PyArg_ParseTuple(outline_color_obj, "iii|i", &r, &g, &b, &a)) {
|
|
PyErr_SetString(PyExc_TypeError, "outline_color tuple must be (r, g, b) or (r, g, b, a)");
|
|
return -1;
|
|
}
|
|
color = sf::Color(r, g, b, a);
|
|
} else {
|
|
PyErr_SetString(PyExc_TypeError, "outline_color must be a Color or tuple");
|
|
return -1;
|
|
}
|
|
self->data->setOutlineColor(color);
|
|
}
|
|
|
|
// Set outline thickness
|
|
self->data->setOutline(outline);
|
|
|
|
// Handle common UIDrawable properties
|
|
if (click_obj && click_obj != Py_None) {
|
|
if (!PyCallable_Check(click_obj)) {
|
|
PyErr_SetString(PyExc_TypeError, "click must be callable");
|
|
return -1;
|
|
}
|
|
self->data->click_register(click_obj);
|
|
}
|
|
|
|
self->data->visible = (visible != 0);
|
|
self->data->opacity = opacity_val;
|
|
self->data->z_index = z_index;
|
|
|
|
if (name) {
|
|
self->data->name = name;
|
|
}
|
|
|
|
return 0;
|
|
}
|