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()
|
GameEngine::~GameEngine()
|
||||||
{
|
{
|
||||||
|
cleanup();
|
||||||
for (auto& [name, scene] : scenes) {
|
for (auto& [name, scene] : scenes) {
|
||||||
delete scene;
|
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]; }
|
Scene* GameEngine::currentScene() { return scenes[scene]; }
|
||||||
void GameEngine::changeScene(std::string s)
|
void GameEngine::changeScene(std::string s)
|
||||||
{
|
{
|
||||||
|
@ -162,6 +183,9 @@ void GameEngine::run()
|
||||||
running = false;
|
running = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up before exiting the run loop
|
||||||
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<PyTimerCallable> GameEngine::getTimer(const std::string& name)
|
std::shared_ptr<PyTimerCallable> GameEngine::getTimer(const std::string& name)
|
||||||
|
|
|
@ -28,6 +28,7 @@ class GameEngine
|
||||||
|
|
||||||
bool headless = false;
|
bool headless = false;
|
||||||
McRogueFaceConfig config;
|
McRogueFaceConfig config;
|
||||||
|
bool cleaned_up = false;
|
||||||
|
|
||||||
void testTimers();
|
void testTimers();
|
||||||
|
|
||||||
|
@ -50,6 +51,7 @@ public:
|
||||||
sf::RenderTarget* getRenderTargetPtr() { return render_target; }
|
sf::RenderTarget* getRenderTargetPtr() { return render_target; }
|
||||||
void run();
|
void run();
|
||||||
void sUserInput();
|
void sUserInput();
|
||||||
|
void cleanup(); // Clean up Python references before destruction
|
||||||
int getFrame() { return currentFrame; }
|
int getFrame() { return currentFrame; }
|
||||||
float getFrameTime() { return frameTime; }
|
float getFrameTime() { return frameTime; }
|
||||||
sf::View getView() { return visible; }
|
sf::View getView() { return visible; }
|
||||||
|
|
102
src/UIBase.h
102
src/UIBase.h
|
@ -1,4 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "Python.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class UIEntity;
|
class UIEntity;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -30,3 +32,103 @@ typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
std::shared_ptr<UISprite> data;
|
std::shared_ptr<UISprite> data;
|
||||||
} PyUISpriteObject;
|
} 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 "PyVector.h"
|
||||||
#include "PyFont.h"
|
#include "PyFont.h"
|
||||||
#include "PyPositionHelper.h"
|
#include "PyPositionHelper.h"
|
||||||
#include "UIDrawable_methods.h"
|
// UIDrawable methods now in UIBase.h
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
UICaption::UICaption()
|
UICaption::UICaption()
|
||||||
|
@ -277,7 +277,7 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
||||||
{
|
{
|
||||||
using namespace mcrfpydef;
|
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;
|
float x = 0.0f, y = 0.0f, outline = 0.0f;
|
||||||
char* text = NULL;
|
char* text = NULL;
|
||||||
PyObject* font = NULL;
|
PyObject* font = NULL;
|
||||||
|
@ -286,39 +286,72 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
||||||
PyObject* click_handler = NULL;
|
PyObject* click_handler = NULL;
|
||||||
PyObject* pos_obj = NULL;
|
PyObject* pos_obj = NULL;
|
||||||
|
|
||||||
// Try parsing all arguments with keywords
|
// Handle different argument patterns
|
||||||
if (PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOfOO",
|
Py_ssize_t args_size = PyTuple_Size(args);
|
||||||
const_cast<char**>(keywords),
|
|
||||||
&x, &y, &text, &font, &fill_color, &outline_color, &outline, &click_handler, &pos_obj))
|
if (args_size >= 2 && !PyUnicode_Check(PyTuple_GetItem(args, 0))) {
|
||||||
{
|
// Pattern 1: (x, y, text, ...) or ((x,y), text, ...)
|
||||||
// If pos was provided, it overrides x,y
|
PyObject* first_arg = PyTuple_GetItem(args, 0);
|
||||||
if (pos_obj && pos_obj != Py_None) {
|
|
||||||
PyVectorObject* vec = PyVector::from_arg(pos_obj);
|
// Check if first arg is a tuple/Vector (pos format)
|
||||||
if (!vec) {
|
if (PyTuple_Check(first_arg) || PyObject_IsInstance(first_arg, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector"))) {
|
||||||
PyErr_SetString(PyExc_TypeError, "pos must be a Vector or tuple (x, y)");
|
// 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;
|
return -1;
|
||||||
}
|
}
|
||||||
x = vec->data.x;
|
|
||||||
y = vec->data.y;
|
// 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);
|
||||||
|
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 {
|
else {
|
||||||
PyErr_Clear();
|
// Pattern 2: (text, ...) with x, y as keywords
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|zffOOOfOO",
|
||||||
// Try alternative: first arg is pos tuple/Vector
|
const_cast<char**>(keywords),
|
||||||
static const char* alt_keywords[] = { "pos", "text", "font", "fill_color", "outline_color", "outline", "click", nullptr };
|
&text, &x, &y, &font, &fill_color, &outline_color, &outline, &click_handler, &pos_obj))
|
||||||
PyObject* pos = NULL;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OzOOOfO",
|
|
||||||
const_cast<char**>(alt_keywords),
|
|
||||||
&pos, &text, &font, &fill_color, &outline_color, &outline, &click_handler))
|
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse position
|
// If pos was provided, it overrides x,y
|
||||||
if (pos && pos != Py_None) {
|
if (pos_obj && pos_obj != Py_None) {
|
||||||
PyVectorObject* vec = PyVector::from_arg(pos);
|
PyVectorObject* vec = PyVector::from_arg(pos_obj);
|
||||||
if (!vec) {
|
if (!vec) {
|
||||||
PyErr_SetString(PyExc_TypeError, "pos must be a Vector or tuple (x, y)");
|
PyErr_SetString(PyExc_TypeError, "pos must be a Vector or tuple (x, y)");
|
||||||
return -1;
|
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 "PyObjectUtils.h"
|
||||||
#include "PyVector.h"
|
#include "PyVector.h"
|
||||||
#include "PyPositionHelper.h"
|
#include "PyPositionHelper.h"
|
||||||
#include "UIDrawable_methods.h"
|
// UIDrawable methods now in UIBase.h
|
||||||
|
#include "UIEntityPyMethods.h"
|
||||||
|
|
||||||
|
|
||||||
UIEntity::UIEntity()
|
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},
|
{"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},
|
{"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},
|
{"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 */
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,11 @@ public:
|
||||||
bool setProperty(const std::string& name, int value);
|
bool setProperty(const std::string& name, int value);
|
||||||
bool getProperty(const std::string& name, float& value) const;
|
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* at(PyUIEntityObject* self, PyObject* o);
|
||||||
static PyObject* index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
static PyObject* index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
||||||
static PyObject* die(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 "UIGrid.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PyPositionHelper.h"
|
#include "PyPositionHelper.h"
|
||||||
#include "UIDrawable_methods.h"
|
// UIDrawable methods now in UIBase.h
|
||||||
|
|
||||||
UIDrawable* UIFrame::click_at(sf::Vector2f point)
|
UIDrawable* UIFrame::click_at(sf::Vector2f point)
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PyPositionHelper.h"
|
#include "PyPositionHelper.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include "UIDrawable_methods.h"
|
// UIDrawable methods now in UIBase.h
|
||||||
|
|
||||||
UIGrid::UIGrid()
|
UIGrid::UIGrid()
|
||||||
: grid_x(0), grid_y(0), zoom(1.0f), center_x(0.0f), center_y(0.0f), ptex(nullptr)
|
: 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 "GameEngine.h"
|
||||||
#include "PyVector.h"
|
#include "PyVector.h"
|
||||||
#include "PyPositionHelper.h"
|
#include "PyPositionHelper.h"
|
||||||
#include "UIDrawable_methods.h"
|
// UIDrawable methods now in UIBase.h
|
||||||
|
|
||||||
UIDrawable* UISprite::click_at(sf::Vector2f point)
|
UIDrawable* UISprite::click_at(sf::Vector2f point)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue