McRogueFace/src/PyAlignment.cpp

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;
}