fix: prevent segfault when closing window via X button
- Add cleanup() method to GameEngine to clear Python references before destruction - Clear timers and McRFPy_API references in proper order - Call cleanup() at end of run loop and in destructor - Ensure cleanup is only called once per GameEngine instance Also includes: - Fix audio ::stop() calls (already in place, OpenAL warning is benign) - Add Caption support for x, y keywords (e.g. Caption("text", x=5, y=10)) - Refactor UIDrawable_methods.h into UIBase.h for better organization - Move UIEntity-specific implementations to UIEntityPyMethods.h 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
ee6550bf63
commit
97067a104e
|
@ -73,11 +73,32 @@ GameEngine::GameEngine(const McRogueFaceConfig& cfg)
|
|||
|
||||
GameEngine::~GameEngine()
|
||||
{
|
||||
cleanup();
|
||||
for (auto& [name, scene] : scenes) {
|
||||
delete scene;
|
||||
}
|
||||
}
|
||||
|
||||
void GameEngine::cleanup()
|
||||
{
|
||||
if (cleaned_up) return;
|
||||
cleaned_up = true;
|
||||
|
||||
// Clear Python references before destroying C++ objects
|
||||
// Clear all timers (they hold Python callables)
|
||||
timers.clear();
|
||||
|
||||
// Clear McRFPy_API's reference to this game engine
|
||||
if (McRFPy_API::game == this) {
|
||||
McRFPy_API::game = nullptr;
|
||||
}
|
||||
|
||||
// Force close the window if it's still open
|
||||
if (window && window->isOpen()) {
|
||||
window->close();
|
||||
}
|
||||
}
|
||||
|
||||
Scene* GameEngine::currentScene() { return scenes[scene]; }
|
||||
void GameEngine::changeScene(std::string s)
|
||||
{
|
||||
|
@ -162,6 +183,9 @@ void GameEngine::run()
|
|||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up before exiting the run loop
|
||||
cleanup();
|
||||
}
|
||||
|
||||
std::shared_ptr<PyTimerCallable> GameEngine::getTimer(const std::string& name)
|
||||
|
|
|
@ -28,6 +28,7 @@ class GameEngine
|
|||
|
||||
bool headless = false;
|
||||
McRogueFaceConfig config;
|
||||
bool cleaned_up = false;
|
||||
|
||||
void testTimers();
|
||||
|
||||
|
@ -50,6 +51,7 @@ public:
|
|||
sf::RenderTarget* getRenderTargetPtr() { return render_target; }
|
||||
void run();
|
||||
void sUserInput();
|
||||
void cleanup(); // Clean up Python references before destruction
|
||||
int getFrame() { return currentFrame; }
|
||||
float getFrameTime() { return frameTime; }
|
||||
sf::View getView() { return visible; }
|
||||
|
|
102
src/UIBase.h
102
src/UIBase.h
|
@ -1,4 +1,6 @@
|
|||
#pragma once
|
||||
#include "Python.h"
|
||||
#include <memory>
|
||||
|
||||
class UIEntity;
|
||||
typedef struct {
|
||||
|
@ -30,3 +32,103 @@ typedef struct {
|
|||
PyObject_HEAD
|
||||
std::shared_ptr<UISprite> data;
|
||||
} PyUISpriteObject;
|
||||
|
||||
// Common Python method implementations for UIDrawable-derived classes
|
||||
// These template functions provide shared functionality for Python bindings
|
||||
|
||||
// get_bounds method implementation (#89)
|
||||
template<typename T>
|
||||
static PyObject* UIDrawable_get_bounds(T* self, PyObject* Py_UNUSED(args))
|
||||
{
|
||||
auto bounds = self->data->get_bounds();
|
||||
return Py_BuildValue("(ffff)", bounds.left, bounds.top, bounds.width, bounds.height);
|
||||
}
|
||||
|
||||
// move method implementation (#98)
|
||||
template<typename T>
|
||||
static PyObject* UIDrawable_move(T* self, PyObject* args)
|
||||
{
|
||||
float dx, dy;
|
||||
if (!PyArg_ParseTuple(args, "ff", &dx, &dy)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->data->move(dx, dy);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
// resize method implementation (#98)
|
||||
template<typename T>
|
||||
static PyObject* UIDrawable_resize(T* self, PyObject* args)
|
||||
{
|
||||
float w, h;
|
||||
if (!PyArg_ParseTuple(args, "ff", &w, &h)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->data->resize(w, h);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
// Macro to add common UIDrawable methods to a method array
|
||||
#define UIDRAWABLE_METHODS \
|
||||
{"get_bounds", (PyCFunction)UIDrawable_get_bounds<PyObjectType>, METH_NOARGS, \
|
||||
"Get bounding box as (x, y, width, height)"}, \
|
||||
{"move", (PyCFunction)UIDrawable_move<PyObjectType>, METH_VARARGS, \
|
||||
"Move by relative offset (dx, dy)"}, \
|
||||
{"resize", (PyCFunction)UIDrawable_resize<PyObjectType>, METH_VARARGS, \
|
||||
"Resize to new dimensions (width, height)"}
|
||||
|
||||
// Property getters/setters for visible and opacity
|
||||
template<typename T>
|
||||
static PyObject* UIDrawable_get_visible(T* self, void* closure)
|
||||
{
|
||||
return PyBool_FromLong(self->data->visible);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static int UIDrawable_set_visible(T* self, PyObject* value, void* closure)
|
||||
{
|
||||
if (!PyBool_Check(value)) {
|
||||
PyErr_SetString(PyExc_TypeError, "visible must be a boolean");
|
||||
return -1;
|
||||
}
|
||||
self->data->visible = PyObject_IsTrue(value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static PyObject* UIDrawable_get_opacity(T* self, void* closure)
|
||||
{
|
||||
return PyFloat_FromDouble(self->data->opacity);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static int UIDrawable_set_opacity(T* self, PyObject* value, void* closure)
|
||||
{
|
||||
float opacity;
|
||||
if (PyFloat_Check(value)) {
|
||||
opacity = PyFloat_AsDouble(value);
|
||||
} else if (PyLong_Check(value)) {
|
||||
opacity = PyLong_AsDouble(value);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "opacity must be a number");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Clamp to valid range
|
||||
if (opacity < 0.0f) opacity = 0.0f;
|
||||
if (opacity > 1.0f) opacity = 1.0f;
|
||||
|
||||
self->data->opacity = opacity;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Macro to add common UIDrawable properties to a getsetters array
|
||||
#define UIDRAWABLE_GETSETTERS \
|
||||
{"visible", (getter)UIDrawable_get_visible<PyObjectType>, (setter)UIDrawable_set_visible<PyObjectType>, \
|
||||
"Visibility flag", NULL}, \
|
||||
{"opacity", (getter)UIDrawable_get_opacity<PyObjectType>, (setter)UIDrawable_set_opacity<PyObjectType>, \
|
||||
"Opacity (0.0 = transparent, 1.0 = opaque)", NULL}
|
||||
|
||||
// UIEntity specializations are defined in UIEntity.cpp after UIEntity class is complete
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "PyVector.h"
|
||||
#include "PyFont.h"
|
||||
#include "PyPositionHelper.h"
|
||||
#include "UIDrawable_methods.h"
|
||||
// UIDrawable methods now in UIBase.h
|
||||
#include <algorithm>
|
||||
|
||||
UICaption::UICaption()
|
||||
|
@ -277,7 +277,7 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
|||
{
|
||||
using namespace mcrfpydef;
|
||||
|
||||
static const char* keywords[] = { "x", "y", "text", "font", "fill_color", "outline_color", "outline", "click", "pos", nullptr };
|
||||
static const char* keywords[] = { "text", "x", "y", "font", "fill_color", "outline_color", "outline", "click", "pos", nullptr };
|
||||
float x = 0.0f, y = 0.0f, outline = 0.0f;
|
||||
char* text = NULL;
|
||||
PyObject* font = NULL;
|
||||
|
@ -286,11 +286,48 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
|||
PyObject* click_handler = NULL;
|
||||
PyObject* pos_obj = NULL;
|
||||
|
||||
// Try parsing all arguments with keywords
|
||||
if (PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOfOO",
|
||||
const_cast<char**>(keywords),
|
||||
// Handle different argument patterns
|
||||
Py_ssize_t args_size = PyTuple_Size(args);
|
||||
|
||||
if (args_size >= 2 && !PyUnicode_Check(PyTuple_GetItem(args, 0))) {
|
||||
// Pattern 1: (x, y, text, ...) or ((x,y), text, ...)
|
||||
PyObject* first_arg = PyTuple_GetItem(args, 0);
|
||||
|
||||
// Check if first arg is a tuple/Vector (pos format)
|
||||
if (PyTuple_Check(first_arg) || PyObject_IsInstance(first_arg, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector"))) {
|
||||
// Pattern: ((x,y), text, ...)
|
||||
static const char* pos_keywords[] = { "pos", "text", "font", "fill_color", "outline_color", "outline", "click", "x", "y", nullptr };
|
||||
PyObject* pos = NULL;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OzOOOfOff",
|
||||
const_cast<char**>(pos_keywords),
|
||||
&pos, &text, &font, &fill_color, &outline_color, &outline, &click_handler, &x, &y))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parse position
|
||||
if (pos && pos != Py_None) {
|
||||
PyVectorObject* vec = PyVector::from_arg(pos);
|
||||
if (!vec) {
|
||||
PyErr_SetString(PyExc_TypeError, "pos must be a Vector or tuple (x, y)");
|
||||
return -1;
|
||||
}
|
||||
x = vec->data.x;
|
||||
y = vec->data.y;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Pattern: (x, y, text, ...)
|
||||
static const char* xy_keywords[] = { "x", "y", "text", "font", "fill_color", "outline_color", "outline", "click", "pos", nullptr };
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOfOO",
|
||||
const_cast<char**>(xy_keywords),
|
||||
&x, &y, &text, &font, &fill_color, &outline_color, &outline, &click_handler, &pos_obj))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If pos was provided, it overrides x,y
|
||||
if (pos_obj && pos_obj != Py_None) {
|
||||
PyVectorObject* vec = PyVector::from_arg(pos_obj);
|
||||
|
@ -302,23 +339,19 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
|||
y = vec->data.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
PyErr_Clear();
|
||||
|
||||
// Try alternative: first arg is pos tuple/Vector
|
||||
static const char* alt_keywords[] = { "pos", "text", "font", "fill_color", "outline_color", "outline", "click", nullptr };
|
||||
PyObject* pos = NULL;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OzOOOfO",
|
||||
const_cast<char**>(alt_keywords),
|
||||
&pos, &text, &font, &fill_color, &outline_color, &outline, &click_handler))
|
||||
// Pattern 2: (text, ...) with x, y as keywords
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|zffOOOfOO",
|
||||
const_cast<char**>(keywords),
|
||||
&text, &x, &y, &font, &fill_color, &outline_color, &outline, &click_handler, &pos_obj))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parse position
|
||||
if (pos && pos != Py_None) {
|
||||
PyVectorObject* vec = PyVector::from_arg(pos);
|
||||
// If pos was provided, it overrides x,y
|
||||
if (pos_obj && pos_obj != Py_None) {
|
||||
PyVectorObject* vec = PyVector::from_arg(pos_obj);
|
||||
if (!vec) {
|
||||
PyErr_SetString(PyExc_TypeError, "pos must be a Vector or tuple (x, y)");
|
||||
return -1;
|
||||
|
|
|
@ -1,193 +0,0 @@
|
|||
#pragma once
|
||||
#include "Python.h"
|
||||
#include "UIDrawable.h"
|
||||
#include "UIBase.h" // For PyUIEntityObject
|
||||
|
||||
// Common methods for all UIDrawable-derived classes
|
||||
|
||||
// get_bounds method implementation (#89)
|
||||
template<typename T>
|
||||
static PyObject* UIDrawable_get_bounds(T* self, PyObject* Py_UNUSED(args))
|
||||
{
|
||||
auto bounds = self->data->get_bounds();
|
||||
return Py_BuildValue("(ffff)", bounds.left, bounds.top, bounds.width, bounds.height);
|
||||
}
|
||||
|
||||
// move method implementation (#98)
|
||||
template<typename T>
|
||||
static PyObject* UIDrawable_move(T* self, PyObject* args)
|
||||
{
|
||||
float dx, dy;
|
||||
if (!PyArg_ParseTuple(args, "ff", &dx, &dy)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->data->move(dx, dy);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
// resize method implementation (#98)
|
||||
template<typename T>
|
||||
static PyObject* UIDrawable_resize(T* self, PyObject* args)
|
||||
{
|
||||
float w, h;
|
||||
if (!PyArg_ParseTuple(args, "ff", &w, &h)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->data->resize(w, h);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
// Macro to add common UIDrawable methods to a method array
|
||||
#define UIDRAWABLE_METHODS \
|
||||
{"get_bounds", (PyCFunction)UIDrawable_get_bounds<PyObjectType>, METH_NOARGS, \
|
||||
"Get bounding box as (x, y, width, height)"}, \
|
||||
{"move", (PyCFunction)UIDrawable_move<PyObjectType>, METH_VARARGS, \
|
||||
"Move by relative offset (dx, dy)"}, \
|
||||
{"resize", (PyCFunction)UIDrawable_resize<PyObjectType>, METH_VARARGS, \
|
||||
"Resize to new dimensions (width, height)"}
|
||||
|
||||
// Common getsetters for UIDrawable properties
|
||||
#define UIDRAWABLE_GETSETTERS \
|
||||
{"visible", (getter)UIDrawable_get_visible<PyObjectType>, (setter)UIDrawable_set_visible<PyObjectType>, \
|
||||
"Whether the object is visible", NULL}, \
|
||||
{"opacity", (getter)UIDrawable_get_opacity<PyObjectType>, (setter)UIDrawable_set_opacity<PyObjectType>, \
|
||||
"Opacity level (0.0 = transparent, 1.0 = opaque)", NULL}
|
||||
|
||||
// Visible property getter (new for #87)
|
||||
template<typename T>
|
||||
static PyObject* UIDrawable_get_visible(T* self, void* closure)
|
||||
{
|
||||
return PyBool_FromLong(self->data->visible);
|
||||
}
|
||||
|
||||
// Visible property setter (new for #87)
|
||||
template<typename T>
|
||||
static int UIDrawable_set_visible(T* self, PyObject* value, void* closure)
|
||||
{
|
||||
if (!PyBool_Check(value)) {
|
||||
PyErr_SetString(PyExc_TypeError, "visible must be a boolean");
|
||||
return -1;
|
||||
}
|
||||
|
||||
self->data->visible = (value == Py_True);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Opacity property getter (new for #88)
|
||||
template<typename T>
|
||||
static PyObject* UIDrawable_get_opacity(T* self, void* closure)
|
||||
{
|
||||
return PyFloat_FromDouble(self->data->opacity);
|
||||
}
|
||||
|
||||
// Opacity property setter (new for #88)
|
||||
template<typename T>
|
||||
static int UIDrawable_set_opacity(T* self, PyObject* value, void* closure)
|
||||
{
|
||||
float val;
|
||||
if (PyFloat_Check(value)) {
|
||||
val = PyFloat_AsDouble(value);
|
||||
} else if (PyLong_Check(value)) {
|
||||
val = PyLong_AsLong(value);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "opacity must be a number");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Clamp to valid range
|
||||
if (val < 0.0f) val = 0.0f;
|
||||
if (val > 1.0f) val = 1.0f;
|
||||
|
||||
self->data->opacity = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Specializations for UIEntity that delegate to its sprite member
|
||||
template<>
|
||||
inline PyObject* UIDrawable_get_visible<PyUIEntityObject>(PyUIEntityObject* self, void* closure)
|
||||
{
|
||||
return PyBool_FromLong(self->data->sprite.visible);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline int UIDrawable_set_visible<PyUIEntityObject>(PyUIEntityObject* self, PyObject* value, void* closure)
|
||||
{
|
||||
if (!PyBool_Check(value)) {
|
||||
PyErr_SetString(PyExc_TypeError, "visible must be a boolean");
|
||||
return -1;
|
||||
}
|
||||
|
||||
self->data->sprite.visible = (value == Py_True);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline PyObject* UIDrawable_get_opacity<PyUIEntityObject>(PyUIEntityObject* self, void* closure)
|
||||
{
|
||||
return PyFloat_FromDouble(self->data->sprite.opacity);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline int UIDrawable_set_opacity<PyUIEntityObject>(PyUIEntityObject* self, PyObject* value, void* closure)
|
||||
{
|
||||
float val;
|
||||
if (PyFloat_Check(value)) {
|
||||
val = PyFloat_AsDouble(value);
|
||||
} else if (PyLong_Check(value)) {
|
||||
val = PyLong_AsLong(value);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "opacity must be a number");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Clamp to valid range
|
||||
if (val < 0.0f) val = 0.0f;
|
||||
if (val > 1.0f) val = 1.0f;
|
||||
|
||||
self->data->sprite.opacity = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// For get_bounds - UIEntity doesn't have this method, so we delegate to sprite
|
||||
template<>
|
||||
inline PyObject* UIDrawable_get_bounds<PyUIEntityObject>(PyUIEntityObject* self, PyObject* Py_UNUSED(args))
|
||||
{
|
||||
auto bounds = self->data->sprite.get_bounds();
|
||||
return Py_BuildValue("(ffff)", bounds.left, bounds.top, bounds.width, bounds.height);
|
||||
}
|
||||
|
||||
// For move - UIEntity needs to update its position
|
||||
template<>
|
||||
inline PyObject* UIDrawable_move<PyUIEntityObject>(PyUIEntityObject* self, PyObject* args)
|
||||
{
|
||||
float dx, dy;
|
||||
if (!PyArg_ParseTuple(args, "ff", &dx, &dy)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Update entity position
|
||||
self->data->position.x += dx;
|
||||
self->data->position.y += dy;
|
||||
self->data->collision_pos.x = std::floor(self->data->position.x);
|
||||
self->data->collision_pos.y = std::floor(self->data->position.y);
|
||||
|
||||
// Also update sprite position
|
||||
self->data->sprite.move(dx, dy);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
// For resize - delegate to sprite
|
||||
template<>
|
||||
inline PyObject* UIDrawable_resize<PyUIEntityObject>(PyUIEntityObject* self, PyObject* args)
|
||||
{
|
||||
float w, h;
|
||||
if (!PyArg_ParseTuple(args, "ff", &w, &h)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->data->sprite.resize(w, h);
|
||||
Py_RETURN_NONE;
|
||||
}
|
|
@ -5,7 +5,8 @@
|
|||
#include "PyObjectUtils.h"
|
||||
#include "PyVector.h"
|
||||
#include "PyPositionHelper.h"
|
||||
#include "UIDrawable_methods.h"
|
||||
// UIDrawable methods now in UIBase.h
|
||||
#include "UIEntityPyMethods.h"
|
||||
|
||||
|
||||
UIEntity::UIEntity()
|
||||
|
@ -383,7 +384,8 @@ PyGetSetDef UIEntity::getsetters[] = {
|
|||
{"sprite_number", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite index on the texture on the display (deprecated: use sprite_index)", NULL},
|
||||
{"x", (getter)UIEntity::get_float_member, (setter)UIEntity::set_float_member, "Entity x position", (void*)0},
|
||||
{"y", (getter)UIEntity::get_float_member, (setter)UIEntity::set_float_member, "Entity y position", (void*)1},
|
||||
UIDRAWABLE_GETSETTERS,
|
||||
{"visible", (getter)UIEntity_get_visible, (setter)UIEntity_set_visible, "Visibility flag", NULL},
|
||||
{"opacity", (getter)UIEntity_get_opacity, (setter)UIEntity_set_opacity, "Opacity (0.0 = transparent, 1.0 = opaque)", NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
|
|
@ -51,6 +51,11 @@ public:
|
|||
bool setProperty(const std::string& name, int value);
|
||||
bool getProperty(const std::string& name, float& value) const;
|
||||
|
||||
// Methods that delegate to sprite
|
||||
sf::FloatRect get_bounds() const { return sprite.get_bounds(); }
|
||||
void move(float dx, float dy) { sprite.move(dx, dy); position.x += dx; position.y += dy; }
|
||||
void resize(float w, float h) { /* Entities don't support direct resizing */ }
|
||||
|
||||
static PyObject* at(PyUIEntityObject* self, PyObject* o);
|
||||
static PyObject* index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
||||
static PyObject* die(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
#include "UIEntity.h"
|
||||
#include "UIBase.h"
|
||||
|
||||
// UIEntity-specific property implementations
|
||||
// These delegate to the wrapped sprite member
|
||||
|
||||
// Visible property
|
||||
static PyObject* UIEntity_get_visible(PyUIEntityObject* self, void* closure)
|
||||
{
|
||||
return PyBool_FromLong(self->data->sprite.visible);
|
||||
}
|
||||
|
||||
static int UIEntity_set_visible(PyUIEntityObject* self, PyObject* value, void* closure)
|
||||
{
|
||||
if (!PyBool_Check(value)) {
|
||||
PyErr_SetString(PyExc_TypeError, "visible must be a boolean");
|
||||
return -1;
|
||||
}
|
||||
self->data->sprite.visible = PyObject_IsTrue(value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Opacity property
|
||||
static PyObject* UIEntity_get_opacity(PyUIEntityObject* self, void* closure)
|
||||
{
|
||||
return PyFloat_FromDouble(self->data->sprite.opacity);
|
||||
}
|
||||
|
||||
static int UIEntity_set_opacity(PyUIEntityObject* self, PyObject* value, void* closure)
|
||||
{
|
||||
float opacity;
|
||||
if (PyFloat_Check(value)) {
|
||||
opacity = PyFloat_AsDouble(value);
|
||||
} else if (PyLong_Check(value)) {
|
||||
opacity = PyLong_AsDouble(value);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "opacity must be a number");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Clamp to valid range
|
||||
if (opacity < 0.0f) opacity = 0.0f;
|
||||
if (opacity > 1.0f) opacity = 1.0f;
|
||||
|
||||
self->data->sprite.opacity = opacity;
|
||||
return 0;
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
#include "UIGrid.h"
|
||||
#include "McRFPy_API.h"
|
||||
#include "PyPositionHelper.h"
|
||||
#include "UIDrawable_methods.h"
|
||||
// UIDrawable methods now in UIBase.h
|
||||
|
||||
UIDrawable* UIFrame::click_at(sf::Vector2f point)
|
||||
{
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "McRFPy_API.h"
|
||||
#include "PyPositionHelper.h"
|
||||
#include <algorithm>
|
||||
#include "UIDrawable_methods.h"
|
||||
// UIDrawable methods now in UIBase.h
|
||||
|
||||
UIGrid::UIGrid()
|
||||
: grid_x(0), grid_y(0), zoom(1.0f), center_x(0.0f), center_y(0.0f), ptex(nullptr)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#include "GameEngine.h"
|
||||
#include "PyVector.h"
|
||||
#include "PyPositionHelper.h"
|
||||
#include "UIDrawable_methods.h"
|
||||
// UIDrawable methods now in UIBase.h
|
||||
|
||||
UIDrawable* UISprite::click_at(sf::Vector2f point)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue