232 lines
7.7 KiB
C++
232 lines
7.7 KiB
C++
#include "PyAlignment.h"
|
|
#include "McRFPy_API.h"
|
|
|
|
// Static storage for cached enum class reference
|
|
PyObject* PyAlignment::alignment_enum_class = nullptr;
|
|
|
|
// Alignment table - maps enum value to name
|
|
struct AlignmentEntry {
|
|
const char* name;
|
|
int value;
|
|
AlignmentType type;
|
|
};
|
|
|
|
static const AlignmentEntry alignment_table[] = {
|
|
{"TOP_LEFT", 0, AlignmentType::TOP_LEFT},
|
|
{"TOP_CENTER", 1, AlignmentType::TOP_CENTER},
|
|
{"TOP_RIGHT", 2, AlignmentType::TOP_RIGHT},
|
|
{"CENTER_LEFT", 3, AlignmentType::CENTER_LEFT},
|
|
{"CENTER", 4, AlignmentType::CENTER},
|
|
{"CENTER_RIGHT", 5, AlignmentType::CENTER_RIGHT},
|
|
{"BOTTOM_LEFT", 6, AlignmentType::BOTTOM_LEFT},
|
|
{"BOTTOM_CENTER", 7, AlignmentType::BOTTOM_CENTER},
|
|
{"BOTTOM_RIGHT", 8, AlignmentType::BOTTOM_RIGHT},
|
|
};
|
|
|
|
// Legacy camelCase names (for backwards compatibility if desired)
|
|
static const char* legacy_names[] = {
|
|
"topLeft", "topCenter", "topRight",
|
|
"centerLeft", "center", "centerRight",
|
|
"bottomLeft", "bottomCenter", "bottomRight"
|
|
};
|
|
|
|
static const int NUM_ALIGNMENT_ENTRIES = sizeof(alignment_table) / sizeof(alignment_table[0]);
|
|
|
|
const char* PyAlignment::alignment_name(AlignmentType value) {
|
|
int idx = static_cast<int>(value);
|
|
if (idx >= 0 && idx < NUM_ALIGNMENT_ENTRIES) {
|
|
return alignment_table[idx].name;
|
|
}
|
|
return "NONE";
|
|
}
|
|
|
|
PyObject* PyAlignment::create_enum_class(PyObject* module) {
|
|
// Import IntEnum from enum module
|
|
PyObject* enum_module = PyImport_ImportModule("enum");
|
|
if (!enum_module) {
|
|
return NULL;
|
|
}
|
|
|
|
PyObject* int_enum = PyObject_GetAttrString(enum_module, "IntEnum");
|
|
Py_DECREF(enum_module);
|
|
if (!int_enum) {
|
|
return NULL;
|
|
}
|
|
|
|
// Create dict of enum members
|
|
PyObject* members = PyDict_New();
|
|
if (!members) {
|
|
Py_DECREF(int_enum);
|
|
return NULL;
|
|
}
|
|
|
|
// Add all alignment members
|
|
for (int i = 0; i < NUM_ALIGNMENT_ENTRIES; i++) {
|
|
PyObject* value = PyLong_FromLong(alignment_table[i].value);
|
|
if (!value) {
|
|
Py_DECREF(members);
|
|
Py_DECREF(int_enum);
|
|
return NULL;
|
|
}
|
|
if (PyDict_SetItemString(members, alignment_table[i].name, value) < 0) {
|
|
Py_DECREF(value);
|
|
Py_DECREF(members);
|
|
Py_DECREF(int_enum);
|
|
return NULL;
|
|
}
|
|
Py_DECREF(value);
|
|
}
|
|
|
|
// Call IntEnum("Alignment", members) to create the enum class
|
|
PyObject* name = PyUnicode_FromString("Alignment");
|
|
if (!name) {
|
|
Py_DECREF(members);
|
|
Py_DECREF(int_enum);
|
|
return NULL;
|
|
}
|
|
|
|
// IntEnum(name, members) using functional API
|
|
PyObject* args = PyTuple_Pack(2, name, members);
|
|
Py_DECREF(name);
|
|
Py_DECREF(members);
|
|
if (!args) {
|
|
Py_DECREF(int_enum);
|
|
return NULL;
|
|
}
|
|
|
|
PyObject* alignment_class = PyObject_Call(int_enum, args, NULL);
|
|
Py_DECREF(args);
|
|
Py_DECREF(int_enum);
|
|
|
|
if (!alignment_class) {
|
|
return NULL;
|
|
}
|
|
|
|
// Cache the reference for fast type checking
|
|
alignment_enum_class = alignment_class;
|
|
Py_INCREF(alignment_enum_class);
|
|
|
|
// Add docstring to the enum class
|
|
static const char* alignment_doc =
|
|
"Alignment enum for positioning UI elements relative to parent bounds.\n\n"
|
|
"Values:\n"
|
|
" TOP_LEFT, TOP_CENTER, TOP_RIGHT\n"
|
|
" CENTER_LEFT, CENTER, CENTER_RIGHT\n"
|
|
" BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT\n\n"
|
|
"Margin Validation Rules:\n"
|
|
" Margins define distance from parent edge when aligned.\n\n"
|
|
" - CENTER: No margins allowed (raises ValueError if margin != 0)\n"
|
|
" - TOP_CENTER, BOTTOM_CENTER: Only vert_margin applies (horiz_margin raises ValueError)\n"
|
|
" - CENTER_LEFT, CENTER_RIGHT: Only horiz_margin applies (vert_margin raises ValueError)\n"
|
|
" - Corner alignments (TOP_LEFT, etc.): All margins valid\n\n"
|
|
"Properties:\n"
|
|
" align: Alignment value or None to disable\n"
|
|
" margin: General margin for all applicable edges\n"
|
|
" horiz_margin: Override for horizontal edge (0 = use general margin)\n"
|
|
" vert_margin: Override for vertical edge (0 = use general margin)\n\n"
|
|
"Example:\n"
|
|
" # Center a panel in the scene\n"
|
|
" panel = Frame(size=(200, 100), align=Alignment.CENTER)\n"
|
|
" scene.children.append(panel)\n\n"
|
|
" # Place button in bottom-right with 10px margin\n"
|
|
" button = Frame(size=(80, 30), align=Alignment.BOTTOM_RIGHT, margin=10)\n"
|
|
" panel.children.append(button)";
|
|
PyObject* doc = PyUnicode_FromString(alignment_doc);
|
|
if (doc) {
|
|
PyObject_SetAttrString(alignment_class, "__doc__", doc);
|
|
Py_DECREF(doc);
|
|
}
|
|
|
|
// Add to module
|
|
if (PyModule_AddObject(module, "Alignment", alignment_class) < 0) {
|
|
Py_DECREF(alignment_class);
|
|
alignment_enum_class = nullptr;
|
|
return NULL;
|
|
}
|
|
|
|
return alignment_class;
|
|
}
|
|
|
|
int PyAlignment::from_arg(PyObject* arg, AlignmentType* out_align, bool* was_none) {
|
|
if (was_none) *was_none = false;
|
|
|
|
// Accept None -> NONE alignment (no alignment)
|
|
if (arg == Py_None) {
|
|
if (was_none) *was_none = true;
|
|
*out_align = AlignmentType::NONE;
|
|
return 1;
|
|
}
|
|
|
|
// Accept Alignment enum member (check if it's an instance of our enum)
|
|
if (alignment_enum_class && PyObject_IsInstance(arg, alignment_enum_class)) {
|
|
// IntEnum members have a 'value' attribute
|
|
PyObject* value = PyObject_GetAttrString(arg, "value");
|
|
if (!value) {
|
|
return 0;
|
|
}
|
|
long val = PyLong_AsLong(value);
|
|
Py_DECREF(value);
|
|
if (val == -1 && PyErr_Occurred()) {
|
|
return 0;
|
|
}
|
|
if (val >= 0 && val < NUM_ALIGNMENT_ENTRIES) {
|
|
*out_align = alignment_table[val].type;
|
|
return 1;
|
|
}
|
|
PyErr_Format(PyExc_ValueError,
|
|
"Invalid Alignment value: %ld. Must be 0-%d.", val, NUM_ALIGNMENT_ENTRIES - 1);
|
|
return 0;
|
|
}
|
|
|
|
// Accept int (for direct enum value access)
|
|
if (PyLong_Check(arg)) {
|
|
long val = PyLong_AsLong(arg);
|
|
if (val == -1 && PyErr_Occurred()) {
|
|
return 0;
|
|
}
|
|
if (val >= 0 && val < NUM_ALIGNMENT_ENTRIES) {
|
|
*out_align = alignment_table[val].type;
|
|
return 1;
|
|
}
|
|
PyErr_Format(PyExc_ValueError,
|
|
"Invalid alignment value: %ld. Must be 0-%d or use mcrfpy.Alignment enum.",
|
|
val, NUM_ALIGNMENT_ENTRIES - 1);
|
|
return 0;
|
|
}
|
|
|
|
// Accept string (for backwards compatibility)
|
|
if (PyUnicode_Check(arg)) {
|
|
const char* name = PyUnicode_AsUTF8(arg);
|
|
if (!name) {
|
|
return 0;
|
|
}
|
|
|
|
// Check legacy camelCase names first
|
|
for (int i = 0; i < NUM_ALIGNMENT_ENTRIES; i++) {
|
|
if (strcmp(name, legacy_names[i]) == 0) {
|
|
*out_align = alignment_table[i].type;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Also check enum-style names (TOP_LEFT, CENTER, etc.)
|
|
for (int i = 0; i < NUM_ALIGNMENT_ENTRIES; i++) {
|
|
if (strcmp(name, alignment_table[i].name) == 0) {
|
|
*out_align = alignment_table[i].type;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Build error message with available options
|
|
PyErr_Format(PyExc_ValueError,
|
|
"Unknown alignment: '%s'. Use mcrfpy.Alignment enum (e.g., Alignment.CENTER) "
|
|
"or string names: 'topLeft', 'topCenter', 'topRight', 'centerLeft', 'center', "
|
|
"'centerRight', 'bottomLeft', 'bottomCenter', 'bottomRight'.",
|
|
name);
|
|
return 0;
|
|
}
|
|
|
|
PyErr_SetString(PyExc_TypeError,
|
|
"Alignment must be mcrfpy.Alignment enum member, string, int, or None");
|
|
return 0;
|
|
}
|