Positions are always mcrfpy.Vector, Vector/tuple/iterables expected as inputs, and for position-only inputs we permit x,y args to prevent requiring double-parens
This commit is contained in:
parent
016ca693b5
commit
d2e4791f5a
25 changed files with 2109 additions and 636 deletions
|
|
@ -4,6 +4,7 @@
|
|||
#include "PyColor.h"
|
||||
#include "PyTexture.h"
|
||||
#include "PyFOV.h"
|
||||
#include "PyPositionHelper.h"
|
||||
#include <sstream>
|
||||
|
||||
// =============================================================================
|
||||
|
|
@ -562,10 +563,18 @@ void TileLayer::render(sf::RenderTarget& target,
|
|||
// =============================================================================
|
||||
|
||||
PyMethodDef PyGridLayerAPI::ColorLayer_methods[] = {
|
||||
{"at", (PyCFunction)PyGridLayerAPI::ColorLayer_at, METH_VARARGS,
|
||||
"at(x, y) -> Color\n\nGet the color at cell position (x, y)."},
|
||||
{"at", (PyCFunction)PyGridLayerAPI::ColorLayer_at, METH_VARARGS | METH_KEYWORDS,
|
||||
"at(pos) -> Color\nat(x, y) -> Color\n\n"
|
||||
"Get the color at cell position.\n\n"
|
||||
"Args:\n"
|
||||
" pos: Position as (x, y) tuple, list, or Vector\n"
|
||||
" x, y: Position as separate integer arguments"},
|
||||
{"set", (PyCFunction)PyGridLayerAPI::ColorLayer_set, METH_VARARGS,
|
||||
"set(x, y, color)\n\nSet the color at cell position (x, y)."},
|
||||
"set(pos, color)\n\n"
|
||||
"Set the color at cell position.\n\n"
|
||||
"Args:\n"
|
||||
" pos: Position as (x, y) tuple, list, or Vector\n"
|
||||
" color: Color object or (r, g, b[, a]) tuple"},
|
||||
{"fill", (PyCFunction)PyGridLayerAPI::ColorLayer_fill, METH_VARARGS,
|
||||
"fill(color)\n\nFill the entire layer with the specified color."},
|
||||
{"fill_rect", (PyCFunction)PyGridLayerAPI::ColorLayer_fill_rect, METH_VARARGS | METH_KEYWORDS,
|
||||
|
|
@ -646,9 +655,9 @@ int PyGridLayerAPI::ColorLayer_init(PyColorLayerObject* self, PyObject* args, Py
|
|||
return 0;
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::ColorLayer_at(PyColorLayerObject* self, PyObject* args) {
|
||||
PyObject* PyGridLayerAPI::ColorLayer_at(PyColorLayerObject* self, PyObject* args, PyObject* kwds) {
|
||||
int x, y;
|
||||
if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
|
||||
if (!PyPosition_ParseInt(args, kwds, &x, &y)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -678,9 +687,14 @@ PyObject* PyGridLayerAPI::ColorLayer_at(PyColorLayerObject* self, PyObject* args
|
|||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::ColorLayer_set(PyColorLayerObject* self, PyObject* args) {
|
||||
int x, y;
|
||||
PyObject* pos_obj;
|
||||
PyObject* color_obj;
|
||||
if (!PyArg_ParseTuple(args, "iiO", &x, &y, &color_obj)) {
|
||||
if (!PyArg_ParseTuple(args, "OO", &pos_obj, &color_obj)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int x, y;
|
||||
if (!PyPosition_FromObjectInt(pos_obj, &x, &y)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -1108,10 +1122,18 @@ PyObject* PyGridLayerAPI::ColorLayer_repr(PyColorLayerObject* self) {
|
|||
// =============================================================================
|
||||
|
||||
PyMethodDef PyGridLayerAPI::TileLayer_methods[] = {
|
||||
{"at", (PyCFunction)PyGridLayerAPI::TileLayer_at, METH_VARARGS,
|
||||
"at(x, y) -> int\n\nGet the tile index at cell position (x, y). Returns -1 if no tile."},
|
||||
{"at", (PyCFunction)PyGridLayerAPI::TileLayer_at, METH_VARARGS | METH_KEYWORDS,
|
||||
"at(pos) -> int\nat(x, y) -> int\n\n"
|
||||
"Get the tile index at cell position. Returns -1 if no tile.\n\n"
|
||||
"Args:\n"
|
||||
" pos: Position as (x, y) tuple, list, or Vector\n"
|
||||
" x, y: Position as separate integer arguments"},
|
||||
{"set", (PyCFunction)PyGridLayerAPI::TileLayer_set, METH_VARARGS,
|
||||
"set(x, y, index)\n\nSet the tile index at cell position (x, y). Use -1 for no tile."},
|
||||
"set(pos, index)\n\n"
|
||||
"Set the tile index at cell position. Use -1 for no tile.\n\n"
|
||||
"Args:\n"
|
||||
" pos: Position as (x, y) tuple, list, or Vector\n"
|
||||
" index: Tile index (-1 for no tile)"},
|
||||
{"fill", (PyCFunction)PyGridLayerAPI::TileLayer_fill, METH_VARARGS,
|
||||
"fill(index)\n\nFill the entire layer with the specified tile index."},
|
||||
{"fill_rect", (PyCFunction)PyGridLayerAPI::TileLayer_fill_rect, METH_VARARGS | METH_KEYWORDS,
|
||||
|
|
@ -1190,9 +1212,9 @@ int PyGridLayerAPI::TileLayer_init(PyTileLayerObject* self, PyObject* args, PyOb
|
|||
return 0;
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::TileLayer_at(PyTileLayerObject* self, PyObject* args) {
|
||||
PyObject* PyGridLayerAPI::TileLayer_at(PyTileLayerObject* self, PyObject* args, PyObject* kwds) {
|
||||
int x, y;
|
||||
if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
|
||||
if (!PyPosition_ParseInt(args, kwds, &x, &y)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -1210,8 +1232,14 @@ PyObject* PyGridLayerAPI::TileLayer_at(PyTileLayerObject* self, PyObject* args)
|
|||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::TileLayer_set(PyTileLayerObject* self, PyObject* args) {
|
||||
int x, y, index;
|
||||
if (!PyArg_ParseTuple(args, "iii", &x, &y, &index)) {
|
||||
PyObject* pos_obj;
|
||||
int index;
|
||||
if (!PyArg_ParseTuple(args, "Oi", &pos_obj, &index)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int x, y;
|
||||
if (!PyPosition_FromObjectInt(pos_obj, &x, &y)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ class PyGridLayerAPI {
|
|||
public:
|
||||
// ColorLayer methods
|
||||
static int ColorLayer_init(PyColorLayerObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* ColorLayer_at(PyColorLayerObject* self, PyObject* args);
|
||||
static PyObject* ColorLayer_at(PyColorLayerObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* ColorLayer_set(PyColorLayerObject* self, PyObject* args);
|
||||
static PyObject* ColorLayer_fill(PyColorLayerObject* self, PyObject* args);
|
||||
static PyObject* ColorLayer_fill_rect(PyColorLayerObject* self, PyObject* args, PyObject* kwds);
|
||||
|
|
@ -217,7 +217,7 @@ public:
|
|||
|
||||
// TileLayer methods
|
||||
static int TileLayer_init(PyTileLayerObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* TileLayer_at(PyTileLayerObject* self, PyObject* args);
|
||||
static PyObject* TileLayer_at(PyTileLayerObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* TileLayer_set(PyTileLayerObject* self, PyObject* args);
|
||||
static PyObject* TileLayer_fill(PyTileLayerObject* self, PyObject* args);
|
||||
static PyObject* TileLayer_fill_rect(PyTileLayerObject* self, PyObject* args, PyObject* kwds);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -20,7 +20,7 @@ public:
|
|||
// Mouse position and screen info
|
||||
static PyObject* _position(PyObject* self, PyObject* args);
|
||||
static PyObject* _size(PyObject* self, PyObject* args);
|
||||
static PyObject* _onScreen(PyObject* self, PyObject* args);
|
||||
static PyObject* _onScreen(PyObject* self, PyObject* args, PyObject* kwargs);
|
||||
|
||||
// Mouse movement
|
||||
static PyObject* _moveTo(PyObject* self, PyObject* args, PyObject* kwargs);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "PyCallable.h"
|
||||
#include "McRFPy_API.h"
|
||||
#include "GameEngine.h"
|
||||
#include "PyVector.h"
|
||||
|
||||
PyCallable::PyCallable(PyObject* _target)
|
||||
{
|
||||
|
|
@ -49,7 +50,16 @@ PyClickCallable::PyClickCallable()
|
|||
|
||||
void PyClickCallable::call(sf::Vector2f mousepos, std::string button, std::string action)
|
||||
{
|
||||
PyObject* args = Py_BuildValue("(iiss)", (int)mousepos.x, (int)mousepos.y, button.c_str(), action.c_str());
|
||||
// Create a Vector object for the position
|
||||
PyObject* pos = PyObject_CallFunction((PyObject*)&mcrfpydef::PyVectorType, "ff", mousepos.x, mousepos.y);
|
||||
if (!pos) {
|
||||
std::cerr << "Failed to create Vector object for click callback" << std::endl;
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
return;
|
||||
}
|
||||
PyObject* args = Py_BuildValue("(Oss)", pos, button.c_str(), action.c_str());
|
||||
Py_DECREF(pos); // Py_BuildValue increments the refcount
|
||||
PyObject* retval = PyCallable::call(args, NULL);
|
||||
if (!retval)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
#include "PyDrawable.h"
|
||||
#include "McRFPy_API.h"
|
||||
#include "McRFPy_Doc.h"
|
||||
#include "PyPositionHelper.h"
|
||||
|
||||
// Click property getter
|
||||
static PyObject* PyDrawable_get_click(PyDrawableObject* self, void* closure)
|
||||
{
|
||||
if (!self->data->click_callable)
|
||||
if (!self->data->click_callable)
|
||||
Py_RETURN_NONE;
|
||||
|
||||
|
||||
PyObject* ptr = self->data->click_callable->borrow();
|
||||
if (ptr && ptr != Py_None)
|
||||
return ptr;
|
||||
|
|
@ -35,20 +36,20 @@ static PyObject* PyDrawable_get_z_index(PyDrawableObject* self, void* closure)
|
|||
return PyLong_FromLong(self->data->z_index);
|
||||
}
|
||||
|
||||
// Z-index property setter
|
||||
// Z-index property setter
|
||||
static int PyDrawable_set_z_index(PyDrawableObject* self, PyObject* value, void* closure)
|
||||
{
|
||||
if (!PyLong_Check(value)) {
|
||||
PyErr_SetString(PyExc_TypeError, "z_index must be an integer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int val = PyLong_AsLong(value);
|
||||
self->data->z_index = val;
|
||||
|
||||
|
||||
// Mark scene as needing resort
|
||||
self->data->notifyZIndexChanged();
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +66,7 @@ static int PyDrawable_set_visible(PyDrawableObject* self, PyObject* value, void*
|
|||
PyErr_SetString(PyExc_TypeError, "visible must be a boolean");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
self->data->visible = (value == Py_True);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -88,11 +89,11 @@ static int PyDrawable_set_opacity(PyDrawableObject* self, PyObject* value, void*
|
|||
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;
|
||||
}
|
||||
|
|
@ -102,7 +103,7 @@ static PyGetSetDef PyDrawable_getsetters[] = {
|
|||
{"on_click", (getter)PyDrawable_get_click, (setter)PyDrawable_set_click,
|
||||
MCRF_PROPERTY(on_click,
|
||||
"Callable executed when object is clicked. "
|
||||
"Function receives (x, y) coordinates of click."
|
||||
"Function receives (pos: Vector, button: str, action: str)."
|
||||
), NULL},
|
||||
{"z_index", (getter)PyDrawable_get_z_index, (setter)PyDrawable_set_z_index,
|
||||
MCRF_PROPERTY(z_index,
|
||||
|
|
@ -130,25 +131,25 @@ static PyObject* PyDrawable_get_bounds(PyDrawableObject* self, PyObject* Py_UNUS
|
|||
}
|
||||
|
||||
// move method implementation (#98)
|
||||
static PyObject* PyDrawable_move(PyDrawableObject* self, PyObject* args)
|
||||
static PyObject* PyDrawable_move(PyDrawableObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
float dx, dy;
|
||||
if (!PyArg_ParseTuple(args, "ff", &dx, &dy)) {
|
||||
if (!PyPosition_ParseFloat(args, kwds, &dx, &dy)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
self->data->move(dx, dy);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
// resize method implementation (#98)
|
||||
static PyObject* PyDrawable_resize(PyDrawableObject* self, PyObject* args)
|
||||
static PyObject* PyDrawable_resize(PyDrawableObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
float w, h;
|
||||
if (!PyArg_ParseTuple(args, "ff", &w, &h)) {
|
||||
if (!PyPosition_ParseFloat(args, kwds, &w, &h)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
self->data->resize(w, h);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
|
@ -162,23 +163,27 @@ static PyMethodDef PyDrawable_methods[] = {
|
|||
MCRF_RETURNS("tuple: (x, y, width, height) representing the element's bounds")
|
||||
MCRF_NOTE("The bounds are in screen coordinates and account for current position and size.")
|
||||
)},
|
||||
{"move", (PyCFunction)PyDrawable_move, METH_VARARGS,
|
||||
{"move", (PyCFunction)PyDrawable_move, METH_VARARGS | METH_KEYWORDS,
|
||||
MCRF_METHOD(Drawable, move,
|
||||
MCRF_SIG("(dx: float, dy: float)", "None"),
|
||||
MCRF_SIG("(dx, dy) or (delta)", "None"),
|
||||
MCRF_DESC("Move the element by a relative offset."),
|
||||
MCRF_ARGS_START
|
||||
MCRF_ARG("dx", "Horizontal offset in pixels")
|
||||
MCRF_ARG("dy", "Vertical offset in pixels")
|
||||
MCRF_NOTE("This modifies the x and y position properties by the given amounts.")
|
||||
MCRF_ARG("dx", "Horizontal offset in pixels (or use delta)")
|
||||
MCRF_ARG("dy", "Vertical offset in pixels (or use delta)")
|
||||
MCRF_ARG("delta", "Offset as tuple, list, or Vector: (dx, dy)")
|
||||
MCRF_NOTE("This modifies the x and y position properties by the given amounts. "
|
||||
"Accepts move(dx, dy), move((dx, dy)), move(Vector), or move(pos=(dx, dy)).")
|
||||
)},
|
||||
{"resize", (PyCFunction)PyDrawable_resize, METH_VARARGS,
|
||||
{"resize", (PyCFunction)PyDrawable_resize, METH_VARARGS | METH_KEYWORDS,
|
||||
MCRF_METHOD(Drawable, resize,
|
||||
MCRF_SIG("(width: float, height: float)", "None"),
|
||||
MCRF_SIG("(width, height) or (size)", "None"),
|
||||
MCRF_DESC("Resize the element to new dimensions."),
|
||||
MCRF_ARGS_START
|
||||
MCRF_ARG("width", "New width in pixels")
|
||||
MCRF_ARG("height", "New height in pixels")
|
||||
MCRF_NOTE("For Caption and Sprite, this may not change actual size if determined by content.")
|
||||
MCRF_ARG("width", "New width in pixels (or use size)")
|
||||
MCRF_ARG("height", "New height in pixels (or use size)")
|
||||
MCRF_ARG("size", "Size as tuple, list, or Vector: (width, height)")
|
||||
MCRF_NOTE("For Caption and Sprite, this may not change actual size if determined by content. "
|
||||
"Accepts resize(w, h), resize((w, h)), resize(Vector), or resize(pos=(w, h)).")
|
||||
)},
|
||||
{NULL} // Sentinel
|
||||
};
|
||||
|
|
@ -208,4 +213,4 @@ namespace mcrfpydef {
|
|||
.tp_init = (initproc)PyDrawable_init,
|
||||
.tp_new = PyType_GenericNew,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,32 @@
|
|||
#include "PyVector.h"
|
||||
#include "McRFPy_API.h"
|
||||
|
||||
// Helper class for standardized position argument parsing across UI classes
|
||||
// ============================================================================
|
||||
// PyPositionHelper - Reusable position argument parsing for McRogueFace API
|
||||
// ============================================================================
|
||||
//
|
||||
// This helper provides standardized parsing for position arguments that can be
|
||||
// specified in multiple formats:
|
||||
// - Two separate args: func(x, y)
|
||||
// - A tuple: func((x, y))
|
||||
// - A Vector object: func(Vector(x, y))
|
||||
// - Any iterable with len() == 2: func([x, y])
|
||||
// - Keyword args: func(x=x, y=y) or func(pos=(x,y))
|
||||
//
|
||||
// Usage patterns:
|
||||
// // For methods with only position args (like Grid.at()):
|
||||
// int x, y;
|
||||
// if (!PyPosition_ParseInt(args, kwds, &x, &y)) return NULL;
|
||||
//
|
||||
// // For extracting position from a single PyObject:
|
||||
// float x, y;
|
||||
// if (!PyPosition_FromObject(obj, &x, &y)) return NULL;
|
||||
//
|
||||
// // For more complex parsing with additional args:
|
||||
// auto result = PyPositionHelper::parse_position(args, kwds);
|
||||
// if (!result.has_position) { ... }
|
||||
// ============================================================================
|
||||
|
||||
class PyPositionHelper {
|
||||
public:
|
||||
// Template structure for parsing results
|
||||
|
|
@ -12,33 +37,315 @@ public:
|
|||
float y = 0.0f;
|
||||
bool has_position = false;
|
||||
};
|
||||
|
||||
|
||||
struct ParseResultInt {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
bool has_position = false;
|
||||
};
|
||||
|
||||
|
||||
private:
|
||||
// Internal helper: extract two numeric values from a 2-element iterable
|
||||
// Returns true on success, false on failure (does NOT set Python error)
|
||||
static bool extract_from_iterable(PyObject* obj, float* out_x, float* out_y) {
|
||||
// First check if it's a Vector (most specific)
|
||||
PyTypeObject* vector_type = (PyTypeObject*)PyObject_GetAttrString(
|
||||
McRFPy_API::mcrf_module, "Vector");
|
||||
if (vector_type) {
|
||||
if (PyObject_IsInstance(obj, (PyObject*)vector_type)) {
|
||||
PyVectorObject* vec = (PyVectorObject*)obj;
|
||||
*out_x = vec->data.x;
|
||||
*out_y = vec->data.y;
|
||||
Py_DECREF(vector_type);
|
||||
return true;
|
||||
}
|
||||
Py_DECREF(vector_type);
|
||||
} else {
|
||||
PyErr_Clear(); // Clear any error from GetAttrString
|
||||
}
|
||||
|
||||
// Check for tuple (common case, optimized)
|
||||
if (PyTuple_Check(obj)) {
|
||||
if (PyTuple_Size(obj) != 2) return false;
|
||||
PyObject* x_obj = PyTuple_GetItem(obj, 0);
|
||||
PyObject* y_obj = PyTuple_GetItem(obj, 1);
|
||||
if (!extract_number(x_obj, out_x) || !extract_number(y_obj, out_y)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for list (also common)
|
||||
if (PyList_Check(obj)) {
|
||||
if (PyList_Size(obj) != 2) return false;
|
||||
PyObject* x_obj = PyList_GetItem(obj, 0);
|
||||
PyObject* y_obj = PyList_GetItem(obj, 1);
|
||||
if (!extract_number(x_obj, out_x) || !extract_number(y_obj, out_y)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Generic iterable fallback: check __len__ and index
|
||||
// This handles any object that implements sequence protocol
|
||||
if (PySequence_Check(obj)) {
|
||||
Py_ssize_t len = PySequence_Size(obj);
|
||||
if (len != 2) {
|
||||
PyErr_Clear(); // Clear size error
|
||||
return false;
|
||||
}
|
||||
PyObject* x_obj = PySequence_GetItem(obj, 0);
|
||||
if (!x_obj) { PyErr_Clear(); return false; }
|
||||
PyObject* y_obj = PySequence_GetItem(obj, 1);
|
||||
if (!y_obj) { Py_DECREF(x_obj); PyErr_Clear(); return false; }
|
||||
|
||||
bool success = extract_number(x_obj, out_x) && extract_number(y_obj, out_y);
|
||||
Py_DECREF(x_obj);
|
||||
Py_DECREF(y_obj);
|
||||
return success;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Internal helper: extract integer values from a 2-element iterable
|
||||
static bool extract_from_iterable_int(PyObject* obj, int* out_x, int* out_y) {
|
||||
// First check if it's a Vector
|
||||
PyTypeObject* vector_type = (PyTypeObject*)PyObject_GetAttrString(
|
||||
McRFPy_API::mcrf_module, "Vector");
|
||||
if (vector_type) {
|
||||
if (PyObject_IsInstance(obj, (PyObject*)vector_type)) {
|
||||
PyVectorObject* vec = (PyVectorObject*)obj;
|
||||
*out_x = static_cast<int>(vec->data.x);
|
||||
*out_y = static_cast<int>(vec->data.y);
|
||||
Py_DECREF(vector_type);
|
||||
return true;
|
||||
}
|
||||
Py_DECREF(vector_type);
|
||||
} else {
|
||||
PyErr_Clear();
|
||||
}
|
||||
|
||||
// Check for tuple
|
||||
if (PyTuple_Check(obj)) {
|
||||
if (PyTuple_Size(obj) != 2) return false;
|
||||
PyObject* x_obj = PyTuple_GetItem(obj, 0);
|
||||
PyObject* y_obj = PyTuple_GetItem(obj, 1);
|
||||
if (!extract_int(x_obj, out_x) || !extract_int(y_obj, out_y)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for list
|
||||
if (PyList_Check(obj)) {
|
||||
if (PyList_Size(obj) != 2) return false;
|
||||
PyObject* x_obj = PyList_GetItem(obj, 0);
|
||||
PyObject* y_obj = PyList_GetItem(obj, 1);
|
||||
if (!extract_int(x_obj, out_x) || !extract_int(y_obj, out_y)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Generic sequence fallback
|
||||
if (PySequence_Check(obj)) {
|
||||
Py_ssize_t len = PySequence_Size(obj);
|
||||
if (len != 2) {
|
||||
PyErr_Clear();
|
||||
return false;
|
||||
}
|
||||
PyObject* x_obj = PySequence_GetItem(obj, 0);
|
||||
if (!x_obj) { PyErr_Clear(); return false; }
|
||||
PyObject* y_obj = PySequence_GetItem(obj, 1);
|
||||
if (!y_obj) { Py_DECREF(x_obj); PyErr_Clear(); return false; }
|
||||
|
||||
bool success = extract_int(x_obj, out_x) && extract_int(y_obj, out_y);
|
||||
Py_DECREF(x_obj);
|
||||
Py_DECREF(y_obj);
|
||||
return success;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract a float from a numeric Python object
|
||||
static bool extract_number(PyObject* obj, float* out) {
|
||||
if (PyFloat_Check(obj)) {
|
||||
*out = static_cast<float>(PyFloat_AsDouble(obj));
|
||||
return true;
|
||||
}
|
||||
if (PyLong_Check(obj)) {
|
||||
*out = static_cast<float>(PyLong_AsLong(obj));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract an int from a numeric Python object (integers only)
|
||||
static bool extract_int(PyObject* obj, int* out) {
|
||||
if (PyLong_Check(obj)) {
|
||||
*out = static_cast<int>(PyLong_AsLong(obj));
|
||||
return true;
|
||||
}
|
||||
// Also accept float but only if it's a whole number
|
||||
if (PyFloat_Check(obj)) {
|
||||
double val = PyFloat_AsDouble(obj);
|
||||
if (val == static_cast<double>(static_cast<int>(val))) {
|
||||
*out = static_cast<int>(val);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
// ========================================================================
|
||||
// Simple API: Parse position from a single PyObject
|
||||
// ========================================================================
|
||||
|
||||
// Extract float position from any supported format
|
||||
// Sets Python error and returns false on failure
|
||||
static bool FromObject(PyObject* obj, float* out_x, float* out_y) {
|
||||
if (extract_from_iterable(obj, out_x, out_y)) {
|
||||
return true;
|
||||
}
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"Expected a position as (x, y) tuple, [x, y] list, Vector, or other 2-element sequence");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract integer position from any supported format
|
||||
// Sets Python error and returns false on failure
|
||||
static bool FromObjectInt(PyObject* obj, int* out_x, int* out_y) {
|
||||
if (extract_from_iterable_int(obj, out_x, out_y)) {
|
||||
return true;
|
||||
}
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"Expected integer position as (x, y) tuple, [x, y] list, Vector, or other 2-element sequence");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Method argument API: Parse position from args tuple
|
||||
// ========================================================================
|
||||
|
||||
// Parse float position from method arguments
|
||||
// Supports: func(x, y) or func((x, y)) or func(Vector) or func(iterable)
|
||||
// Sets Python error and returns false on failure
|
||||
static bool ParseFloat(PyObject* args, PyObject* kwds, float* out_x, float* out_y) {
|
||||
// First try keyword arguments
|
||||
if (kwds) {
|
||||
PyObject* x_obj = PyDict_GetItemString(kwds, "x");
|
||||
PyObject* y_obj = PyDict_GetItemString(kwds, "y");
|
||||
PyObject* pos_obj = PyDict_GetItemString(kwds, "pos");
|
||||
|
||||
if (x_obj && y_obj) {
|
||||
if (extract_number(x_obj, out_x) && extract_number(y_obj, out_y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos_obj) {
|
||||
if (extract_from_iterable(pos_obj, out_x, out_y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Py_ssize_t nargs = PyTuple_Size(args);
|
||||
|
||||
// Try two separate numeric arguments: func(x, y)
|
||||
if (nargs >= 2) {
|
||||
PyObject* first = PyTuple_GetItem(args, 0);
|
||||
PyObject* second = PyTuple_GetItem(args, 1);
|
||||
|
||||
if (extract_number(first, out_x) && extract_number(second, out_y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Try single iterable argument: func((x, y)) or func(Vector) or func([x, y])
|
||||
if (nargs == 1) {
|
||||
PyObject* first = PyTuple_GetItem(args, 0);
|
||||
if (extract_from_iterable(first, out_x, out_y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"Position can be specified as: (x, y), ((x,y)), pos=(x,y), Vector, or 2-element sequence");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse integer position from method arguments
|
||||
// Supports: func(x, y) or func((x, y)) or func(Vector) or func(iterable)
|
||||
// Sets Python error and returns false on failure
|
||||
static bool ParseInt(PyObject* args, PyObject* kwds, int* out_x, int* out_y) {
|
||||
// First try keyword arguments
|
||||
if (kwds) {
|
||||
PyObject* x_obj = PyDict_GetItemString(kwds, "x");
|
||||
PyObject* y_obj = PyDict_GetItemString(kwds, "y");
|
||||
PyObject* pos_obj = PyDict_GetItemString(kwds, "pos");
|
||||
|
||||
if (x_obj && y_obj) {
|
||||
if (extract_int(x_obj, out_x) && extract_int(y_obj, out_y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos_obj) {
|
||||
if (extract_from_iterable_int(pos_obj, out_x, out_y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Py_ssize_t nargs = PyTuple_Size(args);
|
||||
|
||||
// Try two separate integer arguments: func(x, y)
|
||||
if (nargs >= 2) {
|
||||
PyObject* first = PyTuple_GetItem(args, 0);
|
||||
PyObject* second = PyTuple_GetItem(args, 1);
|
||||
|
||||
if (extract_int(first, out_x) && extract_int(second, out_y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Try single iterable argument: func((x, y)) or func(Vector) or func([x, y])
|
||||
if (nargs == 1) {
|
||||
PyObject* first = PyTuple_GetItem(args, 0);
|
||||
if (extract_from_iterable_int(first, out_x, out_y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"Position must be integers specified as: (x, y), ((x,y)), pos=(x,y), Vector, or 2-element sequence");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Legacy struct-based API (for compatibility with existing code)
|
||||
// ========================================================================
|
||||
|
||||
// Parse position from multiple formats for UI class constructors
|
||||
// Supports: (x, y), x=x, y=y, ((x,y)), (pos=(x,y)), (Vector), pos=Vector
|
||||
static ParseResult parse_position(PyObject* args, PyObject* kwds,
|
||||
int* arg_index = nullptr)
|
||||
static ParseResult parse_position(PyObject* args, PyObject* kwds,
|
||||
int* arg_index = nullptr)
|
||||
{
|
||||
ParseResult result;
|
||||
float x = 0.0f, y = 0.0f;
|
||||
PyObject* pos_obj = nullptr;
|
||||
int start_index = arg_index ? *arg_index : 0;
|
||||
|
||||
|
||||
// Check for positional tuple (x, y) first
|
||||
if (!kwds && PyTuple_Size(args) > start_index + 1) {
|
||||
if (PyTuple_Size(args) > start_index + 1) {
|
||||
PyObject* first = PyTuple_GetItem(args, start_index);
|
||||
PyObject* second = PyTuple_GetItem(args, start_index + 1);
|
||||
|
||||
|
||||
// Check if both are numbers
|
||||
if ((PyFloat_Check(first) || PyLong_Check(first)) &&
|
||||
(PyFloat_Check(second) || PyLong_Check(second))) {
|
||||
x = PyFloat_Check(first) ? PyFloat_AsDouble(first) : PyLong_AsLong(first);
|
||||
y = PyFloat_Check(second) ? PyFloat_AsDouble(second) : PyLong_AsLong(second);
|
||||
if (extract_number(first, &x) && extract_number(second, &y)) {
|
||||
result.x = x;
|
||||
result.y = y;
|
||||
result.has_position = true;
|
||||
|
|
@ -46,119 +353,100 @@ public:
|
|||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for single positional argument that might be tuple or Vector
|
||||
if (!kwds && PyTuple_Size(args) > start_index) {
|
||||
|
||||
// Check for single positional argument that might be tuple, list, or Vector
|
||||
if (PyTuple_Size(args) > start_index) {
|
||||
PyObject* first = PyTuple_GetItem(args, start_index);
|
||||
PyVectorObject* vec = PyVector::from_arg(first);
|
||||
if (vec) {
|
||||
result.x = vec->data.x;
|
||||
result.y = vec->data.y;
|
||||
if (extract_from_iterable(first, &x, &y)) {
|
||||
result.x = x;
|
||||
result.y = y;
|
||||
result.has_position = true;
|
||||
if (arg_index) *arg_index += 1;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Try keyword arguments
|
||||
if (kwds) {
|
||||
PyObject* x_obj = PyDict_GetItemString(kwds, "x");
|
||||
PyObject* y_obj = PyDict_GetItemString(kwds, "y");
|
||||
PyObject* pos_kw = PyDict_GetItemString(kwds, "pos");
|
||||
|
||||
|
||||
if (x_obj && y_obj) {
|
||||
if ((PyFloat_Check(x_obj) || PyLong_Check(x_obj)) &&
|
||||
(PyFloat_Check(y_obj) || PyLong_Check(y_obj))) {
|
||||
result.x = PyFloat_Check(x_obj) ? PyFloat_AsDouble(x_obj) : PyLong_AsLong(x_obj);
|
||||
result.y = PyFloat_Check(y_obj) ? PyFloat_AsDouble(y_obj) : PyLong_AsLong(y_obj);
|
||||
if (extract_number(x_obj, &x) && extract_number(y_obj, &y)) {
|
||||
result.x = x;
|
||||
result.y = y;
|
||||
result.has_position = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (pos_kw) {
|
||||
PyVectorObject* vec = PyVector::from_arg(pos_kw);
|
||||
if (vec) {
|
||||
result.x = vec->data.x;
|
||||
result.y = vec->data.y;
|
||||
if (extract_from_iterable(pos_kw, &x, &y)) {
|
||||
result.x = x;
|
||||
result.y = y;
|
||||
result.has_position = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Parse integer position for Grid.at() and similar
|
||||
static ParseResultInt parse_position_int(PyObject* args, PyObject* kwds)
|
||||
static ParseResultInt parse_position_int(PyObject* args, PyObject* kwds)
|
||||
{
|
||||
ParseResultInt result;
|
||||
|
||||
// Check for positional tuple (x, y) first
|
||||
if (!kwds && PyTuple_Size(args) >= 2) {
|
||||
PyObject* first = PyTuple_GetItem(args, 0);
|
||||
PyObject* second = PyTuple_GetItem(args, 1);
|
||||
|
||||
if (PyLong_Check(first) && PyLong_Check(second)) {
|
||||
result.x = PyLong_AsLong(first);
|
||||
result.y = PyLong_AsLong(second);
|
||||
result.has_position = true;
|
||||
return result;
|
||||
}
|
||||
int x = 0, y = 0;
|
||||
|
||||
// Try the new simplified parser first
|
||||
if (ParseInt(args, kwds, &x, &y)) {
|
||||
result.x = x;
|
||||
result.y = y;
|
||||
result.has_position = true;
|
||||
PyErr_Clear(); // Clear any error set by ParseInt
|
||||
}
|
||||
|
||||
// Check for single tuple argument
|
||||
if (!kwds && PyTuple_Size(args) == 1) {
|
||||
PyObject* first = PyTuple_GetItem(args, 0);
|
||||
if (PyTuple_Check(first) && PyTuple_Size(first) == 2) {
|
||||
PyObject* x_obj = PyTuple_GetItem(first, 0);
|
||||
PyObject* y_obj = PyTuple_GetItem(first, 1);
|
||||
if (PyLong_Check(x_obj) && PyLong_Check(y_obj)) {
|
||||
result.x = PyLong_AsLong(x_obj);
|
||||
result.y = PyLong_AsLong(y_obj);
|
||||
result.has_position = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try keyword arguments
|
||||
if (kwds) {
|
||||
PyObject* x_obj = PyDict_GetItemString(kwds, "x");
|
||||
PyObject* y_obj = PyDict_GetItemString(kwds, "y");
|
||||
PyObject* pos_obj = PyDict_GetItemString(kwds, "pos");
|
||||
|
||||
if (x_obj && y_obj && PyLong_Check(x_obj) && PyLong_Check(y_obj)) {
|
||||
result.x = PyLong_AsLong(x_obj);
|
||||
result.y = PyLong_AsLong(y_obj);
|
||||
result.has_position = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (pos_obj && PyTuple_Check(pos_obj) && PyTuple_Size(pos_obj) == 2) {
|
||||
PyObject* x_val = PyTuple_GetItem(pos_obj, 0);
|
||||
PyObject* y_val = PyTuple_GetItem(pos_obj, 1);
|
||||
if (PyLong_Check(x_val) && PyLong_Check(y_val)) {
|
||||
result.x = PyLong_AsLong(x_val);
|
||||
result.y = PyLong_AsLong(y_val);
|
||||
result.has_position = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Error message helper
|
||||
static void set_position_error() {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"Position can be specified as: (x, y), x=x, y=y, ((x,y)), pos=(x,y), or pos=Vector");
|
||||
"Position can be specified as: (x, y), x=x, y=y, ((x,y)), pos=(x,y), Vector, or 2-element sequence");
|
||||
}
|
||||
|
||||
|
||||
static void set_position_int_error() {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"Position must be specified as: (x, y), x=x, y=y, ((x,y)), or pos=(x,y) with integer values");
|
||||
"Position must be integers specified as: (x, y), x=x, y=y, ((x,y)), pos=(x,y), Vector, or 2-element sequence");
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Convenience macros/functions for common use patterns
|
||||
// ============================================================================
|
||||
|
||||
// Parse integer position from method args - simplest API
|
||||
// Usage: if (!PyPosition_ParseInt(args, kwds, &x, &y)) return NULL;
|
||||
inline bool PyPosition_ParseInt(PyObject* args, PyObject* kwds, int* x, int* y) {
|
||||
return PyPositionHelper::ParseInt(args, kwds, x, y);
|
||||
}
|
||||
|
||||
// Parse float position from method args
|
||||
// Usage: if (!PyPosition_ParseFloat(args, kwds, &x, &y)) return NULL;
|
||||
inline bool PyPosition_ParseFloat(PyObject* args, PyObject* kwds, float* x, float* y) {
|
||||
return PyPositionHelper::ParseFloat(args, kwds, x, y);
|
||||
}
|
||||
|
||||
// Extract integer position from a single Python object
|
||||
// Usage: if (!PyPosition_FromObjectInt(obj, &x, &y)) return NULL;
|
||||
inline bool PyPosition_FromObjectInt(PyObject* obj, int* x, int* y) {
|
||||
return PyPositionHelper::FromObjectInt(obj, x, y);
|
||||
}
|
||||
|
||||
// Extract float position from a single Python object
|
||||
// Usage: if (!PyPosition_FromObject(obj, &x, &y)) return NULL;
|
||||
inline bool PyPosition_FromObject(PyObject* obj, float* x, float* y) {
|
||||
return PyPositionHelper::FromObject(obj, x, y);
|
||||
}
|
||||
|
|
@ -430,7 +430,7 @@ PyGetSetDef UIArc::getsetters[] = {
|
|||
{"thickness", (getter)UIArc::get_thickness, (setter)UIArc::set_thickness,
|
||||
"Line thickness", NULL},
|
||||
{"on_click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
||||
"Callable executed when arc is clicked.", (void*)PyObjectsEnum::UIARC},
|
||||
"Callable executed when arc is clicked. Function receives (pos: Vector, button: str, action: str).", (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,
|
||||
|
|
|
|||
39
src/UIBase.h
39
src/UIBase.h
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
#include "Python.h"
|
||||
#include "McRFPy_Doc.h"
|
||||
#include "PyPositionHelper.h"
|
||||
#include <memory>
|
||||
|
||||
class UIEntity;
|
||||
|
|
@ -52,23 +53,23 @@ static PyObject* UIDrawable_get_bounds(T* self, PyObject* Py_UNUSED(args))
|
|||
|
||||
// move method implementation (#98)
|
||||
template<typename T>
|
||||
static PyObject* UIDrawable_move(T* self, PyObject* args)
|
||||
static PyObject* UIDrawable_move(T* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
float dx, dy;
|
||||
if (!PyArg_ParseTuple(args, "ff", &dx, &dy)) {
|
||||
if (!PyPosition_ParseFloat(args, kwds, &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)
|
||||
static PyObject* UIDrawable_resize(T* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
float w, h;
|
||||
if (!PyArg_ParseTuple(args, "ff", &w, &h)) {
|
||||
if (!PyPosition_ParseFloat(args, kwds, &w, &h)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -97,23 +98,25 @@ static PyObject* UIDrawable_animate(T* self, PyObject* args, PyObject* kwds)
|
|||
MCRF_RETURNS("tuple: (x, y, width, height) representing the element's bounds") \
|
||||
MCRF_NOTE("The bounds are in screen coordinates and account for current position and size.") \
|
||||
)}, \
|
||||
{"move", (PyCFunction)UIDrawable_move<PyObjectType>, METH_VARARGS, \
|
||||
{"move", (PyCFunction)UIDrawable_move<PyObjectType>, METH_VARARGS | METH_KEYWORDS, \
|
||||
MCRF_METHOD(Drawable, move, \
|
||||
MCRF_SIG("(dx: float, dy: float)", "None"), \
|
||||
MCRF_SIG("(dx, dy) or (delta)", "None"), \
|
||||
MCRF_DESC("Move the element by a relative offset."), \
|
||||
MCRF_ARGS_START \
|
||||
MCRF_ARG("dx", "Horizontal offset in pixels") \
|
||||
MCRF_ARG("dy", "Vertical offset in pixels") \
|
||||
MCRF_NOTE("This modifies the x and y position properties by the given amounts.") \
|
||||
MCRF_ARG("dx", "Horizontal offset in pixels (or use delta)") \
|
||||
MCRF_ARG("dy", "Vertical offset in pixels (or use delta)") \
|
||||
MCRF_ARG("delta", "Offset as tuple, list, or Vector: (dx, dy)") \
|
||||
MCRF_NOTE("Accepts move(dx, dy), move((dx, dy)), move(Vector), or move(pos=(dx, dy)).") \
|
||||
)}, \
|
||||
{"resize", (PyCFunction)UIDrawable_resize<PyObjectType>, METH_VARARGS, \
|
||||
{"resize", (PyCFunction)UIDrawable_resize<PyObjectType>, METH_VARARGS | METH_KEYWORDS, \
|
||||
MCRF_METHOD(Drawable, resize, \
|
||||
MCRF_SIG("(width: float, height: float)", "None"), \
|
||||
MCRF_SIG("(width, height) or (size)", "None"), \
|
||||
MCRF_DESC("Resize the element to new dimensions."), \
|
||||
MCRF_ARGS_START \
|
||||
MCRF_ARG("width", "New width in pixels") \
|
||||
MCRF_ARG("height", "New height in pixels") \
|
||||
MCRF_NOTE("For Caption and Sprite, this may not change actual size if determined by content.") \
|
||||
MCRF_ARG("width", "New width in pixels (or use size)") \
|
||||
MCRF_ARG("height", "New height in pixels (or use size)") \
|
||||
MCRF_ARG("size", "Size as tuple, list, or Vector: (width, height)") \
|
||||
MCRF_NOTE("Accepts resize(w, h), resize((w, h)), resize(Vector), or resize(pos=(w, h)).") \
|
||||
)}
|
||||
|
||||
// Macro to add common UIDrawable methods to a method array (includes animate for UIDrawable derivatives)
|
||||
|
|
@ -222,12 +225,12 @@ static int UIDrawable_set_opacity(T* self, PyObject* value, void* closure)
|
|||
{"on_enter", (getter)UIDrawable::get_on_enter, (setter)UIDrawable::set_on_enter, \
|
||||
MCRF_PROPERTY(on_enter, \
|
||||
"Callback for mouse enter events. " \
|
||||
"Called with (x, y, button, action) when mouse enters this element's bounds." \
|
||||
"Called with (pos: Vector, button: str, action: str) when mouse enters this element's bounds." \
|
||||
), (void*)type_enum}, \
|
||||
{"on_exit", (getter)UIDrawable::get_on_exit, (setter)UIDrawable::set_on_exit, \
|
||||
MCRF_PROPERTY(on_exit, \
|
||||
"Callback for mouse exit events. " \
|
||||
"Called with (x, y, button, action) when mouse leaves this element's bounds." \
|
||||
"Called with (pos: Vector, button: str, action: str) when mouse leaves this element's bounds." \
|
||||
), (void*)type_enum}, \
|
||||
{"hovered", (getter)UIDrawable::get_hovered, NULL, \
|
||||
MCRF_PROPERTY(hovered, \
|
||||
|
|
@ -237,7 +240,7 @@ static int UIDrawable_set_opacity(T* self, PyObject* value, void* closure)
|
|||
{"on_move", (getter)UIDrawable::get_on_move, (setter)UIDrawable::set_on_move, \
|
||||
MCRF_PROPERTY(on_move, \
|
||||
"Callback for mouse movement within bounds. " \
|
||||
"Called with (x, y, button, action) for each mouse movement while inside. " \
|
||||
"Called with (pos: Vector, button: str, action: str) for each mouse movement while inside. " \
|
||||
"Performance note: Called frequently during movement - keep handlers fast." \
|
||||
), (void*)type_enum}
|
||||
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ PyGetSetDef UICaption::getsetters[] = {
|
|||
{"on_click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
||||
MCRF_PROPERTY(on_click,
|
||||
"Callable executed when object is clicked. "
|
||||
"Function receives (x, y) coordinates of click."
|
||||
"Function receives (pos: Vector, button: str, action: str)."
|
||||
), (void*)PyObjectsEnum::UICAPTION},
|
||||
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int,
|
||||
MCRF_PROPERTY(z_index,
|
||||
|
|
|
|||
|
|
@ -385,7 +385,7 @@ PyGetSetDef UICircle::getsetters[] = {
|
|||
{"outline", (getter)UICircle::get_outline, (setter)UICircle::set_outline,
|
||||
"Outline thickness (0 for no outline)", NULL},
|
||||
{"on_click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
||||
"Callable executed when circle is clicked.", (void*)PyObjectsEnum::UICIRCLE},
|
||||
"Callable executed when circle is clicked. Function receives (pos: Vector, button: str, action: str).", (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,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include "Animation.h"
|
||||
#include "PyAnimation.h"
|
||||
#include "PyEasing.h"
|
||||
#include "PyPositionHelper.h"
|
||||
// UIDrawable methods now in UIBase.h
|
||||
#include "UIEntityPyMethods.h"
|
||||
|
||||
|
|
@ -94,18 +95,17 @@ void UIEntity::updateVisibility()
|
|||
}
|
||||
}
|
||||
|
||||
PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* o) {
|
||||
PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
||||
int x, y;
|
||||
if (!PyArg_ParseTuple(o, "ii", &x, &y)) {
|
||||
PyErr_SetString(PyExc_TypeError, "UIEntity.at requires two integer arguments: (x, y)");
|
||||
return NULL;
|
||||
if (!PyPosition_ParseInt(args, kwds, &x, &y)) {
|
||||
return NULL; // Error already set by PyPosition_ParseInt
|
||||
}
|
||||
|
||||
|
||||
if (self->data->grid == NULL) {
|
||||
PyErr_SetString(PyExc_ValueError, "Entity cannot access surroundings because it is not associated with a grid");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// Lazy initialize gridstate if needed
|
||||
if (self->data->gridstate.size() == 0) {
|
||||
self->data->gridstate.resize(self->data->grid->grid_x * self->data->grid->grid_y);
|
||||
|
|
@ -115,13 +115,13 @@ PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* o) {
|
|||
state.discovered = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Bounds check
|
||||
if (x < 0 || x >= self->data->grid->grid_x || y < 0 || y >= self->data->grid->grid_y) {
|
||||
PyErr_Format(PyExc_IndexError, "Grid coordinates (%d, %d) out of bounds", x, y);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState");
|
||||
auto obj = (PyUIGridPointStateObject*)type->tp_alloc(type, 0);
|
||||
Py_DECREF(type);
|
||||
|
|
@ -590,21 +590,14 @@ PyObject* UIEntity::die(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored))
|
|||
}
|
||||
|
||||
PyObject* UIEntity::path_to(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
||||
static const char* keywords[] = {"target_x", "target_y", "x", "y", nullptr};
|
||||
int target_x = -1, target_y = -1;
|
||||
|
||||
// Parse arguments - support both target_x/target_y and x/y parameter names
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii", const_cast<char**>(keywords),
|
||||
&target_x, &target_y)) {
|
||||
PyErr_Clear();
|
||||
// Try alternative parameter names
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiii", const_cast<char**>(keywords),
|
||||
&target_x, &target_y, &target_x, &target_y)) {
|
||||
PyErr_SetString(PyExc_TypeError, "path_to() requires target_x and target_y integer arguments");
|
||||
return NULL;
|
||||
}
|
||||
int target_x, target_y;
|
||||
|
||||
// Parse position using flexible position helper
|
||||
// Supports: path_to(x, y), path_to((x, y)), path_to(pos=(x, y)), path_to(Vector(x, y))
|
||||
if (!PyPosition_ParseInt(args, kwds, &target_x, &target_y)) {
|
||||
return NULL; // Error already set by PyPosition_ParseInt
|
||||
}
|
||||
|
||||
|
||||
// Check if entity has a grid
|
||||
if (!self->data || !self->data->grid) {
|
||||
PyErr_SetString(PyExc_ValueError, "Entity must be associated with a grid to compute paths");
|
||||
|
|
@ -743,19 +736,32 @@ PyObject* UIEntity::visible_entities(PyUIEntityObject* self, PyObject* args, PyO
|
|||
}
|
||||
|
||||
PyMethodDef UIEntity::methods[] = {
|
||||
{"at", (PyCFunction)UIEntity::at, METH_O},
|
||||
{"at", (PyCFunction)UIEntity::at, METH_VARARGS | METH_KEYWORDS,
|
||||
"at(x, y) or at(pos) -> GridPointState\n\n"
|
||||
"Get the grid point state at the specified position.\n\n"
|
||||
"Args:\n"
|
||||
" x, y: Grid coordinates as two integers, OR\n"
|
||||
" pos: Grid coordinates as tuple, list, or Vector\n\n"
|
||||
"Returns:\n"
|
||||
" GridPointState for the entity's view of that grid cell.\n\n"
|
||||
"Example:\n"
|
||||
" state = entity.at(5, 3)\n"
|
||||
" state = entity.at((5, 3))\n"
|
||||
" state = entity.at(pos=(5, 3))"},
|
||||
{"index", (PyCFunction)UIEntity::index, METH_NOARGS, "Return the index of this entity in its grid's entity collection"},
|
||||
{"die", (PyCFunction)UIEntity::die, METH_NOARGS, "Remove this entity from its grid"},
|
||||
{"path_to", (PyCFunction)UIEntity::path_to, METH_VARARGS | METH_KEYWORDS,
|
||||
"path_to(x: int, y: int) -> bool\n\n"
|
||||
"Find and follow path to target position using A* pathfinding.\n\n"
|
||||
"path_to(x, y) or path_to(target) -> list\n\n"
|
||||
"Find a path to the target position using Dijkstra pathfinding.\n\n"
|
||||
"Args:\n"
|
||||
" x: Target X coordinate\n"
|
||||
" y: Target Y coordinate\n\n"
|
||||
" x, y: Target coordinates as two integers, OR\n"
|
||||
" target: Target coordinates as tuple, list, or Vector\n\n"
|
||||
"Returns:\n"
|
||||
" True if a path was found and the entity started moving, False otherwise\n\n"
|
||||
"The entity will automatically move along the path over multiple frames.\n"
|
||||
"Call this again to change the target or repath."},
|
||||
" List of (x, y) tuples representing the path.\n\n"
|
||||
"Example:\n"
|
||||
" path = entity.path_to(10, 5)\n"
|
||||
" path = entity.path_to((10, 5))\n"
|
||||
" path = entity.path_to(pos=(10, 5))"},
|
||||
{"update_visibility", (PyCFunction)UIEntity::update_visibility, METH_NOARGS,
|
||||
"update_visibility() -> None\n\n"
|
||||
"Update entity's visibility state based on current FOV.\n\n"
|
||||
|
|
@ -799,19 +805,32 @@ PyMethodDef UIEntity_all_methods[] = {
|
|||
MCRF_RAISES("ValueError", "If property name is not valid for Entity (x, y, sprite_scale, sprite_index)")
|
||||
MCRF_NOTE("Entity animations use grid coordinates for x/y, not pixel coordinates.")
|
||||
)},
|
||||
{"at", (PyCFunction)UIEntity::at, METH_O},
|
||||
{"at", (PyCFunction)UIEntity::at, METH_VARARGS | METH_KEYWORDS,
|
||||
"at(x, y) or at(pos) -> GridPointState\n\n"
|
||||
"Get the grid point state at the specified position.\n\n"
|
||||
"Args:\n"
|
||||
" x, y: Grid coordinates as two integers, OR\n"
|
||||
" pos: Grid coordinates as tuple, list, or Vector\n\n"
|
||||
"Returns:\n"
|
||||
" GridPointState for the entity's view of that grid cell.\n\n"
|
||||
"Example:\n"
|
||||
" state = entity.at(5, 3)\n"
|
||||
" state = entity.at((5, 3))\n"
|
||||
" state = entity.at(pos=(5, 3))"},
|
||||
{"index", (PyCFunction)UIEntity::index, METH_NOARGS, "Return the index of this entity in its grid's entity collection"},
|
||||
{"die", (PyCFunction)UIEntity::die, METH_NOARGS, "Remove this entity from its grid"},
|
||||
{"path_to", (PyCFunction)UIEntity::path_to, METH_VARARGS | METH_KEYWORDS,
|
||||
"path_to(x: int, y: int) -> bool\n\n"
|
||||
"Find and follow path to target position using A* pathfinding.\n\n"
|
||||
"path_to(x, y) or path_to(target) -> list\n\n"
|
||||
"Find a path to the target position using Dijkstra pathfinding.\n\n"
|
||||
"Args:\n"
|
||||
" x: Target X coordinate\n"
|
||||
" y: Target Y coordinate\n\n"
|
||||
" x, y: Target coordinates as two integers, OR\n"
|
||||
" target: Target coordinates as tuple, list, or Vector\n\n"
|
||||
"Returns:\n"
|
||||
" True if a path was found and the entity started moving, False otherwise\n\n"
|
||||
"The entity will automatically move along the path over multiple frames.\n"
|
||||
"Call this again to change the target or repath."},
|
||||
" List of (x, y) tuples representing the path.\n\n"
|
||||
"Example:\n"
|
||||
" path = entity.path_to(10, 5)\n"
|
||||
" path = entity.path_to((10, 5))\n"
|
||||
" path = entity.path_to(pos=(10, 5))"},
|
||||
{"update_visibility", (PyCFunction)UIEntity::update_visibility, METH_NOARGS,
|
||||
"update_visibility() -> None\n\n"
|
||||
"Update entity's visibility state based on current FOV.\n\n"
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ public:
|
|||
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* args, PyObject* kwds);
|
||||
static PyObject* index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
||||
static PyObject* die(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
||||
static PyObject* path_to(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
||||
|
|
|
|||
|
|
@ -444,7 +444,7 @@ PyGetSetDef UIFrame::getsetters[] = {
|
|||
{"on_click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
||||
MCRF_PROPERTY(on_click,
|
||||
"Callable executed when object is clicked. "
|
||||
"Function receives (x, y) coordinates of click."
|
||||
"Function receives (pos: Vector, button: str, action: str)."
|
||||
), (void*)PyObjectsEnum::UIFRAME},
|
||||
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int,
|
||||
MCRF_PROPERTY(z_index,
|
||||
|
|
|
|||
355
src/UIGrid.cpp
355
src/UIGrid.cpp
|
|
@ -5,6 +5,7 @@
|
|||
#include "UIEntity.h"
|
||||
#include "Profiler.h"
|
||||
#include "PyFOV.h"
|
||||
#include "PyPositionHelper.h" // For standardized position argument parsing
|
||||
#include <algorithm>
|
||||
#include <cmath> // #142 - for std::floor, std::isnan
|
||||
#include <cstring> // #150 - for strcmp
|
||||
|
|
@ -685,15 +686,20 @@ UIDrawable* UIGrid::click_at(sf::Vector2f point)
|
|||
|
||||
// Only fire if within valid grid bounds
|
||||
if (cell_x >= 0 && cell_x < this->grid_x && cell_y >= 0 && cell_y < this->grid_y) {
|
||||
PyObject* args = Py_BuildValue("(ii)", cell_x, cell_y);
|
||||
PyObject* result = PyObject_CallObject(on_cell_click_callable->borrow(), args);
|
||||
Py_DECREF(args);
|
||||
if (!result) {
|
||||
std::cerr << "Cell click callback raised an exception:" << std::endl;
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
Py_DECREF(result);
|
||||
// Create Vector object for cell position
|
||||
PyObject* cell_pos = PyObject_CallFunction((PyObject*)&mcrfpydef::PyVectorType, "ff", (float)cell_x, (float)cell_y);
|
||||
if (cell_pos) {
|
||||
PyObject* args = Py_BuildValue("(O)", cell_pos);
|
||||
Py_DECREF(cell_pos);
|
||||
PyObject* result = PyObject_CallObject(on_cell_click_callable->borrow(), args);
|
||||
Py_DECREF(args);
|
||||
if (!result) {
|
||||
std::cerr << "Cell click callback raised an exception:" << std::endl;
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
Py_DECREF(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -709,15 +715,20 @@ UIDrawable* UIGrid::click_at(sf::Vector2f point)
|
|||
|
||||
// Only fire if within valid grid bounds
|
||||
if (cell_x >= 0 && cell_x < this->grid_x && cell_y >= 0 && cell_y < this->grid_y) {
|
||||
PyObject* args = Py_BuildValue("(ii)", cell_x, cell_y);
|
||||
PyObject* result = PyObject_CallObject(on_cell_click_callable->borrow(), args);
|
||||
Py_DECREF(args);
|
||||
if (!result) {
|
||||
std::cerr << "Cell click callback raised an exception:" << std::endl;
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
Py_DECREF(result);
|
||||
// Create Vector object for cell position
|
||||
PyObject* cell_pos = PyObject_CallFunction((PyObject*)&mcrfpydef::PyVectorType, "ff", (float)cell_x, (float)cell_y);
|
||||
if (cell_pos) {
|
||||
PyObject* args = Py_BuildValue("(O)", cell_pos);
|
||||
Py_DECREF(cell_pos);
|
||||
PyObject* result = PyObject_CallObject(on_cell_click_callable->borrow(), args);
|
||||
Py_DECREF(args);
|
||||
if (!result) {
|
||||
std::cerr << "Cell click callback raised an exception:" << std::endl;
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
Py_DECREF(result);
|
||||
}
|
||||
}
|
||||
// Don't return this - no click_callable to call
|
||||
}
|
||||
|
|
@ -1141,36 +1152,14 @@ PyObject* UIGrid::get_texture(PyUIGridObject* self, void* closure) {
|
|||
|
||||
PyObject* UIGrid::py_at(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
static const char* keywords[] = {"x", "y", nullptr};
|
||||
int x = 0, y = 0;
|
||||
|
||||
// First try to parse as two integers
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii", const_cast<char**>(keywords), &x, &y)) {
|
||||
PyErr_Clear();
|
||||
|
||||
// Try to parse as a single tuple argument
|
||||
PyObject* pos_tuple = nullptr;
|
||||
if (PyArg_ParseTuple(args, "O", &pos_tuple)) {
|
||||
if (PyTuple_Check(pos_tuple) && PyTuple_Size(pos_tuple) == 2) {
|
||||
PyObject* x_obj = PyTuple_GetItem(pos_tuple, 0);
|
||||
PyObject* y_obj = PyTuple_GetItem(pos_tuple, 1);
|
||||
if (PyLong_Check(x_obj) && PyLong_Check(y_obj)) {
|
||||
x = PyLong_AsLong(x_obj);
|
||||
y = PyLong_AsLong(y_obj);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "Grid indices must be integers");
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "at() takes two integers or a tuple of two integers");
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "at() takes two integers or a tuple of two integers");
|
||||
return NULL;
|
||||
}
|
||||
int x, y;
|
||||
|
||||
// Use the flexible position parsing helper - accepts:
|
||||
// at(x, y), at((x, y)), at([x, y]), at(Vector(x, y)), at(pos=(x, y)), etc.
|
||||
if (!PyPosition_ParseInt(args, kwds, &x, &y)) {
|
||||
return NULL; // Error already set by PyPosition_ParseInt
|
||||
}
|
||||
|
||||
|
||||
// Range validation
|
||||
if (x < 0 || x >= self->data->grid_x) {
|
||||
PyErr_Format(PyExc_IndexError, "x index %d is out of range [0, %d)", x, self->data->grid_x);
|
||||
|
|
@ -1349,16 +1338,22 @@ int UIGrid::set_fov_radius(PyUIGridObject* self, PyObject* value, void* closure)
|
|||
// Python API implementations for TCOD functionality
|
||||
PyObject* UIGrid::py_compute_fov(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
static const char* kwlist[] = {"x", "y", "radius", "light_walls", "algorithm", NULL};
|
||||
int x, y, radius = 0;
|
||||
static const char* kwlist[] = {"pos", "radius", "light_walls", "algorithm", NULL};
|
||||
PyObject* pos_obj = NULL;
|
||||
int radius = 0;
|
||||
int light_walls = 1;
|
||||
int algorithm = FOV_BASIC;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii|ipi", const_cast<char**>(kwlist),
|
||||
&x, &y, &radius, &light_walls, &algorithm)) {
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|ipi", const_cast<char**>(kwlist),
|
||||
&pos_obj, &radius, &light_walls, &algorithm)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
int x, y;
|
||||
if (!PyPosition_FromObjectInt(pos_obj, &x, &y)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Compute FOV
|
||||
self->data->computeFOV(x, y, radius, light_walls, (TCOD_fov_algorithm_t)algorithm);
|
||||
|
||||
|
|
@ -1367,33 +1362,42 @@ PyObject* UIGrid::py_compute_fov(PyUIGridObject* self, PyObject* args, PyObject*
|
|||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject* UIGrid::py_is_in_fov(PyUIGridObject* self, PyObject* args)
|
||||
PyObject* UIGrid::py_is_in_fov(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
int x, y;
|
||||
if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
|
||||
if (!PyPosition_ParseInt(args, kwds, &x, &y)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
bool in_fov = self->data->isInFOV(x, y);
|
||||
return PyBool_FromLong(in_fov);
|
||||
}
|
||||
|
||||
PyObject* UIGrid::py_find_path(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
static const char* kwlist[] = {"x1", "y1", "x2", "y2", "diagonal_cost", NULL};
|
||||
int x1, y1, x2, y2;
|
||||
static const char* kwlist[] = {"start", "end", "diagonal_cost", NULL};
|
||||
PyObject* start_obj = NULL;
|
||||
PyObject* end_obj = NULL;
|
||||
float diagonal_cost = 1.41f;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "iiii|f", const_cast<char**>(kwlist),
|
||||
&x1, &y1, &x2, &y2, &diagonal_cost)) {
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|f", const_cast<char**>(kwlist),
|
||||
&start_obj, &end_obj, &diagonal_cost)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
int x1, y1, x2, y2;
|
||||
if (!PyPosition_FromObjectInt(start_obj, &x1, &y1)) {
|
||||
return NULL;
|
||||
}
|
||||
if (!PyPosition_FromObjectInt(end_obj, &x2, &y2)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, int>> path = self->data->findPath(x1, y1, x2, y2, diagonal_cost);
|
||||
|
||||
|
||||
PyObject* path_list = PyList_New(path.size());
|
||||
if (!path_list) return NULL;
|
||||
|
||||
|
||||
for (size_t i = 0; i < path.size(); i++) {
|
||||
PyObject* coord = Py_BuildValue("(ii)", path[i].first, path[i].second);
|
||||
if (!coord) {
|
||||
|
|
@ -1402,80 +1406,93 @@ PyObject* UIGrid::py_find_path(PyUIGridObject* self, PyObject* args, PyObject* k
|
|||
}
|
||||
PyList_SET_ITEM(path_list, i, coord);
|
||||
}
|
||||
|
||||
|
||||
return path_list;
|
||||
}
|
||||
|
||||
PyObject* UIGrid::py_compute_dijkstra(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
static const char* kwlist[] = {"root_x", "root_y", "diagonal_cost", NULL};
|
||||
int root_x, root_y;
|
||||
static const char* kwlist[] = {"root", "diagonal_cost", NULL};
|
||||
PyObject* root_obj = NULL;
|
||||
float diagonal_cost = 1.41f;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii|f", const_cast<char**>(kwlist),
|
||||
&root_x, &root_y, &diagonal_cost)) {
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|f", const_cast<char**>(kwlist),
|
||||
&root_obj, &diagonal_cost)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
int root_x, root_y;
|
||||
if (!PyPosition_FromObjectInt(root_obj, &root_x, &root_y)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->data->computeDijkstra(root_x, root_y, diagonal_cost);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject* UIGrid::py_get_dijkstra_distance(PyUIGridObject* self, PyObject* args)
|
||||
PyObject* UIGrid::py_get_dijkstra_distance(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
int x, y;
|
||||
if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
|
||||
if (!PyPosition_ParseInt(args, kwds, &x, &y)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
float distance = self->data->getDijkstraDistance(x, y);
|
||||
if (distance < 0) {
|
||||
Py_RETURN_NONE; // Invalid position
|
||||
}
|
||||
|
||||
|
||||
return PyFloat_FromDouble(distance);
|
||||
}
|
||||
|
||||
PyObject* UIGrid::py_get_dijkstra_path(PyUIGridObject* self, PyObject* args)
|
||||
PyObject* UIGrid::py_get_dijkstra_path(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
int x, y;
|
||||
if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
|
||||
if (!PyPosition_ParseInt(args, kwds, &x, &y)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::pair<int, int>> path = self->data->getDijkstraPath(x, y);
|
||||
|
||||
|
||||
PyObject* path_list = PyList_New(path.size());
|
||||
for (size_t i = 0; i < path.size(); i++) {
|
||||
PyObject* pos = Py_BuildValue("(ii)", path[i].first, path[i].second);
|
||||
PyList_SetItem(path_list, i, pos); // Steals reference
|
||||
}
|
||||
|
||||
|
||||
return path_list;
|
||||
}
|
||||
|
||||
PyObject* UIGrid::py_compute_astar_path(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
int x1, y1, x2, y2;
|
||||
static const char* kwlist[] = {"start", "end", "diagonal_cost", NULL};
|
||||
PyObject* start_obj = NULL;
|
||||
PyObject* end_obj = NULL;
|
||||
float diagonal_cost = 1.41f;
|
||||
|
||||
static const char* kwlist[] = {"x1", "y1", "x2", "y2", "diagonal_cost", NULL};
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "iiii|f", const_cast<char**>(kwlist),
|
||||
&x1, &y1, &x2, &y2, &diagonal_cost)) {
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|f", const_cast<char**>(kwlist),
|
||||
&start_obj, &end_obj, &diagonal_cost)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
int x1, y1, x2, y2;
|
||||
if (!PyPosition_FromObjectInt(start_obj, &x1, &y1)) {
|
||||
return NULL;
|
||||
}
|
||||
if (!PyPosition_FromObjectInt(end_obj, &x2, &y2)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Compute A* path
|
||||
std::vector<std::pair<int, int>> path = self->data->computeAStarPath(x1, y1, x2, y2, diagonal_cost);
|
||||
|
||||
|
||||
// Convert to Python list
|
||||
PyObject* path_list = PyList_New(path.size());
|
||||
for (size_t i = 0; i < path.size(); i++) {
|
||||
PyObject* pos = Py_BuildValue("(ii)", path[i].first, path[i].second);
|
||||
PyList_SetItem(path_list, i, pos); // Steals reference
|
||||
}
|
||||
|
||||
|
||||
return path_list;
|
||||
}
|
||||
|
||||
|
|
@ -1812,72 +1829,63 @@ PyObject* UIGrid::py_center_camera(PyUIGridObject* self, PyObject* args) {
|
|||
PyMethodDef UIGrid::methods[] = {
|
||||
{"at", (PyCFunction)UIGrid::py_at, METH_VARARGS | METH_KEYWORDS},
|
||||
{"compute_fov", (PyCFunction)UIGrid::py_compute_fov, METH_VARARGS | METH_KEYWORDS,
|
||||
"compute_fov(x: int, y: int, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> None\n\n"
|
||||
"compute_fov(pos, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> None\n\n"
|
||||
"Compute field of view from a position.\n\n"
|
||||
"Args:\n"
|
||||
" x: X coordinate of the viewer\n"
|
||||
" y: Y coordinate of the viewer\n"
|
||||
" pos: Position as (x, y) tuple, list, or Vector\n"
|
||||
" radius: Maximum view distance (0 = unlimited)\n"
|
||||
" light_walls: Whether walls are lit when visible\n"
|
||||
" algorithm: FOV algorithm to use (FOV_BASIC, FOV_DIAMOND, FOV_SHADOW, FOV_PERMISSIVE_0-8)\n\n"
|
||||
"Updates the internal FOV state. Use is_in_fov(x, y) to query visibility."},
|
||||
{"is_in_fov", (PyCFunction)UIGrid::py_is_in_fov, METH_VARARGS,
|
||||
"is_in_fov(x: int, y: int) -> bool\n\n"
|
||||
"Updates the internal FOV state. Use is_in_fov(pos) to query visibility."},
|
||||
{"is_in_fov", (PyCFunction)UIGrid::py_is_in_fov, METH_VARARGS | METH_KEYWORDS,
|
||||
"is_in_fov(pos) -> bool\n\n"
|
||||
"Check if a cell is in the field of view.\n\n"
|
||||
"Args:\n"
|
||||
" x: X coordinate to check\n"
|
||||
" y: Y coordinate to check\n\n"
|
||||
" pos: Position as (x, y) tuple, list, or Vector\n\n"
|
||||
"Returns:\n"
|
||||
" True if the cell is visible, False otherwise\n\n"
|
||||
"Must call compute_fov() first to calculate visibility."},
|
||||
{"find_path", (PyCFunction)UIGrid::py_find_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"find_path(x1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n"
|
||||
{"find_path", (PyCFunction)UIGrid::py_find_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"find_path(start, end, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n"
|
||||
"Find A* path between two points.\n\n"
|
||||
"Args:\n"
|
||||
" x1: Starting X coordinate\n"
|
||||
" y1: Starting Y coordinate\n"
|
||||
" x2: Target X coordinate\n"
|
||||
" y2: Target Y coordinate\n"
|
||||
" start: Starting position as (x, y) tuple, list, or Vector\n"
|
||||
" end: Target position as (x, y) tuple, list, or Vector\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
||||
"Returns:\n"
|
||||
" List of (x, y) tuples representing the path, empty list if no path exists\n\n"
|
||||
"Uses A* algorithm with walkability from grid cells."},
|
||||
{"compute_dijkstra", (PyCFunction)UIGrid::py_compute_dijkstra, METH_VARARGS | METH_KEYWORDS,
|
||||
"compute_dijkstra(root_x: int, root_y: int, diagonal_cost: float = 1.41) -> None\n\n"
|
||||
{"compute_dijkstra", (PyCFunction)UIGrid::py_compute_dijkstra, METH_VARARGS | METH_KEYWORDS,
|
||||
"compute_dijkstra(root, diagonal_cost: float = 1.41) -> None\n\n"
|
||||
"Compute Dijkstra map from root position.\n\n"
|
||||
"Args:\n"
|
||||
" root_x: X coordinate of the root/target\n"
|
||||
" root_y: Y coordinate of the root/target\n"
|
||||
" root: Root position as (x, y) tuple, list, or Vector\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
||||
"Precomputes distances from all reachable cells to the root.\n"
|
||||
"Use get_dijkstra_distance() and get_dijkstra_path() to query results.\n"
|
||||
"Useful for multiple entities pathfinding to the same target."},
|
||||
{"get_dijkstra_distance", (PyCFunction)UIGrid::py_get_dijkstra_distance, METH_VARARGS,
|
||||
"get_dijkstra_distance(x: int, y: int) -> Optional[float]\n\n"
|
||||
{"get_dijkstra_distance", (PyCFunction)UIGrid::py_get_dijkstra_distance, METH_VARARGS | METH_KEYWORDS,
|
||||
"get_dijkstra_distance(pos) -> Optional[float]\n\n"
|
||||
"Get distance from Dijkstra root to position.\n\n"
|
||||
"Args:\n"
|
||||
" x: X coordinate to query\n"
|
||||
" y: Y coordinate to query\n\n"
|
||||
" pos: Position as (x, y) tuple, list, or Vector\n\n"
|
||||
"Returns:\n"
|
||||
" Distance as float, or None if position is unreachable or invalid\n\n"
|
||||
"Must call compute_dijkstra() first."},
|
||||
{"get_dijkstra_path", (PyCFunction)UIGrid::py_get_dijkstra_path, METH_VARARGS,
|
||||
"get_dijkstra_path(x: int, y: int) -> List[Tuple[int, int]]\n\n"
|
||||
{"get_dijkstra_path", (PyCFunction)UIGrid::py_get_dijkstra_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"get_dijkstra_path(pos) -> List[Tuple[int, int]]\n\n"
|
||||
"Get path from position to Dijkstra root.\n\n"
|
||||
"Args:\n"
|
||||
" x: Starting X coordinate\n"
|
||||
" y: Starting Y coordinate\n\n"
|
||||
" pos: Position as (x, y) tuple, list, or Vector\n\n"
|
||||
"Returns:\n"
|
||||
" List of (x, y) tuples representing path to root, empty if unreachable\n\n"
|
||||
"Must call compute_dijkstra() first. Path includes start but not root position."},
|
||||
{"compute_astar_path", (PyCFunction)UIGrid::py_compute_astar_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"compute_astar_path(x1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n"
|
||||
"compute_astar_path(start, end, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n"
|
||||
"Compute A* path between two points.\n\n"
|
||||
"Args:\n"
|
||||
" x1: Starting X coordinate\n"
|
||||
" y1: Starting Y coordinate\n"
|
||||
" x2: Target X coordinate\n"
|
||||
" y2: Target Y coordinate\n"
|
||||
" start: Starting position as (x, y) tuple, list, or Vector\n"
|
||||
" end: Target position as (x, y) tuple, list, or Vector\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
||||
"Returns:\n"
|
||||
" List of (x, y) tuples representing the path, empty list if no path exists\n\n"
|
||||
|
|
@ -1917,72 +1925,63 @@ PyMethodDef UIGrid_all_methods[] = {
|
|||
UIDRAWABLE_METHODS,
|
||||
{"at", (PyCFunction)UIGrid::py_at, METH_VARARGS | METH_KEYWORDS},
|
||||
{"compute_fov", (PyCFunction)UIGrid::py_compute_fov, METH_VARARGS | METH_KEYWORDS,
|
||||
"compute_fov(x: int, y: int, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> None\n\n"
|
||||
"compute_fov(pos, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> None\n\n"
|
||||
"Compute field of view from a position.\n\n"
|
||||
"Args:\n"
|
||||
" x: X coordinate of the viewer\n"
|
||||
" y: Y coordinate of the viewer\n"
|
||||
" pos: Position as (x, y) tuple, list, or Vector\n"
|
||||
" radius: Maximum view distance (0 = unlimited)\n"
|
||||
" light_walls: Whether walls are lit when visible\n"
|
||||
" algorithm: FOV algorithm to use (FOV_BASIC, FOV_DIAMOND, FOV_SHADOW, FOV_PERMISSIVE_0-8)\n\n"
|
||||
"Updates the internal FOV state. Use is_in_fov(x, y) to query visibility."},
|
||||
{"is_in_fov", (PyCFunction)UIGrid::py_is_in_fov, METH_VARARGS,
|
||||
"is_in_fov(x: int, y: int) -> bool\n\n"
|
||||
"Updates the internal FOV state. Use is_in_fov(pos) to query visibility."},
|
||||
{"is_in_fov", (PyCFunction)UIGrid::py_is_in_fov, METH_VARARGS | METH_KEYWORDS,
|
||||
"is_in_fov(pos) -> bool\n\n"
|
||||
"Check if a cell is in the field of view.\n\n"
|
||||
"Args:\n"
|
||||
" x: X coordinate to check\n"
|
||||
" y: Y coordinate to check\n\n"
|
||||
" pos: Position as (x, y) tuple, list, or Vector\n\n"
|
||||
"Returns:\n"
|
||||
" True if the cell is visible, False otherwise\n\n"
|
||||
"Must call compute_fov() first to calculate visibility."},
|
||||
{"find_path", (PyCFunction)UIGrid::py_find_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"find_path(x1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n"
|
||||
{"find_path", (PyCFunction)UIGrid::py_find_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"find_path(start, end, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n"
|
||||
"Find A* path between two points.\n\n"
|
||||
"Args:\n"
|
||||
" x1: Starting X coordinate\n"
|
||||
" y1: Starting Y coordinate\n"
|
||||
" x2: Target X coordinate\n"
|
||||
" y2: Target Y coordinate\n"
|
||||
" start: Starting position as (x, y) tuple, list, or Vector\n"
|
||||
" end: Target position as (x, y) tuple, list, or Vector\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
||||
"Returns:\n"
|
||||
" List of (x, y) tuples representing the path, empty list if no path exists\n\n"
|
||||
"Uses A* algorithm with walkability from grid cells."},
|
||||
{"compute_dijkstra", (PyCFunction)UIGrid::py_compute_dijkstra, METH_VARARGS | METH_KEYWORDS,
|
||||
"compute_dijkstra(root_x: int, root_y: int, diagonal_cost: float = 1.41) -> None\n\n"
|
||||
{"compute_dijkstra", (PyCFunction)UIGrid::py_compute_dijkstra, METH_VARARGS | METH_KEYWORDS,
|
||||
"compute_dijkstra(root, diagonal_cost: float = 1.41) -> None\n\n"
|
||||
"Compute Dijkstra map from root position.\n\n"
|
||||
"Args:\n"
|
||||
" root_x: X coordinate of the root/target\n"
|
||||
" root_y: Y coordinate of the root/target\n"
|
||||
" root: Root position as (x, y) tuple, list, or Vector\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
||||
"Precomputes distances from all reachable cells to the root.\n"
|
||||
"Use get_dijkstra_distance() and get_dijkstra_path() to query results.\n"
|
||||
"Useful for multiple entities pathfinding to the same target."},
|
||||
{"get_dijkstra_distance", (PyCFunction)UIGrid::py_get_dijkstra_distance, METH_VARARGS,
|
||||
"get_dijkstra_distance(x: int, y: int) -> Optional[float]\n\n"
|
||||
{"get_dijkstra_distance", (PyCFunction)UIGrid::py_get_dijkstra_distance, METH_VARARGS | METH_KEYWORDS,
|
||||
"get_dijkstra_distance(pos) -> Optional[float]\n\n"
|
||||
"Get distance from Dijkstra root to position.\n\n"
|
||||
"Args:\n"
|
||||
" x: X coordinate to query\n"
|
||||
" y: Y coordinate to query\n\n"
|
||||
" pos: Position as (x, y) tuple, list, or Vector\n\n"
|
||||
"Returns:\n"
|
||||
" Distance as float, or None if position is unreachable or invalid\n\n"
|
||||
"Must call compute_dijkstra() first."},
|
||||
{"get_dijkstra_path", (PyCFunction)UIGrid::py_get_dijkstra_path, METH_VARARGS,
|
||||
"get_dijkstra_path(x: int, y: int) -> List[Tuple[int, int]]\n\n"
|
||||
{"get_dijkstra_path", (PyCFunction)UIGrid::py_get_dijkstra_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"get_dijkstra_path(pos) -> List[Tuple[int, int]]\n\n"
|
||||
"Get path from position to Dijkstra root.\n\n"
|
||||
"Args:\n"
|
||||
" x: Starting X coordinate\n"
|
||||
" y: Starting Y coordinate\n\n"
|
||||
" pos: Position as (x, y) tuple, list, or Vector\n\n"
|
||||
"Returns:\n"
|
||||
" List of (x, y) tuples representing path to root, empty if unreachable\n\n"
|
||||
"Must call compute_dijkstra() first. Path includes start but not root position."},
|
||||
{"compute_astar_path", (PyCFunction)UIGrid::py_compute_astar_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"compute_astar_path(x1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n"
|
||||
"compute_astar_path(start, end, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n"
|
||||
"Compute A* path between two points.\n\n"
|
||||
"Args:\n"
|
||||
" x1: Starting X coordinate\n"
|
||||
" y1: Starting Y coordinate\n"
|
||||
" x2: Target X coordinate\n"
|
||||
" y2: Target Y coordinate\n"
|
||||
" start: Starting position as (x, y) tuple, list, or Vector\n"
|
||||
" end: Target position as (x, y) tuple, list, or Vector\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
||||
"Returns:\n"
|
||||
" List of (x, y) tuples representing the path, empty list if no path exists\n\n"
|
||||
|
|
@ -2055,7 +2054,7 @@ PyGetSetDef UIGrid::getsetters[] = {
|
|||
{"on_click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
||||
MCRF_PROPERTY(on_click,
|
||||
"Callable executed when object is clicked. "
|
||||
"Function receives (x, y) coordinates of click."
|
||||
"Function receives (pos: Vector, button: str, action: str)."
|
||||
), (void*)PyObjectsEnum::UIGRID},
|
||||
|
||||
{"texture", (getter)UIGrid::get_texture, NULL, "Texture of the grid", NULL}, //TODO 7DRL-day2-item5
|
||||
|
|
@ -2083,11 +2082,11 @@ PyGetSetDef UIGrid::getsetters[] = {
|
|||
UIDRAWABLE_PARENT_GETSETTERS(PyObjectsEnum::UIGRID),
|
||||
// #142 - Grid cell mouse events
|
||||
{"on_cell_enter", (getter)UIGrid::get_on_cell_enter, (setter)UIGrid::set_on_cell_enter,
|
||||
"Callback when mouse enters a grid cell. Called with (cell_x, cell_y).", NULL},
|
||||
"Callback when mouse enters a grid cell. Called with (cell_pos: Vector).", NULL},
|
||||
{"on_cell_exit", (getter)UIGrid::get_on_cell_exit, (setter)UIGrid::set_on_cell_exit,
|
||||
"Callback when mouse exits a grid cell. Called with (cell_x, cell_y).", NULL},
|
||||
"Callback when mouse exits a grid cell. Called with (cell_pos: Vector).", NULL},
|
||||
{"on_cell_click", (getter)UIGrid::get_on_cell_click, (setter)UIGrid::set_on_cell_click,
|
||||
"Callback when a grid cell is clicked. Called with (cell_x, cell_y).", NULL},
|
||||
"Callback when a grid cell is clicked. Called with (cell_pos: Vector).", NULL},
|
||||
{"hovered_cell", (getter)UIGrid::get_hovered_cell, NULL,
|
||||
"Currently hovered cell as (x, y) tuple, or None if not hovering.", NULL},
|
||||
{NULL} /* Sentinel */
|
||||
|
|
@ -2249,29 +2248,39 @@ void UIGrid::updateCellHover(sf::Vector2f mousepos) {
|
|||
if (new_cell != hovered_cell) {
|
||||
// Fire exit callback for old cell
|
||||
if (hovered_cell.has_value() && on_cell_exit_callable) {
|
||||
PyObject* args = Py_BuildValue("(ii)", hovered_cell->x, hovered_cell->y);
|
||||
PyObject* result = PyObject_CallObject(on_cell_exit_callable->borrow(), args);
|
||||
Py_DECREF(args);
|
||||
if (!result) {
|
||||
std::cerr << "Cell exit callback raised an exception:" << std::endl;
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
Py_DECREF(result);
|
||||
// Create Vector object for cell position
|
||||
PyObject* cell_pos = PyObject_CallFunction((PyObject*)&mcrfpydef::PyVectorType, "ff", (float)hovered_cell->x, (float)hovered_cell->y);
|
||||
if (cell_pos) {
|
||||
PyObject* args = Py_BuildValue("(O)", cell_pos);
|
||||
Py_DECREF(cell_pos);
|
||||
PyObject* result = PyObject_CallObject(on_cell_exit_callable->borrow(), args);
|
||||
Py_DECREF(args);
|
||||
if (!result) {
|
||||
std::cerr << "Cell exit callback raised an exception:" << std::endl;
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
Py_DECREF(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fire enter callback for new cell
|
||||
if (new_cell.has_value() && on_cell_enter_callable) {
|
||||
PyObject* args = Py_BuildValue("(ii)", new_cell->x, new_cell->y);
|
||||
PyObject* result = PyObject_CallObject(on_cell_enter_callable->borrow(), args);
|
||||
Py_DECREF(args);
|
||||
if (!result) {
|
||||
std::cerr << "Cell enter callback raised an exception:" << std::endl;
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
Py_DECREF(result);
|
||||
// Create Vector object for cell position
|
||||
PyObject* cell_pos = PyObject_CallFunction((PyObject*)&mcrfpydef::PyVectorType, "ff", (float)new_cell->x, (float)new_cell->y);
|
||||
if (cell_pos) {
|
||||
PyObject* args = Py_BuildValue("(O)", cell_pos);
|
||||
Py_DECREF(cell_pos);
|
||||
PyObject* result = PyObject_CallObject(on_cell_enter_callable->borrow(), args);
|
||||
Py_DECREF(args);
|
||||
if (!result) {
|
||||
std::cerr << "Cell enter callback raised an exception:" << std::endl;
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
Py_DECREF(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -165,11 +165,11 @@ public:
|
|||
static int set_fov_radius(PyUIGridObject* self, PyObject* value, void* closure);
|
||||
static PyObject* py_at(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* py_compute_fov(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* py_is_in_fov(PyUIGridObject* self, PyObject* args);
|
||||
static PyObject* py_is_in_fov(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* py_find_path(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* py_compute_dijkstra(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* py_get_dijkstra_distance(PyUIGridObject* self, PyObject* args);
|
||||
static PyObject* py_get_dijkstra_path(PyUIGridObject* self, PyObject* args);
|
||||
static PyObject* py_get_dijkstra_distance(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* py_get_dijkstra_path(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* py_compute_astar_path(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* py_entities_in_radius(PyUIGridObject* self, PyObject* args, PyObject* kwds); // #115
|
||||
static PyObject* py_center_camera(PyUIGridObject* self, PyObject* args); // #169
|
||||
|
|
|
|||
|
|
@ -451,7 +451,7 @@ PyGetSetDef UILine::getsetters[] = {
|
|||
{"thickness", (getter)UILine::get_thickness, (setter)UILine::set_thickness,
|
||||
MCRF_PROPERTY(thickness, "Line thickness in pixels."), NULL},
|
||||
{"on_click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
||||
MCRF_PROPERTY(on_click, "Callable executed when line is clicked."),
|
||||
MCRF_PROPERTY(on_click, "Callable executed when line is clicked. Function receives (pos: Vector, button: str, action: str)."),
|
||||
(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)."),
|
||||
|
|
|
|||
|
|
@ -343,7 +343,7 @@ PyGetSetDef UISprite::getsetters[] = {
|
|||
{"on_click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
||||
MCRF_PROPERTY(on_click,
|
||||
"Callable executed when object is clicked. "
|
||||
"Function receives (x, y) coordinates of click."
|
||||
"Function receives (pos: Vector, button: str, action: str)."
|
||||
), (void*)PyObjectsEnum::UISPRITE},
|
||||
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int,
|
||||
MCRF_PROPERTY(z_index,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue