feat: Add UILine, UICircle, and UIArc drawing primitives
Implement new UIDrawable-derived classes for vector graphics: - UILine: Thick line segments using sf::ConvexShape for proper thickness - Properties: start, end, color, thickness - Supports click detection along the line - UICircle: Filled and outlined circles using sf::CircleShape - Properties: radius, center, fill_color, outline_color, outline - Full property system for animations - UIArc: Arc segments for orbital paths and partial circles - Properties: center, radius, start_angle, end_angle, color, thickness - Uses sf::VertexArray with TriangleStrip for smooth rendering - Supports arbitrary angle spans including negative (reverse) arcs All primitives integrate with the Python API through mcrfpy module: - Added to PyObjectsEnum for type identification - Full getter/setter support for all properties - Added to UICollection for scene management - Support for visibility, opacity, z_index, name, and click handling closes #128, closes #129 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
acef21593b
commit
311dc02f1d
|
|
@ -10,6 +10,9 @@
|
|||
#include "PySceneObject.h"
|
||||
#include "GameEngine.h"
|
||||
#include "UI.h"
|
||||
#include "UILine.h"
|
||||
#include "UICircle.h"
|
||||
#include "UIArc.h"
|
||||
#include "Resources.h"
|
||||
#include "PyScene.h"
|
||||
#include <filesystem>
|
||||
|
|
@ -251,6 +254,7 @@ PyObject* PyInit_mcrfpy()
|
|||
|
||||
/*UI widgets*/
|
||||
&PyUICaptionType, &PyUISpriteType, &PyUIFrameType, &PyUIEntityType, &PyUIGridType,
|
||||
&PyUILineType, &PyUICircleType, &PyUIArcType,
|
||||
|
||||
/*game map & perspective data*/
|
||||
&PyUIGridPointType, &PyUIGridPointStateType,
|
||||
|
|
@ -258,19 +262,19 @@ PyObject* PyInit_mcrfpy()
|
|||
/*collections & iterators*/
|
||||
&PyUICollectionType, &PyUICollectionIterType,
|
||||
&PyUIEntityCollectionType, &PyUIEntityCollectionIterType,
|
||||
|
||||
|
||||
/*animation*/
|
||||
&PyAnimationType,
|
||||
|
||||
|
||||
/*timer*/
|
||||
&PyTimerType,
|
||||
|
||||
|
||||
/*window singleton*/
|
||||
&PyWindowType,
|
||||
|
||||
|
||||
/*scene class*/
|
||||
&PySceneType,
|
||||
|
||||
|
||||
nullptr};
|
||||
|
||||
// Set up PyWindowType methods and getsetters before PyType_Ready
|
||||
|
|
@ -288,6 +292,9 @@ PyObject* PyInit_mcrfpy()
|
|||
PyUISpriteType.tp_weaklistoffset = offsetof(PyUISpriteObject, weakreflist);
|
||||
PyUIGridType.tp_weaklistoffset = offsetof(PyUIGridObject, weakreflist);
|
||||
PyUIEntityType.tp_weaklistoffset = offsetof(PyUIEntityObject, weakreflist);
|
||||
PyUILineType.tp_weaklistoffset = offsetof(PyUILineObject, weakreflist);
|
||||
PyUICircleType.tp_weaklistoffset = offsetof(PyUICircleObject, weakreflist);
|
||||
PyUIArcType.tp_weaklistoffset = offsetof(PyUIArcObject, weakreflist);
|
||||
|
||||
int i = 0;
|
||||
auto t = pytypes[i];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,520 @@
|
|||
#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);
|
||||
return true;
|
||||
}
|
||||
else if (name == "start_angle") {
|
||||
setStartAngle(value);
|
||||
return true;
|
||||
}
|
||||
else if (name == "end_angle") {
|
||||
setEndAngle(value);
|
||||
return true;
|
||||
}
|
||||
else if (name == "thickness") {
|
||||
setThickness(value);
|
||||
return true;
|
||||
}
|
||||
else if (name == "x") {
|
||||
center.x = value;
|
||||
position = center;
|
||||
vertices_dirty = true;
|
||||
return true;
|
||||
}
|
||||
else if (name == "y") {
|
||||
center.y = value;
|
||||
position = center;
|
||||
vertices_dirty = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UIArc::setProperty(const std::string& name, const sf::Color& value) {
|
||||
if (name == "color") {
|
||||
setColor(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UIArc::setProperty(const std::string& name, const sf::Vector2f& value) {
|
||||
if (name == "center") {
|
||||
setCenter(value);
|
||||
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},
|
||||
{"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,
|
||||
{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),
|
||||
¢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;
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "Python.h"
|
||||
#include "structmember.h"
|
||||
#include "UIDrawable.h"
|
||||
#include "UIBase.h"
|
||||
#include "PyDrawable.h"
|
||||
#include "PyColor.h"
|
||||
#include "PyVector.h"
|
||||
#include "McRFPy_Doc.h"
|
||||
|
||||
// Forward declaration
|
||||
class UIArc;
|
||||
|
||||
// Python object structure
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
std::shared_ptr<UIArc> data;
|
||||
PyObject* weakreflist;
|
||||
} PyUIArcObject;
|
||||
|
||||
class UIArc : public UIDrawable
|
||||
{
|
||||
private:
|
||||
sf::Vector2f center;
|
||||
float radius;
|
||||
float start_angle; // in degrees
|
||||
float end_angle; // in degrees
|
||||
sf::Color color;
|
||||
float thickness;
|
||||
|
||||
// Cached vertex array for rendering
|
||||
sf::VertexArray vertices;
|
||||
bool vertices_dirty;
|
||||
|
||||
void rebuildVertices();
|
||||
|
||||
public:
|
||||
UIArc();
|
||||
UIArc(sf::Vector2f center, float radius, float startAngle, float endAngle,
|
||||
sf::Color color = sf::Color::White, float thickness = 1.0f);
|
||||
|
||||
// Copy constructor and assignment
|
||||
UIArc(const UIArc& other);
|
||||
UIArc& operator=(const UIArc& other);
|
||||
|
||||
// Move constructor and assignment
|
||||
UIArc(UIArc&& other) noexcept;
|
||||
UIArc& operator=(UIArc&& other) noexcept;
|
||||
|
||||
// UIDrawable interface
|
||||
void render(sf::Vector2f offset, sf::RenderTarget& target) override;
|
||||
UIDrawable* click_at(sf::Vector2f point) override;
|
||||
PyObjectsEnum derived_type() override;
|
||||
|
||||
// Getters and setters
|
||||
sf::Vector2f getCenter() const { return center; }
|
||||
void setCenter(sf::Vector2f c) { center = c; position = c; vertices_dirty = true; }
|
||||
|
||||
float getRadius() const { return radius; }
|
||||
void setRadius(float r) { radius = r; vertices_dirty = true; }
|
||||
|
||||
float getStartAngle() const { return start_angle; }
|
||||
void setStartAngle(float a) { start_angle = a; vertices_dirty = true; }
|
||||
|
||||
float getEndAngle() const { return end_angle; }
|
||||
void setEndAngle(float a) { end_angle = a; vertices_dirty = true; }
|
||||
|
||||
sf::Color getColor() const { return color; }
|
||||
void setColor(sf::Color c) { color = c; vertices_dirty = true; }
|
||||
|
||||
float getThickness() const { return thickness; }
|
||||
void setThickness(float t) { thickness = t; vertices_dirty = true; }
|
||||
|
||||
// Phase 1 virtual method implementations
|
||||
sf::FloatRect get_bounds() const override;
|
||||
void move(float dx, float dy) override;
|
||||
void resize(float w, float h) override;
|
||||
|
||||
// Property system for animations
|
||||
bool setProperty(const std::string& name, float value) override;
|
||||
bool setProperty(const std::string& name, const sf::Color& value) override;
|
||||
bool setProperty(const std::string& name, const sf::Vector2f& value) override;
|
||||
bool getProperty(const std::string& name, float& value) const override;
|
||||
bool getProperty(const std::string& name, sf::Color& value) const override;
|
||||
bool getProperty(const std::string& name, sf::Vector2f& value) const override;
|
||||
|
||||
// Python API
|
||||
static PyObject* get_center(PyUIArcObject* self, void* closure);
|
||||
static int set_center(PyUIArcObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_radius(PyUIArcObject* self, void* closure);
|
||||
static int set_radius(PyUIArcObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_start_angle(PyUIArcObject* self, void* closure);
|
||||
static int set_start_angle(PyUIArcObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_end_angle(PyUIArcObject* self, void* closure);
|
||||
static int set_end_angle(PyUIArcObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_color(PyUIArcObject* self, void* closure);
|
||||
static int set_color(PyUIArcObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_thickness(PyUIArcObject* self, void* closure);
|
||||
static int set_thickness(PyUIArcObject* self, PyObject* value, void* closure);
|
||||
|
||||
static PyGetSetDef getsetters[];
|
||||
static PyObject* repr(PyUIArcObject* self);
|
||||
static int init(PyUIArcObject* self, PyObject* args, PyObject* kwds);
|
||||
};
|
||||
|
||||
// Method definitions
|
||||
extern PyMethodDef UIArc_methods[];
|
||||
|
||||
namespace mcrfpydef {
|
||||
static PyTypeObject PyUIArcType = {
|
||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||
.tp_name = "mcrfpy.Arc",
|
||||
.tp_basicsize = sizeof(PyUIArcObject),
|
||||
.tp_itemsize = 0,
|
||||
.tp_dealloc = (destructor)[](PyObject* self) {
|
||||
PyUIArcObject* obj = (PyUIArcObject*)self;
|
||||
if (obj->weakreflist != NULL) {
|
||||
PyObject_ClearWeakRefs(self);
|
||||
}
|
||||
obj->data.reset();
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
},
|
||||
.tp_repr = (reprfunc)UIArc::repr,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
||||
.tp_doc = PyDoc_STR(
|
||||
"Arc(center=None, radius=0, start_angle=0, end_angle=90, color=None, thickness=1, **kwargs)\n\n"
|
||||
"An arc UI element for drawing curved line segments.\n\n"
|
||||
"Args:\n"
|
||||
" center (tuple, optional): Center position as (x, y). Default: (0, 0)\n"
|
||||
" radius (float, optional): Arc radius in pixels. Default: 0\n"
|
||||
" start_angle (float, optional): Starting angle in degrees. Default: 0\n"
|
||||
" end_angle (float, optional): Ending angle in degrees. Default: 90\n"
|
||||
" color (Color, optional): Arc color. Default: White\n"
|
||||
" thickness (float, optional): Line thickness. Default: 1.0\n\n"
|
||||
"Keyword Args:\n"
|
||||
" click (callable): Click handler. Default: None\n"
|
||||
" visible (bool): Visibility state. Default: True\n"
|
||||
" opacity (float): Opacity (0.0-1.0). Default: 1.0\n"
|
||||
" z_index (int): Rendering order. Default: 0\n"
|
||||
" name (str): Element name for finding. Default: None\n\n"
|
||||
"Attributes:\n"
|
||||
" center (Vector): Center position\n"
|
||||
" radius (float): Arc radius\n"
|
||||
" start_angle (float): Starting angle in degrees\n"
|
||||
" end_angle (float): Ending angle in degrees\n"
|
||||
" color (Color): Arc color\n"
|
||||
" thickness (float): Line thickness\n"
|
||||
" visible (bool): Visibility state\n"
|
||||
" opacity (float): Opacity value\n"
|
||||
" z_index (int): Rendering order\n"
|
||||
" name (str): Element name\n"
|
||||
),
|
||||
.tp_methods = UIArc_methods,
|
||||
.tp_getset = UIArc::getsetters,
|
||||
.tp_base = &mcrfpydef::PyDrawableType,
|
||||
.tp_init = (initproc)UIArc::init,
|
||||
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* {
|
||||
PyUIArcObject* self = (PyUIArcObject*)type->tp_alloc(type, 0);
|
||||
if (self) {
|
||||
self->data = std::make_shared<UIArc>();
|
||||
self->weakreflist = nullptr;
|
||||
}
|
||||
return (PyObject*)self;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,490 @@
|
|||
#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;
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "Python.h"
|
||||
#include "structmember.h"
|
||||
#include "UIDrawable.h"
|
||||
#include "UIBase.h"
|
||||
#include "PyDrawable.h"
|
||||
#include "PyColor.h"
|
||||
#include "PyVector.h"
|
||||
#include "McRFPy_Doc.h"
|
||||
|
||||
// Forward declaration
|
||||
class UICircle;
|
||||
|
||||
// Python object structure
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
std::shared_ptr<UICircle> data;
|
||||
PyObject* weakreflist;
|
||||
} PyUICircleObject;
|
||||
|
||||
class UICircle : public UIDrawable
|
||||
{
|
||||
private:
|
||||
sf::CircleShape shape;
|
||||
float radius;
|
||||
sf::Color fill_color;
|
||||
sf::Color outline_color;
|
||||
float outline_thickness;
|
||||
|
||||
public:
|
||||
UICircle();
|
||||
UICircle(float radius, sf::Vector2f center = sf::Vector2f(0, 0),
|
||||
sf::Color fillColor = sf::Color::White,
|
||||
sf::Color outlineColor = sf::Color::Transparent,
|
||||
float outlineThickness = 0.0f);
|
||||
|
||||
// Copy constructor and assignment
|
||||
UICircle(const UICircle& other);
|
||||
UICircle& operator=(const UICircle& other);
|
||||
|
||||
// Move constructor and assignment
|
||||
UICircle(UICircle&& other) noexcept;
|
||||
UICircle& operator=(UICircle&& other) noexcept;
|
||||
|
||||
// UIDrawable interface
|
||||
void render(sf::Vector2f offset, sf::RenderTarget& target) override;
|
||||
UIDrawable* click_at(sf::Vector2f point) override;
|
||||
PyObjectsEnum derived_type() override;
|
||||
|
||||
// Getters and setters
|
||||
float getRadius() const { return radius; }
|
||||
void setRadius(float r);
|
||||
|
||||
sf::Vector2f getCenter() const { return position; }
|
||||
void setCenter(sf::Vector2f c) { position = c; }
|
||||
|
||||
sf::Color getFillColor() const { return fill_color; }
|
||||
void setFillColor(sf::Color c);
|
||||
|
||||
sf::Color getOutlineColor() const { return outline_color; }
|
||||
void setOutlineColor(sf::Color c);
|
||||
|
||||
float getOutline() const { return outline_thickness; }
|
||||
void setOutline(float t);
|
||||
|
||||
// Phase 1 virtual method implementations
|
||||
sf::FloatRect get_bounds() const override;
|
||||
void move(float dx, float dy) override;
|
||||
void resize(float w, float h) override;
|
||||
|
||||
// Property system for animations
|
||||
bool setProperty(const std::string& name, float value) override;
|
||||
bool setProperty(const std::string& name, const sf::Color& value) override;
|
||||
bool setProperty(const std::string& name, const sf::Vector2f& value) override;
|
||||
bool getProperty(const std::string& name, float& value) const override;
|
||||
bool getProperty(const std::string& name, sf::Color& value) const override;
|
||||
bool getProperty(const std::string& name, sf::Vector2f& value) const override;
|
||||
|
||||
// Python API
|
||||
static PyObject* get_radius(PyUICircleObject* self, void* closure);
|
||||
static int set_radius(PyUICircleObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_center(PyUICircleObject* self, void* closure);
|
||||
static int set_center(PyUICircleObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_fill_color(PyUICircleObject* self, void* closure);
|
||||
static int set_fill_color(PyUICircleObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_outline_color(PyUICircleObject* self, void* closure);
|
||||
static int set_outline_color(PyUICircleObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_outline(PyUICircleObject* self, void* closure);
|
||||
static int set_outline(PyUICircleObject* self, PyObject* value, void* closure);
|
||||
|
||||
static PyGetSetDef getsetters[];
|
||||
static PyObject* repr(PyUICircleObject* self);
|
||||
static int init(PyUICircleObject* self, PyObject* args, PyObject* kwds);
|
||||
};
|
||||
|
||||
// Method definitions
|
||||
extern PyMethodDef UICircle_methods[];
|
||||
|
||||
namespace mcrfpydef {
|
||||
static PyTypeObject PyUICircleType = {
|
||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||
.tp_name = "mcrfpy.Circle",
|
||||
.tp_basicsize = sizeof(PyUICircleObject),
|
||||
.tp_itemsize = 0,
|
||||
.tp_dealloc = (destructor)[](PyObject* self) {
|
||||
PyUICircleObject* obj = (PyUICircleObject*)self;
|
||||
if (obj->weakreflist != NULL) {
|
||||
PyObject_ClearWeakRefs(self);
|
||||
}
|
||||
obj->data.reset();
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
},
|
||||
.tp_repr = (reprfunc)UICircle::repr,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
||||
.tp_doc = PyDoc_STR(
|
||||
"Circle(radius=0, center=None, fill_color=None, outline_color=None, outline=0, **kwargs)\n\n"
|
||||
"A circle UI element for drawing filled or outlined circles.\n\n"
|
||||
"Args:\n"
|
||||
" radius (float, optional): Circle radius in pixels. Default: 0\n"
|
||||
" center (tuple, optional): Center position as (x, y). Default: (0, 0)\n"
|
||||
" fill_color (Color, optional): Fill color. Default: White\n"
|
||||
" outline_color (Color, optional): Outline color. Default: Transparent\n"
|
||||
" outline (float, optional): Outline thickness. Default: 0 (no outline)\n\n"
|
||||
"Keyword Args:\n"
|
||||
" click (callable): Click handler. Default: None\n"
|
||||
" visible (bool): Visibility state. Default: True\n"
|
||||
" opacity (float): Opacity (0.0-1.0). Default: 1.0\n"
|
||||
" z_index (int): Rendering order. Default: 0\n"
|
||||
" name (str): Element name for finding. Default: None\n\n"
|
||||
"Attributes:\n"
|
||||
" radius (float): Circle radius\n"
|
||||
" center (Vector): Center position\n"
|
||||
" fill_color (Color): Fill color\n"
|
||||
" outline_color (Color): Outline color\n"
|
||||
" outline (float): Outline thickness\n"
|
||||
" visible (bool): Visibility state\n"
|
||||
" opacity (float): Opacity value\n"
|
||||
" z_index (int): Rendering order\n"
|
||||
" name (str): Element name\n"
|
||||
),
|
||||
.tp_methods = UICircle_methods,
|
||||
.tp_getset = UICircle::getsetters,
|
||||
.tp_base = &mcrfpydef::PyDrawableType,
|
||||
.tp_init = (initproc)UICircle::init,
|
||||
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* {
|
||||
PyUICircleObject* self = (PyUICircleObject*)type->tp_alloc(type, 0);
|
||||
if (self) {
|
||||
self->data = std::make_shared<UICircle>();
|
||||
self->weakreflist = nullptr;
|
||||
}
|
||||
return (PyObject*)self;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -4,6 +4,9 @@
|
|||
#include "UICaption.h"
|
||||
#include "UISprite.h"
|
||||
#include "UIGrid.h"
|
||||
#include "UILine.h"
|
||||
#include "UICircle.h"
|
||||
#include "UIArc.h"
|
||||
#include "McRFPy_API.h"
|
||||
#include "PyObjectUtils.h"
|
||||
#include "PythonObjectCache.h"
|
||||
|
|
@ -79,6 +82,42 @@ static PyObject* convertDrawableToPython(std::shared_ptr<UIDrawable> drawable) {
|
|||
obj = (PyObject*)pyObj;
|
||||
break;
|
||||
}
|
||||
case PyObjectsEnum::UILINE:
|
||||
{
|
||||
type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Line");
|
||||
if (!type) return nullptr;
|
||||
auto pyObj = (PyUILineObject*)type->tp_alloc(type, 0);
|
||||
if (pyObj) {
|
||||
pyObj->data = std::static_pointer_cast<UILine>(drawable);
|
||||
pyObj->weakreflist = NULL;
|
||||
}
|
||||
obj = (PyObject*)pyObj;
|
||||
break;
|
||||
}
|
||||
case PyObjectsEnum::UICIRCLE:
|
||||
{
|
||||
type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Circle");
|
||||
if (!type) return nullptr;
|
||||
auto pyObj = (PyUICircleObject*)type->tp_alloc(type, 0);
|
||||
if (pyObj) {
|
||||
pyObj->data = std::static_pointer_cast<UICircle>(drawable);
|
||||
pyObj->weakreflist = NULL;
|
||||
}
|
||||
obj = (PyObject*)pyObj;
|
||||
break;
|
||||
}
|
||||
case PyObjectsEnum::UIARC:
|
||||
{
|
||||
type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Arc");
|
||||
if (!type) return nullptr;
|
||||
auto pyObj = (PyUIArcObject*)type->tp_alloc(type, 0);
|
||||
if (pyObj) {
|
||||
pyObj->data = std::static_pointer_cast<UIArc>(drawable);
|
||||
pyObj->weakreflist = NULL;
|
||||
}
|
||||
obj = (PyObject*)pyObj;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError, "Unknown UIDrawable derived type");
|
||||
return nullptr;
|
||||
|
|
@ -577,10 +616,13 @@ PyObject* UICollection::append(PyUICollectionObject* self, PyObject* o)
|
|||
if (!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
||||
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
||||
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
|
||||
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))
|
||||
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid")) &&
|
||||
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Line")) &&
|
||||
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Circle")) &&
|
||||
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Arc"))
|
||||
)
|
||||
{
|
||||
PyErr_SetString(PyExc_TypeError, "Only Frame, Caption, Sprite, and Grid objects can be added to UICollection");
|
||||
PyErr_SetString(PyExc_TypeError, "Only Frame, Caption, Sprite, Grid, Line, Circle, and Arc objects can be added to UICollection");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -620,7 +662,25 @@ PyObject* UICollection::append(PyUICollectionObject* self, PyObject* o)
|
|||
grid->data->z_index = new_z_index;
|
||||
self->data->push_back(grid->data);
|
||||
}
|
||||
|
||||
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Line")))
|
||||
{
|
||||
PyUILineObject* line = (PyUILineObject*)o;
|
||||
line->data->z_index = new_z_index;
|
||||
self->data->push_back(line->data);
|
||||
}
|
||||
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Circle")))
|
||||
{
|
||||
PyUICircleObject* circle = (PyUICircleObject*)o;
|
||||
circle->data->z_index = new_z_index;
|
||||
self->data->push_back(circle->data);
|
||||
}
|
||||
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Arc")))
|
||||
{
|
||||
PyUIArcObject* arc = (PyUIArcObject*)o;
|
||||
arc->data->z_index = new_z_index;
|
||||
self->data->push_back(arc->data);
|
||||
}
|
||||
|
||||
// Mark scene as needing resort after adding element
|
||||
McRFPy_API::markSceneNeedsSort();
|
||||
|
||||
|
|
@ -656,11 +716,14 @@ PyObject* UICollection::extend(PyUICollectionObject* self, PyObject* iterable)
|
|||
if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
||||
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
||||
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
|
||||
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid")))
|
||||
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid")) &&
|
||||
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Line")) &&
|
||||
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Circle")) &&
|
||||
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Arc")))
|
||||
{
|
||||
Py_DECREF(item);
|
||||
Py_DECREF(iterator);
|
||||
PyErr_SetString(PyExc_TypeError, "All items must be Frame, Caption, Sprite, or Grid objects");
|
||||
PyErr_SetString(PyExc_TypeError, "All items must be Frame, Caption, Sprite, Grid, Line, Circle, or Arc objects");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -692,10 +755,25 @@ PyObject* UICollection::extend(PyUICollectionObject* self, PyObject* iterable)
|
|||
grid->data->z_index = current_z_index;
|
||||
self->data->push_back(grid->data);
|
||||
}
|
||||
|
||||
else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Line"))) {
|
||||
PyUILineObject* line = (PyUILineObject*)item;
|
||||
line->data->z_index = current_z_index;
|
||||
self->data->push_back(line->data);
|
||||
}
|
||||
else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Circle"))) {
|
||||
PyUICircleObject* circle = (PyUICircleObject*)item;
|
||||
circle->data->z_index = current_z_index;
|
||||
self->data->push_back(circle->data);
|
||||
}
|
||||
else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Arc"))) {
|
||||
PyUIArcObject* arc = (PyUIArcObject*)item;
|
||||
arc->data->z_index = current_z_index;
|
||||
self->data->push_back(arc->data);
|
||||
}
|
||||
|
||||
Py_DECREF(item);
|
||||
}
|
||||
|
||||
|
||||
Py_DECREF(iterator);
|
||||
|
||||
// Check if iteration ended due to an error
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
#include "UICaption.h"
|
||||
#include "UISprite.h"
|
||||
#include "UIGrid.h"
|
||||
#include "UILine.h"
|
||||
#include "UICircle.h"
|
||||
#include "UIArc.h"
|
||||
#include "GameEngine.h"
|
||||
#include "McRFPy_API.h"
|
||||
#include "PythonObjectCache.h"
|
||||
|
|
@ -152,6 +155,24 @@ PyObject* UIDrawable::get_click(PyObject* self, void* closure) {
|
|||
else
|
||||
ptr = NULL;
|
||||
break;
|
||||
case PyObjectsEnum::UILINE:
|
||||
if (((PyUILineObject*)self)->data->click_callable)
|
||||
ptr = ((PyUILineObject*)self)->data->click_callable->borrow();
|
||||
else
|
||||
ptr = NULL;
|
||||
break;
|
||||
case PyObjectsEnum::UICIRCLE:
|
||||
if (((PyUICircleObject*)self)->data->click_callable)
|
||||
ptr = ((PyUICircleObject*)self)->data->click_callable->borrow();
|
||||
else
|
||||
ptr = NULL;
|
||||
break;
|
||||
case PyObjectsEnum::UIARC:
|
||||
if (((PyUIArcObject*)self)->data->click_callable)
|
||||
ptr = ((PyUIArcObject*)self)->data->click_callable->borrow();
|
||||
else
|
||||
ptr = NULL;
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _get_click");
|
||||
return NULL;
|
||||
|
|
@ -179,6 +200,15 @@ int UIDrawable::set_click(PyObject* self, PyObject* value, void* closure) {
|
|||
case PyObjectsEnum::UIGRID:
|
||||
target = (((PyUIGridObject*)self)->data.get());
|
||||
break;
|
||||
case PyObjectsEnum::UILINE:
|
||||
target = (((PyUILineObject*)self)->data.get());
|
||||
break;
|
||||
case PyObjectsEnum::UICIRCLE:
|
||||
target = (((PyUICircleObject*)self)->data.get());
|
||||
break;
|
||||
case PyObjectsEnum::UIARC:
|
||||
target = (((PyUIArcObject*)self)->data.get());
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _set_click");
|
||||
return -1;
|
||||
|
|
@ -215,18 +245,27 @@ PyObject* UIDrawable::get_int(PyObject* self, void* closure) {
|
|||
case PyObjectsEnum::UIGRID:
|
||||
drawable = ((PyUIGridObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UILINE:
|
||||
drawable = ((PyUILineObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UICIRCLE:
|
||||
drawable = ((PyUICircleObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UIARC:
|
||||
drawable = ((PyUIArcObject*)self)->data.get();
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
return PyLong_FromLong(drawable->z_index);
|
||||
}
|
||||
|
||||
int UIDrawable::set_int(PyObject* self, PyObject* value, void* closure) {
|
||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||
UIDrawable* drawable = nullptr;
|
||||
|
||||
|
||||
switch (objtype) {
|
||||
case PyObjectsEnum::UIFRAME:
|
||||
drawable = ((PyUIFrameObject*)self)->data.get();
|
||||
|
|
@ -240,11 +279,20 @@ int UIDrawable::set_int(PyObject* self, PyObject* value, void* closure) {
|
|||
case PyObjectsEnum::UIGRID:
|
||||
drawable = ((PyUIGridObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UILINE:
|
||||
drawable = ((PyUILineObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UICIRCLE:
|
||||
drawable = ((PyUICircleObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UIARC:
|
||||
drawable = ((PyUIArcObject*)self)->data.get();
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
if (!PyLong_Check(value)) {
|
||||
PyErr_SetString(PyExc_TypeError, "z_index must be an integer");
|
||||
return -1;
|
||||
|
|
@ -283,7 +331,7 @@ void UIDrawable::notifyZIndexChanged() {
|
|||
PyObject* UIDrawable::get_name(PyObject* self, void* closure) {
|
||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||
UIDrawable* drawable = nullptr;
|
||||
|
||||
|
||||
switch (objtype) {
|
||||
case PyObjectsEnum::UIFRAME:
|
||||
drawable = ((PyUIFrameObject*)self)->data.get();
|
||||
|
|
@ -297,18 +345,27 @@ PyObject* UIDrawable::get_name(PyObject* self, void* closure) {
|
|||
case PyObjectsEnum::UIGRID:
|
||||
drawable = ((PyUIGridObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UILINE:
|
||||
drawable = ((PyUILineObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UICIRCLE:
|
||||
drawable = ((PyUICircleObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UIARC:
|
||||
drawable = ((PyUIArcObject*)self)->data.get();
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
return PyUnicode_FromString(drawable->name.c_str());
|
||||
}
|
||||
|
||||
int UIDrawable::set_name(PyObject* self, PyObject* value, void* closure) {
|
||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||
UIDrawable* drawable = nullptr;
|
||||
|
||||
|
||||
switch (objtype) {
|
||||
case PyObjectsEnum::UIFRAME:
|
||||
drawable = ((PyUIFrameObject*)self)->data.get();
|
||||
|
|
@ -322,11 +379,20 @@ int UIDrawable::set_name(PyObject* self, PyObject* value, void* closure) {
|
|||
case PyObjectsEnum::UIGRID:
|
||||
drawable = ((PyUIGridObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UILINE:
|
||||
drawable = ((PyUILineObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UICIRCLE:
|
||||
drawable = ((PyUICircleObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UIARC:
|
||||
drawable = ((PyUIArcObject*)self)->data.get();
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
if (value == NULL || value == Py_None) {
|
||||
drawable->name = "";
|
||||
return 0;
|
||||
|
|
@ -383,7 +449,7 @@ PyObject* UIDrawable::get_float_member(PyObject* self, void* closure) {
|
|||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure) >> 8);
|
||||
int member = reinterpret_cast<intptr_t>(closure) & 0xFF;
|
||||
UIDrawable* drawable = nullptr;
|
||||
|
||||
|
||||
switch (objtype) {
|
||||
case PyObjectsEnum::UIFRAME:
|
||||
drawable = ((PyUIFrameObject*)self)->data.get();
|
||||
|
|
@ -397,11 +463,20 @@ PyObject* UIDrawable::get_float_member(PyObject* self, void* closure) {
|
|||
case PyObjectsEnum::UIGRID:
|
||||
drawable = ((PyUIGridObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UILINE:
|
||||
drawable = ((PyUILineObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UICIRCLE:
|
||||
drawable = ((PyUICircleObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UIARC:
|
||||
drawable = ((PyUIArcObject*)self)->data.get();
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
switch (member) {
|
||||
case 0: // x
|
||||
return PyFloat_FromDouble(drawable->position.x);
|
||||
|
|
@ -421,7 +496,7 @@ int UIDrawable::set_float_member(PyObject* self, PyObject* value, void* closure)
|
|||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure) >> 8);
|
||||
int member = reinterpret_cast<intptr_t>(closure) & 0xFF;
|
||||
UIDrawable* drawable = nullptr;
|
||||
|
||||
|
||||
switch (objtype) {
|
||||
case PyObjectsEnum::UIFRAME:
|
||||
drawable = ((PyUIFrameObject*)self)->data.get();
|
||||
|
|
@ -435,11 +510,20 @@ int UIDrawable::set_float_member(PyObject* self, PyObject* value, void* closure)
|
|||
case PyObjectsEnum::UIGRID:
|
||||
drawable = ((PyUIGridObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UILINE:
|
||||
drawable = ((PyUILineObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UICIRCLE:
|
||||
drawable = ((PyUICircleObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UIARC:
|
||||
drawable = ((PyUIArcObject*)self)->data.get();
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
float val = 0.0f;
|
||||
if (PyFloat_Check(value)) {
|
||||
val = PyFloat_AsDouble(value);
|
||||
|
|
@ -481,7 +565,7 @@ int UIDrawable::set_float_member(PyObject* self, PyObject* value, void* closure)
|
|||
PyObject* UIDrawable::get_pos(PyObject* self, void* closure) {
|
||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||
UIDrawable* drawable = nullptr;
|
||||
|
||||
|
||||
switch (objtype) {
|
||||
case PyObjectsEnum::UIFRAME:
|
||||
drawable = ((PyUIFrameObject*)self)->data.get();
|
||||
|
|
@ -495,11 +579,20 @@ PyObject* UIDrawable::get_pos(PyObject* self, void* closure) {
|
|||
case PyObjectsEnum::UIGRID:
|
||||
drawable = ((PyUIGridObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UILINE:
|
||||
drawable = ((PyUILineObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UICIRCLE:
|
||||
drawable = ((PyUICircleObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UIARC:
|
||||
drawable = ((PyUIArcObject*)self)->data.get();
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// Create a Python Vector object from position
|
||||
PyObject* module = PyImport_ImportModule("mcrfpy");
|
||||
if (!module) return NULL;
|
||||
|
|
@ -519,7 +612,7 @@ PyObject* UIDrawable::get_pos(PyObject* self, void* closure) {
|
|||
int UIDrawable::set_pos(PyObject* self, PyObject* value, void* closure) {
|
||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||
UIDrawable* drawable = nullptr;
|
||||
|
||||
|
||||
switch (objtype) {
|
||||
case PyObjectsEnum::UIFRAME:
|
||||
drawable = ((PyUIFrameObject*)self)->data.get();
|
||||
|
|
@ -533,11 +626,20 @@ int UIDrawable::set_pos(PyObject* self, PyObject* value, void* closure) {
|
|||
case PyObjectsEnum::UIGRID:
|
||||
drawable = ((PyUIGridObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UILINE:
|
||||
drawable = ((PyUILineObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UICIRCLE:
|
||||
drawable = ((PyUICircleObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UIARC:
|
||||
drawable = ((PyUIArcObject*)self)->data.get();
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Accept tuple or Vector
|
||||
float x, y;
|
||||
if (PyTuple_Check(value) && PyTuple_Size(value) == 2) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,10 @@ enum PyObjectsEnum : int
|
|||
UIFRAME = 1,
|
||||
UICAPTION,
|
||||
UISPRITE,
|
||||
UIGRID
|
||||
UIGRID,
|
||||
UILINE,
|
||||
UICIRCLE,
|
||||
UIARC
|
||||
};
|
||||
|
||||
class UIDrawable
|
||||
|
|
|
|||
|
|
@ -0,0 +1,561 @@
|
|||
#include "UILine.h"
|
||||
#include "GameEngine.h"
|
||||
#include "McRFPy_API.h"
|
||||
#include "PyVector.h"
|
||||
#include "PyColor.h"
|
||||
#include "PythonObjectCache.h"
|
||||
#include <cmath>
|
||||
|
||||
UILine::UILine()
|
||||
: start_pos(0.0f, 0.0f),
|
||||
end_pos(0.0f, 0.0f),
|
||||
color(sf::Color::White),
|
||||
thickness(1.0f),
|
||||
vertices(sf::TriangleStrip, 4),
|
||||
vertices_dirty(true)
|
||||
{
|
||||
position = sf::Vector2f(0.0f, 0.0f);
|
||||
}
|
||||
|
||||
UILine::UILine(sf::Vector2f start, sf::Vector2f end, float thickness, sf::Color color)
|
||||
: start_pos(start),
|
||||
end_pos(end),
|
||||
color(color),
|
||||
thickness(thickness),
|
||||
vertices(sf::TriangleStrip, 4),
|
||||
vertices_dirty(true)
|
||||
{
|
||||
// Set position to the midpoint for consistency with other UIDrawables
|
||||
position = (start + end) / 2.0f;
|
||||
}
|
||||
|
||||
UILine::UILine(const UILine& other)
|
||||
: UIDrawable(other),
|
||||
start_pos(other.start_pos),
|
||||
end_pos(other.end_pos),
|
||||
color(other.color),
|
||||
thickness(other.thickness),
|
||||
vertices(sf::TriangleStrip, 4),
|
||||
vertices_dirty(true)
|
||||
{
|
||||
}
|
||||
|
||||
UILine& UILine::operator=(const UILine& other) {
|
||||
if (this != &other) {
|
||||
UIDrawable::operator=(other);
|
||||
start_pos = other.start_pos;
|
||||
end_pos = other.end_pos;
|
||||
color = other.color;
|
||||
thickness = other.thickness;
|
||||
vertices_dirty = true;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
UILine::UILine(UILine&& other) noexcept
|
||||
: UIDrawable(std::move(other)),
|
||||
start_pos(other.start_pos),
|
||||
end_pos(other.end_pos),
|
||||
color(other.color),
|
||||
thickness(other.thickness),
|
||||
vertices(std::move(other.vertices)),
|
||||
vertices_dirty(other.vertices_dirty)
|
||||
{
|
||||
}
|
||||
|
||||
UILine& UILine::operator=(UILine&& other) noexcept {
|
||||
if (this != &other) {
|
||||
UIDrawable::operator=(std::move(other));
|
||||
start_pos = other.start_pos;
|
||||
end_pos = other.end_pos;
|
||||
color = other.color;
|
||||
thickness = other.thickness;
|
||||
vertices = std::move(other.vertices);
|
||||
vertices_dirty = other.vertices_dirty;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void UILine::updateVertices() const {
|
||||
if (!vertices_dirty) return;
|
||||
|
||||
// Calculate direction and perpendicular
|
||||
sf::Vector2f direction = end_pos - start_pos;
|
||||
float length = std::sqrt(direction.x * direction.x + direction.y * direction.y);
|
||||
|
||||
if (length < 0.0001f) {
|
||||
// Zero-length line - make a small dot
|
||||
float half = thickness / 2.0f;
|
||||
vertices[0].position = start_pos + sf::Vector2f(-half, -half);
|
||||
vertices[1].position = start_pos + sf::Vector2f(half, -half);
|
||||
vertices[2].position = start_pos + sf::Vector2f(-half, half);
|
||||
vertices[3].position = start_pos + sf::Vector2f(half, half);
|
||||
} else {
|
||||
// Normalize direction
|
||||
direction /= length;
|
||||
|
||||
// Perpendicular vector
|
||||
sf::Vector2f perpendicular(-direction.y, direction.x);
|
||||
perpendicular *= thickness / 2.0f;
|
||||
|
||||
// Create a quad (triangle strip) for the thick line
|
||||
vertices[0].position = start_pos + perpendicular;
|
||||
vertices[1].position = start_pos - perpendicular;
|
||||
vertices[2].position = end_pos + perpendicular;
|
||||
vertices[3].position = end_pos - perpendicular;
|
||||
}
|
||||
|
||||
// Set colors
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
vertices[i].color = color;
|
||||
}
|
||||
|
||||
vertices_dirty = false;
|
||||
}
|
||||
|
||||
void UILine::render(sf::Vector2f offset, sf::RenderTarget& target) {
|
||||
if (!visible) return;
|
||||
|
||||
updateVertices();
|
||||
|
||||
// Apply opacity to color
|
||||
sf::Color render_color = color;
|
||||
render_color.a = static_cast<sf::Uint8>(color.a * opacity);
|
||||
|
||||
// Use ConvexShape to draw the line as a quad
|
||||
sf::ConvexShape line_shape(4);
|
||||
// Vertices are: 0=start+perp, 1=start-perp, 2=end+perp, 3=end-perp
|
||||
// ConvexShape needs points in clockwise/counter-clockwise order
|
||||
line_shape.setPoint(0, vertices[0].position + offset); // start + perp
|
||||
line_shape.setPoint(1, vertices[2].position + offset); // end + perp
|
||||
line_shape.setPoint(2, vertices[3].position + offset); // end - perp
|
||||
line_shape.setPoint(3, vertices[1].position + offset); // start - perp
|
||||
line_shape.setFillColor(render_color);
|
||||
line_shape.setOutlineThickness(0);
|
||||
|
||||
target.draw(line_shape);
|
||||
}
|
||||
|
||||
UIDrawable* UILine::click_at(sf::Vector2f point) {
|
||||
if (!click_callable) return nullptr;
|
||||
|
||||
// Check if point is close enough to the line
|
||||
// Using a simple bounding box check plus distance-to-line calculation
|
||||
sf::FloatRect bounds = get_bounds();
|
||||
bounds.left -= thickness;
|
||||
bounds.top -= thickness;
|
||||
bounds.width += thickness * 2;
|
||||
bounds.height += thickness * 2;
|
||||
|
||||
if (!bounds.contains(point)) return nullptr;
|
||||
|
||||
// Calculate distance from point to line segment
|
||||
sf::Vector2f line_vec = end_pos - start_pos;
|
||||
sf::Vector2f point_vec = point - start_pos;
|
||||
|
||||
float line_len_sq = line_vec.x * line_vec.x + line_vec.y * line_vec.y;
|
||||
float t = 0.0f;
|
||||
|
||||
if (line_len_sq > 0.0001f) {
|
||||
t = std::max(0.0f, std::min(1.0f,
|
||||
(point_vec.x * line_vec.x + point_vec.y * line_vec.y) / line_len_sq));
|
||||
}
|
||||
|
||||
sf::Vector2f closest = start_pos + t * line_vec;
|
||||
sf::Vector2f diff = point - closest;
|
||||
float distance = std::sqrt(diff.x * diff.x + diff.y * diff.y);
|
||||
|
||||
// Click is valid if within thickness + some margin
|
||||
if (distance <= thickness / 2.0f + 2.0f) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PyObjectsEnum UILine::derived_type() {
|
||||
return PyObjectsEnum::UILINE;
|
||||
}
|
||||
|
||||
sf::FloatRect UILine::get_bounds() const {
|
||||
float min_x = std::min(start_pos.x, end_pos.x);
|
||||
float min_y = std::min(start_pos.y, end_pos.y);
|
||||
float max_x = std::max(start_pos.x, end_pos.x);
|
||||
float max_y = std::max(start_pos.y, end_pos.y);
|
||||
|
||||
return sf::FloatRect(min_x, min_y, max_x - min_x, max_y - min_y);
|
||||
}
|
||||
|
||||
void UILine::move(float dx, float dy) {
|
||||
start_pos.x += dx;
|
||||
start_pos.y += dy;
|
||||
end_pos.x += dx;
|
||||
end_pos.y += dy;
|
||||
position.x += dx;
|
||||
position.y += dy;
|
||||
vertices_dirty = true;
|
||||
}
|
||||
|
||||
void UILine::resize(float w, float h) {
|
||||
// For a line, resize adjusts the end point relative to start
|
||||
end_pos = start_pos + sf::Vector2f(w, h);
|
||||
vertices_dirty = true;
|
||||
}
|
||||
|
||||
// Animation property system
|
||||
bool UILine::setProperty(const std::string& name, float value) {
|
||||
if (name == "thickness") {
|
||||
thickness = value;
|
||||
vertices_dirty = true;
|
||||
return true;
|
||||
}
|
||||
else if (name == "x") {
|
||||
float dx = value - position.x;
|
||||
move(dx, 0);
|
||||
return true;
|
||||
}
|
||||
else if (name == "y") {
|
||||
float dy = value - position.y;
|
||||
move(0, dy);
|
||||
return true;
|
||||
}
|
||||
else if (name == "start_x") {
|
||||
start_pos.x = value;
|
||||
vertices_dirty = true;
|
||||
return true;
|
||||
}
|
||||
else if (name == "start_y") {
|
||||
start_pos.y = value;
|
||||
vertices_dirty = true;
|
||||
return true;
|
||||
}
|
||||
else if (name == "end_x") {
|
||||
end_pos.x = value;
|
||||
vertices_dirty = true;
|
||||
return true;
|
||||
}
|
||||
else if (name == "end_y") {
|
||||
end_pos.y = value;
|
||||
vertices_dirty = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UILine::setProperty(const std::string& name, const sf::Color& value) {
|
||||
if (name == "color") {
|
||||
color = value;
|
||||
vertices_dirty = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UILine::setProperty(const std::string& name, const sf::Vector2f& value) {
|
||||
if (name == "start") {
|
||||
start_pos = value;
|
||||
vertices_dirty = true;
|
||||
return true;
|
||||
}
|
||||
else if (name == "end") {
|
||||
end_pos = value;
|
||||
vertices_dirty = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UILine::getProperty(const std::string& name, float& value) const {
|
||||
if (name == "thickness") {
|
||||
value = thickness;
|
||||
return true;
|
||||
}
|
||||
else if (name == "x") {
|
||||
value = position.x;
|
||||
return true;
|
||||
}
|
||||
else if (name == "y") {
|
||||
value = position.y;
|
||||
return true;
|
||||
}
|
||||
else if (name == "start_x") {
|
||||
value = start_pos.x;
|
||||
return true;
|
||||
}
|
||||
else if (name == "start_y") {
|
||||
value = start_pos.y;
|
||||
return true;
|
||||
}
|
||||
else if (name == "end_x") {
|
||||
value = end_pos.x;
|
||||
return true;
|
||||
}
|
||||
else if (name == "end_y") {
|
||||
value = end_pos.y;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UILine::getProperty(const std::string& name, sf::Color& value) const {
|
||||
if (name == "color") {
|
||||
value = color;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UILine::getProperty(const std::string& name, sf::Vector2f& value) const {
|
||||
if (name == "start") {
|
||||
value = start_pos;
|
||||
return true;
|
||||
}
|
||||
else if (name == "end") {
|
||||
value = end_pos;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Python API implementation
|
||||
PyObject* UILine::get_start(PyUILineObject* self, void* closure) {
|
||||
auto vec = self->data->getStart();
|
||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
||||
auto obj = (PyVectorObject*)type->tp_alloc(type, 0);
|
||||
Py_DECREF(type);
|
||||
if (obj) {
|
||||
obj->data = vec;
|
||||
}
|
||||
return (PyObject*)obj;
|
||||
}
|
||||
|
||||
int UILine::set_start(PyUILineObject* self, PyObject* value, void* closure) {
|
||||
PyVectorObject* vec = PyVector::from_arg(value);
|
||||
if (!vec) {
|
||||
PyErr_SetString(PyExc_TypeError, "start must be a Vector or tuple (x, y)");
|
||||
return -1;
|
||||
}
|
||||
self->data->setStart(vec->data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* UILine::get_end(PyUILineObject* self, void* closure) {
|
||||
auto vec = self->data->getEnd();
|
||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
||||
auto obj = (PyVectorObject*)type->tp_alloc(type, 0);
|
||||
Py_DECREF(type);
|
||||
if (obj) {
|
||||
obj->data = vec;
|
||||
}
|
||||
return (PyObject*)obj;
|
||||
}
|
||||
|
||||
int UILine::set_end(PyUILineObject* self, PyObject* value, void* closure) {
|
||||
PyVectorObject* vec = PyVector::from_arg(value);
|
||||
if (!vec) {
|
||||
PyErr_SetString(PyExc_TypeError, "end must be a Vector or tuple (x, y)");
|
||||
return -1;
|
||||
}
|
||||
self->data->setEnd(vec->data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* UILine::get_color(PyUILineObject* 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 UILine::set_color(PyUILineObject* 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* UILine::get_thickness(PyUILineObject* self, void* closure) {
|
||||
return PyFloat_FromDouble(self->data->getThickness());
|
||||
}
|
||||
|
||||
int UILine::set_thickness(PyUILineObject* self, PyObject* value, void* closure) {
|
||||
float thickness;
|
||||
if (PyFloat_Check(value)) {
|
||||
thickness = PyFloat_AsDouble(value);
|
||||
} else if (PyLong_Check(value)) {
|
||||
thickness = PyLong_AsLong(value);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "thickness must be a number");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (thickness < 0.0f) {
|
||||
PyErr_SetString(PyExc_ValueError, "thickness must be non-negative");
|
||||
return -1;
|
||||
}
|
||||
|
||||
self->data->setThickness(thickness);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Define the Python type alias for macros
|
||||
typedef PyUILineObject PyObjectType;
|
||||
|
||||
// Method definitions
|
||||
PyMethodDef UILine_methods[] = {
|
||||
UIDRAWABLE_METHODS,
|
||||
{NULL} // Sentinel
|
||||
};
|
||||
|
||||
PyGetSetDef UILine::getsetters[] = {
|
||||
{"start", (getter)UILine::get_start, (setter)UILine::set_start,
|
||||
MCRF_PROPERTY(start, "Starting point of the line as a Vector."), NULL},
|
||||
{"end", (getter)UILine::get_end, (setter)UILine::set_end,
|
||||
MCRF_PROPERTY(end, "Ending point of the line as a Vector."), NULL},
|
||||
{"color", (getter)UILine::get_color, (setter)UILine::set_color,
|
||||
MCRF_PROPERTY(color, "Line color as a Color object."), NULL},
|
||||
{"thickness", (getter)UILine::get_thickness, (setter)UILine::set_thickness,
|
||||
MCRF_PROPERTY(thickness, "Line thickness in pixels."), NULL},
|
||||
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
||||
MCRF_PROPERTY(click, "Callable executed when line is clicked."),
|
||||
(void*)PyObjectsEnum::UILINE},
|
||||
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int,
|
||||
MCRF_PROPERTY(z_index, "Z-order for rendering (lower values rendered first)."),
|
||||
(void*)PyObjectsEnum::UILINE},
|
||||
{"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name,
|
||||
MCRF_PROPERTY(name, "Name for finding this element."),
|
||||
(void*)PyObjectsEnum::UILINE},
|
||||
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos,
|
||||
MCRF_PROPERTY(pos, "Position as a Vector (midpoint of line)."),
|
||||
(void*)PyObjectsEnum::UILINE},
|
||||
UIDRAWABLE_GETSETTERS,
|
||||
{NULL}
|
||||
};
|
||||
|
||||
PyObject* UILine::repr(PyUILineObject* self) {
|
||||
std::ostringstream ss;
|
||||
if (!self->data) {
|
||||
ss << "<Line (invalid internal object)>";
|
||||
} else {
|
||||
auto start = self->data->getStart();
|
||||
auto end = self->data->getEnd();
|
||||
auto color = self->data->getColor();
|
||||
ss << "<Line start=(" << start.x << ", " << start.y << ") "
|
||||
<< "end=(" << end.x << ", " << end.y << ") "
|
||||
<< "thickness=" << self->data->getThickness() << " "
|
||||
<< "color=(" << (int)color.r << ", " << (int)color.g << ", "
|
||||
<< (int)color.b << ", " << (int)color.a << ")>";
|
||||
}
|
||||
std::string repr_str = ss.str();
|
||||
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
|
||||
}
|
||||
|
||||
int UILine::init(PyUILineObject* self, PyObject* args, PyObject* kwds) {
|
||||
// Arguments
|
||||
PyObject* start_obj = nullptr;
|
||||
PyObject* end_obj = nullptr;
|
||||
float thickness = 1.0f;
|
||||
PyObject* color_obj = nullptr;
|
||||
PyObject* click_handler = nullptr;
|
||||
int visible = 1;
|
||||
float opacity = 1.0f;
|
||||
int z_index = 0;
|
||||
const char* name = nullptr;
|
||||
|
||||
static const char* kwlist[] = {
|
||||
"start", "end", "thickness", "color",
|
||||
"click", "visible", "opacity", "z_index", "name",
|
||||
nullptr
|
||||
};
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOfOOifiz", const_cast<char**>(kwlist),
|
||||
&start_obj, &end_obj, &thickness, &color_obj,
|
||||
&click_handler, &visible, &opacity, &z_index, &name)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parse start position
|
||||
sf::Vector2f start(0.0f, 0.0f);
|
||||
if (start_obj) {
|
||||
PyVectorObject* vec = PyVector::from_arg(start_obj);
|
||||
if (vec) {
|
||||
start = vec->data;
|
||||
} else {
|
||||
PyErr_Clear();
|
||||
PyErr_SetString(PyExc_TypeError, "start must be a Vector or tuple (x, y)");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse end position
|
||||
sf::Vector2f end(0.0f, 0.0f);
|
||||
if (end_obj) {
|
||||
PyVectorObject* vec = PyVector::from_arg(end_obj);
|
||||
if (vec) {
|
||||
end = vec->data;
|
||||
} else {
|
||||
PyErr_Clear();
|
||||
PyErr_SetString(PyExc_TypeError, "end must be a Vector or tuple (x, y)");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse color
|
||||
sf::Color color = sf::Color::White;
|
||||
if (color_obj && color_obj != Py_None) {
|
||||
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");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate thickness
|
||||
if (thickness < 0.0f) {
|
||||
PyErr_SetString(PyExc_ValueError, "thickness must be non-negative");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create the line
|
||||
self->data = std::make_shared<UILine>(start, end, thickness, color);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "Python.h"
|
||||
#include "structmember.h"
|
||||
#include "UIDrawable.h"
|
||||
#include "UIBase.h"
|
||||
#include "PyDrawable.h"
|
||||
#include "PyColor.h"
|
||||
#include "PyVector.h"
|
||||
#include "McRFPy_Doc.h"
|
||||
|
||||
// Forward declaration
|
||||
class UILine;
|
||||
|
||||
// Python object structure
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
std::shared_ptr<UILine> data;
|
||||
PyObject* weakreflist;
|
||||
} PyUILineObject;
|
||||
|
||||
class UILine : public UIDrawable
|
||||
{
|
||||
private:
|
||||
sf::Vector2f start_pos; // Starting point
|
||||
sf::Vector2f end_pos; // Ending point
|
||||
sf::Color color; // Line color
|
||||
float thickness; // Line thickness in pixels
|
||||
|
||||
// Cached vertex array for rendering
|
||||
mutable sf::VertexArray vertices;
|
||||
mutable bool vertices_dirty;
|
||||
|
||||
void updateVertices() const;
|
||||
|
||||
public:
|
||||
UILine();
|
||||
UILine(sf::Vector2f start, sf::Vector2f end, float thickness = 1.0f, sf::Color color = sf::Color::White);
|
||||
|
||||
// Copy constructor and assignment
|
||||
UILine(const UILine& other);
|
||||
UILine& operator=(const UILine& other);
|
||||
|
||||
// Move constructor and assignment
|
||||
UILine(UILine&& other) noexcept;
|
||||
UILine& operator=(UILine&& other) noexcept;
|
||||
|
||||
// UIDrawable interface
|
||||
void render(sf::Vector2f offset, sf::RenderTarget& target) override;
|
||||
UIDrawable* click_at(sf::Vector2f point) override;
|
||||
PyObjectsEnum derived_type() override;
|
||||
|
||||
// Getters and setters
|
||||
sf::Vector2f getStart() const { return start_pos; }
|
||||
void setStart(sf::Vector2f pos) { start_pos = pos; vertices_dirty = true; }
|
||||
|
||||
sf::Vector2f getEnd() const { return end_pos; }
|
||||
void setEnd(sf::Vector2f pos) { end_pos = pos; vertices_dirty = true; }
|
||||
|
||||
sf::Color getColor() const { return color; }
|
||||
void setColor(sf::Color c) { color = c; vertices_dirty = true; }
|
||||
|
||||
float getThickness() const { return thickness; }
|
||||
void setThickness(float t) { thickness = t; vertices_dirty = true; }
|
||||
|
||||
// Phase 1 virtual method implementations
|
||||
sf::FloatRect get_bounds() const override;
|
||||
void move(float dx, float dy) override;
|
||||
void resize(float w, float h) override;
|
||||
|
||||
// Property system for animations
|
||||
bool setProperty(const std::string& name, float value) override;
|
||||
bool setProperty(const std::string& name, const sf::Color& value) override;
|
||||
bool setProperty(const std::string& name, const sf::Vector2f& value) override;
|
||||
bool getProperty(const std::string& name, float& value) const override;
|
||||
bool getProperty(const std::string& name, sf::Color& value) const override;
|
||||
bool getProperty(const std::string& name, sf::Vector2f& value) const override;
|
||||
|
||||
// Python API
|
||||
static PyObject* get_start(PyUILineObject* self, void* closure);
|
||||
static int set_start(PyUILineObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_end(PyUILineObject* self, void* closure);
|
||||
static int set_end(PyUILineObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_color(PyUILineObject* self, void* closure);
|
||||
static int set_color(PyUILineObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_thickness(PyUILineObject* self, void* closure);
|
||||
static int set_thickness(PyUILineObject* self, PyObject* value, void* closure);
|
||||
|
||||
static PyGetSetDef getsetters[];
|
||||
static PyObject* repr(PyUILineObject* self);
|
||||
static int init(PyUILineObject* self, PyObject* args, PyObject* kwds);
|
||||
};
|
||||
|
||||
// Method definitions (extern to be defined in .cpp)
|
||||
extern PyMethodDef UILine_methods[];
|
||||
|
||||
namespace mcrfpydef {
|
||||
static PyTypeObject PyUILineType = {
|
||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||
.tp_name = "mcrfpy.Line",
|
||||
.tp_basicsize = sizeof(PyUILineObject),
|
||||
.tp_itemsize = 0,
|
||||
.tp_dealloc = (destructor)[](PyObject* self) {
|
||||
PyUILineObject* obj = (PyUILineObject*)self;
|
||||
if (obj->weakreflist != NULL) {
|
||||
PyObject_ClearWeakRefs(self);
|
||||
}
|
||||
obj->data.reset();
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
},
|
||||
.tp_repr = (reprfunc)UILine::repr,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
||||
.tp_doc = PyDoc_STR(
|
||||
"Line(start=None, end=None, thickness=1.0, color=None, **kwargs)\n\n"
|
||||
"A line UI element for drawing straight lines between two points.\n\n"
|
||||
"Args:\n"
|
||||
" start (tuple, optional): Starting point as (x, y). Default: (0, 0)\n"
|
||||
" end (tuple, optional): Ending point as (x, y). Default: (0, 0)\n"
|
||||
" thickness (float, optional): Line thickness in pixels. Default: 1.0\n"
|
||||
" color (Color, optional): Line color. Default: White\n\n"
|
||||
"Keyword Args:\n"
|
||||
" click (callable): Click handler. Default: None\n"
|
||||
" visible (bool): Visibility state. Default: True\n"
|
||||
" opacity (float): Opacity (0.0-1.0). Default: 1.0\n"
|
||||
" z_index (int): Rendering order. Default: 0\n"
|
||||
" name (str): Element name for finding. Default: None\n\n"
|
||||
"Attributes:\n"
|
||||
" start (Vector): Starting point\n"
|
||||
" end (Vector): Ending point\n"
|
||||
" thickness (float): Line thickness\n"
|
||||
" color (Color): Line color\n"
|
||||
" visible (bool): Visibility state\n"
|
||||
" opacity (float): Opacity value\n"
|
||||
" z_index (int): Rendering order\n"
|
||||
" name (str): Element name\n"
|
||||
),
|
||||
.tp_methods = UILine_methods,
|
||||
.tp_getset = UILine::getsetters,
|
||||
.tp_base = &mcrfpydef::PyDrawableType,
|
||||
.tp_init = (initproc)UILine::init,
|
||||
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* {
|
||||
PyUILineObject* self = (PyUILineObject*)type->tp_alloc(type, 0);
|
||||
if (self) {
|
||||
self->data = std::make_shared<UILine>();
|
||||
self->weakreflist = nullptr;
|
||||
}
|
||||
return (PyObject*)self;
|
||||
}
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue