Input Enums instead of strings.
This commit is contained in:
parent
d9411f94a4
commit
9eacedc624
9 changed files with 1205 additions and 0 deletions
|
|
@ -13,6 +13,9 @@
|
||||||
#include "PyFOV.h"
|
#include "PyFOV.h"
|
||||||
#include "PyTransition.h"
|
#include "PyTransition.h"
|
||||||
#include "PyEasing.h"
|
#include "PyEasing.h"
|
||||||
|
#include "PyKey.h"
|
||||||
|
#include "PyMouseButton.h"
|
||||||
|
#include "PyInputState.h"
|
||||||
#include "PySound.h"
|
#include "PySound.h"
|
||||||
#include "PyMusic.h"
|
#include "PyMusic.h"
|
||||||
#include "PyKeyboard.h"
|
#include "PyKeyboard.h"
|
||||||
|
|
@ -536,6 +539,27 @@ PyObject* PyInit_mcrfpy()
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add Key enum class for keyboard input
|
||||||
|
PyObject* key_class = PyKey::create_enum_class(m);
|
||||||
|
if (!key_class) {
|
||||||
|
// If enum creation fails, continue without it (non-fatal)
|
||||||
|
PyErr_Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add MouseButton enum class for mouse input
|
||||||
|
PyObject* mouse_button_class = PyMouseButton::create_enum_class(m);
|
||||||
|
if (!mouse_button_class) {
|
||||||
|
// If enum creation fails, continue without it (non-fatal)
|
||||||
|
PyErr_Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add InputState enum class for input event states (pressed/released)
|
||||||
|
PyObject* input_state_class = PyInputState::create_enum_class(m);
|
||||||
|
if (!input_state_class) {
|
||||||
|
// If enum creation fails, continue without it (non-fatal)
|
||||||
|
PyErr_Clear();
|
||||||
|
}
|
||||||
|
|
||||||
// Add automation submodule
|
// Add automation submodule
|
||||||
PyObject* automation_module = McRFPy_Automation::init_automation_module();
|
PyObject* automation_module = McRFPy_Automation::init_automation_module();
|
||||||
if (automation_module != NULL) {
|
if (automation_module != NULL) {
|
||||||
|
|
|
||||||
187
src/PyInputState.cpp
Normal file
187
src/PyInputState.cpp
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
#include "PyInputState.h"
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
// Static storage for cached enum class reference
|
||||||
|
PyObject* PyInputState::input_state_enum_class = nullptr;
|
||||||
|
|
||||||
|
// InputState entries - maps enum name to value and legacy string
|
||||||
|
struct InputStateEntry {
|
||||||
|
const char* name; // Python enum name (UPPER_SNAKE_CASE)
|
||||||
|
int value; // Integer value
|
||||||
|
const char* legacy; // Legacy string name for backwards compatibility
|
||||||
|
};
|
||||||
|
|
||||||
|
static const InputStateEntry input_state_table[] = {
|
||||||
|
{"PRESSED", 0, "start"},
|
||||||
|
{"RELEASED", 1, "end"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int NUM_INPUT_STATE_ENTRIES = sizeof(input_state_table) / sizeof(input_state_table[0]);
|
||||||
|
|
||||||
|
const char* PyInputState::to_legacy_string(bool pressed) {
|
||||||
|
return pressed ? "start" : "end";
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyInputState::create_enum_class(PyObject* module) {
|
||||||
|
// Build the enum definition dynamically from the table
|
||||||
|
std::ostringstream code;
|
||||||
|
code << "from enum import IntEnum\n\n";
|
||||||
|
|
||||||
|
code << "class InputState(IntEnum):\n";
|
||||||
|
code << " \"\"\"Enum representing input event states (pressed/released).\n";
|
||||||
|
code << " \n";
|
||||||
|
code << " Values:\n";
|
||||||
|
code << " PRESSED: Key or button was pressed (legacy: 'start')\n";
|
||||||
|
code << " RELEASED: Key or button was released (legacy: 'end')\n";
|
||||||
|
code << " \n";
|
||||||
|
code << " These enum values compare equal to their legacy string equivalents\n";
|
||||||
|
code << " for backwards compatibility:\n";
|
||||||
|
code << " InputState.PRESSED == 'start' # True\n";
|
||||||
|
code << " InputState.RELEASED == 'end' # True\n";
|
||||||
|
code << " \"\"\"\n";
|
||||||
|
|
||||||
|
// Add enum members
|
||||||
|
for (int i = 0; i < NUM_INPUT_STATE_ENTRIES; i++) {
|
||||||
|
code << " " << input_state_table[i].name << " = " << input_state_table[i].value << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add legacy names and custom methods AFTER class creation
|
||||||
|
// (IntEnum doesn't allow dict attributes during class definition)
|
||||||
|
code << "\n# Add legacy name mapping after class creation\n";
|
||||||
|
code << "InputState._legacy_names = {\n";
|
||||||
|
for (int i = 0; i < NUM_INPUT_STATE_ENTRIES; i++) {
|
||||||
|
code << " " << input_state_table[i].value << ": \"" << input_state_table[i].legacy << "\",\n";
|
||||||
|
}
|
||||||
|
code << "}\n\n";
|
||||||
|
|
||||||
|
code << R"(
|
||||||
|
def _InputState_eq(self, other):
|
||||||
|
if isinstance(other, str):
|
||||||
|
# Check enum name match (e.g., "PRESSED")
|
||||||
|
if self.name == other:
|
||||||
|
return True
|
||||||
|
# Check legacy name match (e.g., "start")
|
||||||
|
legacy = type(self)._legacy_names.get(self.value)
|
||||||
|
if legacy and legacy == other:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
# Fall back to int comparison for IntEnum
|
||||||
|
return int.__eq__(int(self), other)
|
||||||
|
|
||||||
|
InputState.__eq__ = _InputState_eq
|
||||||
|
InputState.__hash__ = lambda self: hash(int(self))
|
||||||
|
InputState.__repr__ = lambda self: f"{type(self).__name__}.{self.name}"
|
||||||
|
InputState.__str__ = lambda self: self.name
|
||||||
|
)";
|
||||||
|
|
||||||
|
std::string code_str = code.str();
|
||||||
|
|
||||||
|
// Create globals with builtins
|
||||||
|
PyObject* globals = PyDict_New();
|
||||||
|
if (!globals) return NULL;
|
||||||
|
|
||||||
|
PyObject* builtins = PyEval_GetBuiltins();
|
||||||
|
PyDict_SetItemString(globals, "__builtins__", builtins);
|
||||||
|
|
||||||
|
PyObject* locals = PyDict_New();
|
||||||
|
if (!locals) {
|
||||||
|
Py_DECREF(globals);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the code to create the enum
|
||||||
|
PyObject* result = PyRun_String(code_str.c_str(), Py_file_input, globals, locals);
|
||||||
|
if (!result) {
|
||||||
|
Py_DECREF(globals);
|
||||||
|
Py_DECREF(locals);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_DECREF(result);
|
||||||
|
|
||||||
|
// Get the InputState class from locals
|
||||||
|
PyObject* input_state_class = PyDict_GetItemString(locals, "InputState");
|
||||||
|
if (!input_state_class) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Failed to create InputState enum class");
|
||||||
|
Py_DECREF(globals);
|
||||||
|
Py_DECREF(locals);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_INCREF(input_state_class);
|
||||||
|
|
||||||
|
// Cache the reference for fast type checking
|
||||||
|
input_state_enum_class = input_state_class;
|
||||||
|
Py_INCREF(input_state_enum_class);
|
||||||
|
|
||||||
|
// Add to module
|
||||||
|
if (PyModule_AddObject(module, "InputState", input_state_class) < 0) {
|
||||||
|
Py_DECREF(input_state_class);
|
||||||
|
Py_DECREF(globals);
|
||||||
|
Py_DECREF(locals);
|
||||||
|
input_state_enum_class = nullptr;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_DECREF(globals);
|
||||||
|
Py_DECREF(locals);
|
||||||
|
|
||||||
|
return input_state_class;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PyInputState::from_arg(PyObject* arg, bool* out_pressed) {
|
||||||
|
// Accept InputState enum member
|
||||||
|
if (input_state_enum_class && PyObject_IsInstance(arg, input_state_enum_class)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
*out_pressed = (val == 0); // PRESSED = 0
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept int
|
||||||
|
if (PyLong_Check(arg)) {
|
||||||
|
long val = PyLong_AsLong(arg);
|
||||||
|
if (val == -1 && PyErr_Occurred()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (val == 0 || val == 1) {
|
||||||
|
*out_pressed = (val == 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"Invalid InputState value: %ld. Must be 0 (PRESSED) or 1 (RELEASED).", val);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept string (both new and legacy names)
|
||||||
|
if (PyUnicode_Check(arg)) {
|
||||||
|
const char* name = PyUnicode_AsUTF8(arg);
|
||||||
|
if (!name) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check all entries for both name and legacy match
|
||||||
|
for (int i = 0; i < NUM_INPUT_STATE_ENTRIES; i++) {
|
||||||
|
if (strcmp(name, input_state_table[i].name) == 0 ||
|
||||||
|
strcmp(name, input_state_table[i].legacy) == 0) {
|
||||||
|
*out_pressed = (input_state_table[i].value == 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"Unknown InputState: '%s'. Use InputState.PRESSED, InputState.RELEASED, "
|
||||||
|
"or legacy strings 'start', 'end'.", name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"InputState must be mcrfpy.InputState enum member, string, or int");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
34
src/PyInputState.h
Normal file
34
src/PyInputState.h
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
#pragma once
|
||||||
|
#include "Common.h"
|
||||||
|
#include "Python.h"
|
||||||
|
|
||||||
|
// Module-level InputState enum class (created at runtime using Python's IntEnum)
|
||||||
|
// Stored as a module attribute: mcrfpy.InputState
|
||||||
|
//
|
||||||
|
// Values:
|
||||||
|
// PRESSED = 0 (corresponds to "start" in legacy API)
|
||||||
|
// RELEASED = 1 (corresponds to "end" in legacy API)
|
||||||
|
//
|
||||||
|
// The enum compares equal to both its name ("PRESSED") and legacy string ("start")
|
||||||
|
|
||||||
|
class PyInputState {
|
||||||
|
public:
|
||||||
|
// Create the InputState enum class and add to module
|
||||||
|
// Returns the enum class (new reference), or NULL on error
|
||||||
|
static PyObject* create_enum_class(PyObject* module);
|
||||||
|
|
||||||
|
// Helper to extract input state from Python arg
|
||||||
|
// Accepts InputState enum, string (for backwards compatibility), int, or None
|
||||||
|
// Returns 1 on success, 0 on error (with exception set)
|
||||||
|
// out_pressed is set to true for PRESSED/start, false for RELEASED/end
|
||||||
|
static int from_arg(PyObject* arg, bool* out_pressed);
|
||||||
|
|
||||||
|
// Convert bool to legacy string name (for passing to callbacks)
|
||||||
|
static const char* to_legacy_string(bool pressed);
|
||||||
|
|
||||||
|
// Cached reference to the InputState enum class for fast type checking
|
||||||
|
static PyObject* input_state_enum_class;
|
||||||
|
|
||||||
|
// Number of input states
|
||||||
|
static const int NUM_INPUT_STATES = 2;
|
||||||
|
};
|
||||||
335
src/PyKey.cpp
Normal file
335
src/PyKey.cpp
Normal file
|
|
@ -0,0 +1,335 @@
|
||||||
|
#include "PyKey.h"
|
||||||
|
#include <sstream>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
// Static storage for cached enum class reference
|
||||||
|
PyObject* PyKey::key_enum_class = nullptr;
|
||||||
|
|
||||||
|
// Key entries - maps enum name to SFML value and legacy string
|
||||||
|
struct KeyEntry {
|
||||||
|
const char* name; // Python enum name (UPPER_SNAKE_CASE)
|
||||||
|
int value; // Integer value (matches sf::Keyboard::Key)
|
||||||
|
const char* legacy; // Legacy string name for backwards compatibility
|
||||||
|
};
|
||||||
|
|
||||||
|
// Complete key table matching SFML's sf::Keyboard::Key enum
|
||||||
|
static const KeyEntry key_table[] = {
|
||||||
|
// Letters (sf::Keyboard::A = 0 through sf::Keyboard::Z = 25)
|
||||||
|
{"A", sf::Keyboard::A, "A"},
|
||||||
|
{"B", sf::Keyboard::B, "B"},
|
||||||
|
{"C", sf::Keyboard::C, "C"},
|
||||||
|
{"D", sf::Keyboard::D, "D"},
|
||||||
|
{"E", sf::Keyboard::E, "E"},
|
||||||
|
{"F", sf::Keyboard::F, "F"},
|
||||||
|
{"G", sf::Keyboard::G, "G"},
|
||||||
|
{"H", sf::Keyboard::H, "H"},
|
||||||
|
{"I", sf::Keyboard::I, "I"},
|
||||||
|
{"J", sf::Keyboard::J, "J"},
|
||||||
|
{"K", sf::Keyboard::K, "K"},
|
||||||
|
{"L", sf::Keyboard::L, "L"},
|
||||||
|
{"M", sf::Keyboard::M, "M"},
|
||||||
|
{"N", sf::Keyboard::N, "N"},
|
||||||
|
{"O", sf::Keyboard::O, "O"},
|
||||||
|
{"P", sf::Keyboard::P, "P"},
|
||||||
|
{"Q", sf::Keyboard::Q, "Q"},
|
||||||
|
{"R", sf::Keyboard::R, "R"},
|
||||||
|
{"S", sf::Keyboard::S, "S"},
|
||||||
|
{"T", sf::Keyboard::T, "T"},
|
||||||
|
{"U", sf::Keyboard::U, "U"},
|
||||||
|
{"V", sf::Keyboard::V, "V"},
|
||||||
|
{"W", sf::Keyboard::W, "W"},
|
||||||
|
{"X", sf::Keyboard::X, "X"},
|
||||||
|
{"Y", sf::Keyboard::Y, "Y"},
|
||||||
|
{"Z", sf::Keyboard::Z, "Z"},
|
||||||
|
|
||||||
|
// Number row (sf::Keyboard::Num0 = 26 through Num9 = 35)
|
||||||
|
{"NUM_0", sf::Keyboard::Num0, "Num0"},
|
||||||
|
{"NUM_1", sf::Keyboard::Num1, "Num1"},
|
||||||
|
{"NUM_2", sf::Keyboard::Num2, "Num2"},
|
||||||
|
{"NUM_3", sf::Keyboard::Num3, "Num3"},
|
||||||
|
{"NUM_4", sf::Keyboard::Num4, "Num4"},
|
||||||
|
{"NUM_5", sf::Keyboard::Num5, "Num5"},
|
||||||
|
{"NUM_6", sf::Keyboard::Num6, "Num6"},
|
||||||
|
{"NUM_7", sf::Keyboard::Num7, "Num7"},
|
||||||
|
{"NUM_8", sf::Keyboard::Num8, "Num8"},
|
||||||
|
{"NUM_9", sf::Keyboard::Num9, "Num9"},
|
||||||
|
|
||||||
|
// Control keys
|
||||||
|
{"ESCAPE", sf::Keyboard::Escape, "Escape"},
|
||||||
|
{"LEFT_CONTROL", sf::Keyboard::LControl, "LControl"},
|
||||||
|
{"LEFT_SHIFT", sf::Keyboard::LShift, "LShift"},
|
||||||
|
{"LEFT_ALT", sf::Keyboard::LAlt, "LAlt"},
|
||||||
|
{"LEFT_SYSTEM", sf::Keyboard::LSystem, "LSystem"},
|
||||||
|
{"RIGHT_CONTROL", sf::Keyboard::RControl, "RControl"},
|
||||||
|
{"RIGHT_SHIFT", sf::Keyboard::RShift, "RShift"},
|
||||||
|
{"RIGHT_ALT", sf::Keyboard::RAlt, "RAlt"},
|
||||||
|
{"RIGHT_SYSTEM", sf::Keyboard::RSystem, "RSystem"},
|
||||||
|
{"MENU", sf::Keyboard::Menu, "Menu"},
|
||||||
|
|
||||||
|
// Punctuation and symbols
|
||||||
|
{"LEFT_BRACKET", sf::Keyboard::LBracket, "LBracket"},
|
||||||
|
{"RIGHT_BRACKET", sf::Keyboard::RBracket, "RBracket"},
|
||||||
|
{"SEMICOLON", sf::Keyboard::Semicolon, "Semicolon"},
|
||||||
|
{"COMMA", sf::Keyboard::Comma, "Comma"},
|
||||||
|
{"PERIOD", sf::Keyboard::Period, "Period"},
|
||||||
|
{"APOSTROPHE", sf::Keyboard::Apostrophe, "Apostrophe"},
|
||||||
|
{"SLASH", sf::Keyboard::Slash, "Slash"},
|
||||||
|
{"BACKSLASH", sf::Keyboard::Backslash, "Backslash"},
|
||||||
|
{"GRAVE", sf::Keyboard::Grave, "Grave"},
|
||||||
|
{"EQUAL", sf::Keyboard::Equal, "Equal"},
|
||||||
|
{"HYPHEN", sf::Keyboard::Hyphen, "Hyphen"},
|
||||||
|
|
||||||
|
// Whitespace and editing
|
||||||
|
{"SPACE", sf::Keyboard::Space, "Space"},
|
||||||
|
{"ENTER", sf::Keyboard::Enter, "Enter"},
|
||||||
|
{"BACKSPACE", sf::Keyboard::Backspace, "Backspace"},
|
||||||
|
{"TAB", sf::Keyboard::Tab, "Tab"},
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
{"PAGE_UP", sf::Keyboard::PageUp, "PageUp"},
|
||||||
|
{"PAGE_DOWN", sf::Keyboard::PageDown, "PageDown"},
|
||||||
|
{"END", sf::Keyboard::End, "End"},
|
||||||
|
{"HOME", sf::Keyboard::Home, "Home"},
|
||||||
|
{"INSERT", sf::Keyboard::Insert, "Insert"},
|
||||||
|
{"DELETE", sf::Keyboard::Delete, "Delete"},
|
||||||
|
|
||||||
|
// Numpad operators
|
||||||
|
{"ADD", sf::Keyboard::Add, "Add"},
|
||||||
|
{"SUBTRACT", sf::Keyboard::Subtract, "Subtract"},
|
||||||
|
{"MULTIPLY", sf::Keyboard::Multiply, "Multiply"},
|
||||||
|
{"DIVIDE", sf::Keyboard::Divide, "Divide"},
|
||||||
|
|
||||||
|
// Arrow keys
|
||||||
|
{"LEFT", sf::Keyboard::Left, "Left"},
|
||||||
|
{"RIGHT", sf::Keyboard::Right, "Right"},
|
||||||
|
{"UP", sf::Keyboard::Up, "Up"},
|
||||||
|
{"DOWN", sf::Keyboard::Down, "Down"},
|
||||||
|
|
||||||
|
// Numpad numbers (sf::Keyboard::Numpad0 = 75 through Numpad9 = 84)
|
||||||
|
{"NUMPAD_0", sf::Keyboard::Numpad0, "Numpad0"},
|
||||||
|
{"NUMPAD_1", sf::Keyboard::Numpad1, "Numpad1"},
|
||||||
|
{"NUMPAD_2", sf::Keyboard::Numpad2, "Numpad2"},
|
||||||
|
{"NUMPAD_3", sf::Keyboard::Numpad3, "Numpad3"},
|
||||||
|
{"NUMPAD_4", sf::Keyboard::Numpad4, "Numpad4"},
|
||||||
|
{"NUMPAD_5", sf::Keyboard::Numpad5, "Numpad5"},
|
||||||
|
{"NUMPAD_6", sf::Keyboard::Numpad6, "Numpad6"},
|
||||||
|
{"NUMPAD_7", sf::Keyboard::Numpad7, "Numpad7"},
|
||||||
|
{"NUMPAD_8", sf::Keyboard::Numpad8, "Numpad8"},
|
||||||
|
{"NUMPAD_9", sf::Keyboard::Numpad9, "Numpad9"},
|
||||||
|
|
||||||
|
// Function keys (sf::Keyboard::F1 = 85 through F15 = 99)
|
||||||
|
{"F1", sf::Keyboard::F1, "F1"},
|
||||||
|
{"F2", sf::Keyboard::F2, "F2"},
|
||||||
|
{"F3", sf::Keyboard::F3, "F3"},
|
||||||
|
{"F4", sf::Keyboard::F4, "F4"},
|
||||||
|
{"F5", sf::Keyboard::F5, "F5"},
|
||||||
|
{"F6", sf::Keyboard::F6, "F6"},
|
||||||
|
{"F7", sf::Keyboard::F7, "F7"},
|
||||||
|
{"F8", sf::Keyboard::F8, "F8"},
|
||||||
|
{"F9", sf::Keyboard::F9, "F9"},
|
||||||
|
{"F10", sf::Keyboard::F10, "F10"},
|
||||||
|
{"F11", sf::Keyboard::F11, "F11"},
|
||||||
|
{"F12", sf::Keyboard::F12, "F12"},
|
||||||
|
{"F13", sf::Keyboard::F13, "F13"},
|
||||||
|
{"F14", sf::Keyboard::F14, "F14"},
|
||||||
|
{"F15", sf::Keyboard::F15, "F15"},
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
{"PAUSE", sf::Keyboard::Pause, "Pause"},
|
||||||
|
|
||||||
|
// Unknown key (for completeness)
|
||||||
|
{"UNKNOWN", sf::Keyboard::Unknown, "Unknown"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int NUM_KEY_ENTRIES = sizeof(key_table) / sizeof(key_table[0]);
|
||||||
|
|
||||||
|
const char* PyKey::to_legacy_string(sf::Keyboard::Key key) {
|
||||||
|
for (int i = 0; i < NUM_KEY_ENTRIES; i++) {
|
||||||
|
if (key_table[i].value == static_cast<int>(key)) {
|
||||||
|
return key_table[i].legacy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
sf::Keyboard::Key PyKey::from_legacy_string(const char* name) {
|
||||||
|
for (int i = 0; i < NUM_KEY_ENTRIES; i++) {
|
||||||
|
if (strcmp(key_table[i].legacy, name) == 0 ||
|
||||||
|
strcmp(key_table[i].name, name) == 0) {
|
||||||
|
return static_cast<sf::Keyboard::Key>(key_table[i].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sf::Keyboard::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyKey::create_enum_class(PyObject* module) {
|
||||||
|
// Build the enum definition dynamically from the table
|
||||||
|
std::ostringstream code;
|
||||||
|
code << "from enum import IntEnum\n\n";
|
||||||
|
|
||||||
|
code << "class Key(IntEnum):\n";
|
||||||
|
code << " \"\"\"Enum representing keyboard keys.\n";
|
||||||
|
code << " \n";
|
||||||
|
code << " Values map to SFML's sf::Keyboard::Key enum.\n";
|
||||||
|
code << " \n";
|
||||||
|
code << " Categories:\n";
|
||||||
|
code << " Letters: A-Z\n";
|
||||||
|
code << " Numbers: NUM_0 through NUM_9 (top row)\n";
|
||||||
|
code << " Numpad: NUMPAD_0 through NUMPAD_9\n";
|
||||||
|
code << " Function: F1 through F15\n";
|
||||||
|
code << " Modifiers: LEFT_SHIFT, RIGHT_SHIFT, LEFT_CONTROL, etc.\n";
|
||||||
|
code << " Navigation: LEFT, RIGHT, UP, DOWN, HOME, END, PAGE_UP, PAGE_DOWN\n";
|
||||||
|
code << " Editing: ENTER, BACKSPACE, DELETE, INSERT, TAB, SPACE\n";
|
||||||
|
code << " Symbols: COMMA, PERIOD, SLASH, SEMICOLON, etc.\n";
|
||||||
|
code << " \n";
|
||||||
|
code << " These enum values compare equal to their legacy string equivalents\n";
|
||||||
|
code << " for backwards compatibility:\n";
|
||||||
|
code << " Key.ESCAPE == 'Escape' # True\n";
|
||||||
|
code << " Key.LEFT_SHIFT == 'LShift' # True\n";
|
||||||
|
code << " \"\"\"\n";
|
||||||
|
|
||||||
|
// Add enum members
|
||||||
|
for (int i = 0; i < NUM_KEY_ENTRIES; i++) {
|
||||||
|
code << " " << key_table[i].name << " = " << key_table[i].value << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add legacy names and custom methods AFTER class creation
|
||||||
|
// (IntEnum doesn't allow dict attributes during class definition)
|
||||||
|
code << "\n# Add legacy name mapping after class creation\n";
|
||||||
|
code << "Key._legacy_names = {\n";
|
||||||
|
for (int i = 0; i < NUM_KEY_ENTRIES; i++) {
|
||||||
|
code << " " << key_table[i].value << ": \"" << key_table[i].legacy << "\",\n";
|
||||||
|
}
|
||||||
|
code << "}\n\n";
|
||||||
|
|
||||||
|
code << R"(
|
||||||
|
def _Key_eq(self, other):
|
||||||
|
if isinstance(other, str):
|
||||||
|
# Check enum name match (e.g., "ESCAPE")
|
||||||
|
if self.name == other:
|
||||||
|
return True
|
||||||
|
# Check legacy name match (e.g., "Escape")
|
||||||
|
legacy = type(self)._legacy_names.get(self.value)
|
||||||
|
if legacy and legacy == other:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
# Fall back to int comparison for IntEnum
|
||||||
|
return int.__eq__(int(self), other)
|
||||||
|
|
||||||
|
Key.__eq__ = _Key_eq
|
||||||
|
Key.__hash__ = lambda self: hash(int(self))
|
||||||
|
Key.__repr__ = lambda self: f"{type(self).__name__}.{self.name}"
|
||||||
|
Key.__str__ = lambda self: self.name
|
||||||
|
)";
|
||||||
|
|
||||||
|
std::string code_str = code.str();
|
||||||
|
|
||||||
|
// Create globals with builtins
|
||||||
|
PyObject* globals = PyDict_New();
|
||||||
|
if (!globals) return NULL;
|
||||||
|
|
||||||
|
PyObject* builtins = PyEval_GetBuiltins();
|
||||||
|
PyDict_SetItemString(globals, "__builtins__", builtins);
|
||||||
|
|
||||||
|
PyObject* locals = PyDict_New();
|
||||||
|
if (!locals) {
|
||||||
|
Py_DECREF(globals);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the code to create the enum
|
||||||
|
PyObject* result = PyRun_String(code_str.c_str(), Py_file_input, globals, locals);
|
||||||
|
if (!result) {
|
||||||
|
Py_DECREF(globals);
|
||||||
|
Py_DECREF(locals);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_DECREF(result);
|
||||||
|
|
||||||
|
// Get the Key class from locals
|
||||||
|
PyObject* key_class = PyDict_GetItemString(locals, "Key");
|
||||||
|
if (!key_class) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Failed to create Key enum class");
|
||||||
|
Py_DECREF(globals);
|
||||||
|
Py_DECREF(locals);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_INCREF(key_class);
|
||||||
|
|
||||||
|
// Cache the reference for fast type checking
|
||||||
|
key_enum_class = key_class;
|
||||||
|
Py_INCREF(key_enum_class);
|
||||||
|
|
||||||
|
// Add to module
|
||||||
|
if (PyModule_AddObject(module, "Key", key_class) < 0) {
|
||||||
|
Py_DECREF(key_class);
|
||||||
|
Py_DECREF(globals);
|
||||||
|
Py_DECREF(locals);
|
||||||
|
key_enum_class = nullptr;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_DECREF(globals);
|
||||||
|
Py_DECREF(locals);
|
||||||
|
|
||||||
|
return key_class;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PyKey::from_arg(PyObject* arg, sf::Keyboard::Key* out_key) {
|
||||||
|
// Accept Key enum member
|
||||||
|
if (key_enum_class && PyObject_IsInstance(arg, key_enum_class)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
*out_key = static_cast<sf::Keyboard::Key>(val);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept int
|
||||||
|
if (PyLong_Check(arg)) {
|
||||||
|
long val = PyLong_AsLong(arg);
|
||||||
|
if (val == -1 && PyErr_Occurred()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (val >= -1 && val < sf::Keyboard::KeyCount) {
|
||||||
|
*out_key = static_cast<sf::Keyboard::Key>(val);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"Invalid Key value: %ld. Must be -1 (Unknown) to %d.", val, sf::Keyboard::KeyCount - 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept string (both new and legacy names)
|
||||||
|
if (PyUnicode_Check(arg)) {
|
||||||
|
const char* name = PyUnicode_AsUTF8(arg);
|
||||||
|
if (!name) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check all entries for both name and legacy match
|
||||||
|
for (int i = 0; i < NUM_KEY_ENTRIES; i++) {
|
||||||
|
if (strcmp(name, key_table[i].name) == 0 ||
|
||||||
|
strcmp(name, key_table[i].legacy) == 0) {
|
||||||
|
*out_key = static_cast<sf::Keyboard::Key>(key_table[i].value);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"Unknown Key: '%s'. Use Key enum members (e.g., Key.ESCAPE, Key.A) "
|
||||||
|
"or legacy strings (e.g., 'Escape', 'A').", name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"Key must be mcrfpy.Key enum member, string, or int");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
44
src/PyKey.h
Normal file
44
src/PyKey.h
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#pragma once
|
||||||
|
#include "Common.h"
|
||||||
|
#include "Python.h"
|
||||||
|
#include <SFML/Window/Keyboard.hpp>
|
||||||
|
|
||||||
|
// Module-level Key enum class (created at runtime using Python's IntEnum)
|
||||||
|
// Stored as a module attribute: mcrfpy.Key
|
||||||
|
//
|
||||||
|
// Values map to sf::Keyboard::Key enum values.
|
||||||
|
// The enum compares equal to both its name ("ESCAPE") and legacy string ("Escape")
|
||||||
|
//
|
||||||
|
// Naming convention:
|
||||||
|
// - Letters: A, B, C, ... Z
|
||||||
|
// - Numbers: NUM_0, NUM_1, ... NUM_9 (top row)
|
||||||
|
// - Numpad: NUMPAD_0, NUMPAD_1, ... NUMPAD_9
|
||||||
|
// - Function keys: F1, F2, ... F15
|
||||||
|
// - Modifiers: LEFT_SHIFT, RIGHT_SHIFT, LEFT_CONTROL, RIGHT_CONTROL, etc.
|
||||||
|
// - Navigation: LEFT, RIGHT, UP, DOWN, HOME, END, PAGE_UP, PAGE_DOWN
|
||||||
|
// - Special: ESCAPE, ENTER, SPACE, TAB, BACKSPACE, DELETE, INSERT, PAUSE
|
||||||
|
|
||||||
|
class PyKey {
|
||||||
|
public:
|
||||||
|
// Create the Key enum class and add to module
|
||||||
|
// Returns the enum class (new reference), or NULL on error
|
||||||
|
static PyObject* create_enum_class(PyObject* module);
|
||||||
|
|
||||||
|
// Helper to extract key from Python arg
|
||||||
|
// Accepts Key enum, string (for backwards compatibility), int, or None
|
||||||
|
// Returns 1 on success, 0 on error (with exception set)
|
||||||
|
static int from_arg(PyObject* arg, sf::Keyboard::Key* out_key);
|
||||||
|
|
||||||
|
// Convert sf::Keyboard::Key to legacy string name (for passing to callbacks)
|
||||||
|
static const char* to_legacy_string(sf::Keyboard::Key key);
|
||||||
|
|
||||||
|
// Convert legacy string to sf::Keyboard::Key
|
||||||
|
// Returns sf::Keyboard::Unknown if not found
|
||||||
|
static sf::Keyboard::Key from_legacy_string(const char* name);
|
||||||
|
|
||||||
|
// Cached reference to the Key enum class for fast type checking
|
||||||
|
static PyObject* key_enum_class;
|
||||||
|
|
||||||
|
// Number of keys (matches sf::Keyboard::KeyCount)
|
||||||
|
static const int NUM_KEYS = sf::Keyboard::KeyCount;
|
||||||
|
};
|
||||||
205
src/PyMouseButton.cpp
Normal file
205
src/PyMouseButton.cpp
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
#include "PyMouseButton.h"
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
// Static storage for cached enum class reference
|
||||||
|
PyObject* PyMouseButton::mouse_button_enum_class = nullptr;
|
||||||
|
|
||||||
|
// MouseButton entries - maps enum name to value and legacy string
|
||||||
|
struct MouseButtonEntry {
|
||||||
|
const char* name; // Python enum name (UPPER_SNAKE_CASE)
|
||||||
|
int value; // Integer value (matches sf::Mouse::Button)
|
||||||
|
const char* legacy; // Legacy string name for backwards compatibility
|
||||||
|
};
|
||||||
|
|
||||||
|
static const MouseButtonEntry mouse_button_table[] = {
|
||||||
|
{"LEFT", sf::Mouse::Left, "left"},
|
||||||
|
{"RIGHT", sf::Mouse::Right, "right"},
|
||||||
|
{"MIDDLE", sf::Mouse::Middle, "middle"},
|
||||||
|
{"X1", sf::Mouse::XButton1, "x1"},
|
||||||
|
{"X2", sf::Mouse::XButton2, "x2"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int NUM_MOUSE_BUTTON_ENTRIES = sizeof(mouse_button_table) / sizeof(mouse_button_table[0]);
|
||||||
|
|
||||||
|
const char* PyMouseButton::to_legacy_string(sf::Mouse::Button button) {
|
||||||
|
for (int i = 0; i < NUM_MOUSE_BUTTON_ENTRIES; i++) {
|
||||||
|
if (mouse_button_table[i].value == static_cast<int>(button)) {
|
||||||
|
return mouse_button_table[i].legacy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "left"; // Default fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyMouseButton::create_enum_class(PyObject* module) {
|
||||||
|
// Build the enum definition dynamically from the table
|
||||||
|
std::ostringstream code;
|
||||||
|
code << "from enum import IntEnum\n\n";
|
||||||
|
|
||||||
|
code << "class MouseButton(IntEnum):\n";
|
||||||
|
code << " \"\"\"Enum representing mouse buttons.\n";
|
||||||
|
code << " \n";
|
||||||
|
code << " Values:\n";
|
||||||
|
code << " LEFT: Left mouse button (legacy: 'left')\n";
|
||||||
|
code << " RIGHT: Right mouse button (legacy: 'right')\n";
|
||||||
|
code << " MIDDLE: Middle mouse button / scroll wheel click (legacy: 'middle')\n";
|
||||||
|
code << " X1: Extra mouse button 1 (legacy: 'x1')\n";
|
||||||
|
code << " X2: Extra mouse button 2 (legacy: 'x2')\n";
|
||||||
|
code << " \n";
|
||||||
|
code << " These enum values compare equal to their legacy string equivalents\n";
|
||||||
|
code << " for backwards compatibility:\n";
|
||||||
|
code << " MouseButton.LEFT == 'left' # True\n";
|
||||||
|
code << " MouseButton.RIGHT == 'right' # True\n";
|
||||||
|
code << " \"\"\"\n";
|
||||||
|
|
||||||
|
// Add enum members
|
||||||
|
for (int i = 0; i < NUM_MOUSE_BUTTON_ENTRIES; i++) {
|
||||||
|
code << " " << mouse_button_table[i].name << " = " << mouse_button_table[i].value << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add legacy names and custom methods AFTER class creation
|
||||||
|
// (IntEnum doesn't allow dict attributes during class definition)
|
||||||
|
code << "\n# Add legacy name mapping after class creation\n";
|
||||||
|
code << "MouseButton._legacy_names = {\n";
|
||||||
|
for (int i = 0; i < NUM_MOUSE_BUTTON_ENTRIES; i++) {
|
||||||
|
code << " " << mouse_button_table[i].value << ": \"" << mouse_button_table[i].legacy << "\",\n";
|
||||||
|
}
|
||||||
|
code << "}\n\n";
|
||||||
|
|
||||||
|
code << R"(
|
||||||
|
def _MouseButton_eq(self, other):
|
||||||
|
if isinstance(other, str):
|
||||||
|
# Check enum name match (e.g., "LEFT")
|
||||||
|
if self.name == other:
|
||||||
|
return True
|
||||||
|
# Check legacy name match (e.g., "left")
|
||||||
|
legacy = type(self)._legacy_names.get(self.value)
|
||||||
|
if legacy and legacy == other:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
# Fall back to int comparison for IntEnum
|
||||||
|
return int.__eq__(int(self), other)
|
||||||
|
|
||||||
|
MouseButton.__eq__ = _MouseButton_eq
|
||||||
|
MouseButton.__hash__ = lambda self: hash(int(self))
|
||||||
|
MouseButton.__repr__ = lambda self: f"{type(self).__name__}.{self.name}"
|
||||||
|
MouseButton.__str__ = lambda self: self.name
|
||||||
|
)";
|
||||||
|
|
||||||
|
std::string code_str = code.str();
|
||||||
|
|
||||||
|
// Create globals with builtins
|
||||||
|
PyObject* globals = PyDict_New();
|
||||||
|
if (!globals) return NULL;
|
||||||
|
|
||||||
|
PyObject* builtins = PyEval_GetBuiltins();
|
||||||
|
PyDict_SetItemString(globals, "__builtins__", builtins);
|
||||||
|
|
||||||
|
PyObject* locals = PyDict_New();
|
||||||
|
if (!locals) {
|
||||||
|
Py_DECREF(globals);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the code to create the enum
|
||||||
|
PyObject* result = PyRun_String(code_str.c_str(), Py_file_input, globals, locals);
|
||||||
|
if (!result) {
|
||||||
|
Py_DECREF(globals);
|
||||||
|
Py_DECREF(locals);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_DECREF(result);
|
||||||
|
|
||||||
|
// Get the MouseButton class from locals
|
||||||
|
PyObject* mouse_button_class = PyDict_GetItemString(locals, "MouseButton");
|
||||||
|
if (!mouse_button_class) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Failed to create MouseButton enum class");
|
||||||
|
Py_DECREF(globals);
|
||||||
|
Py_DECREF(locals);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_INCREF(mouse_button_class);
|
||||||
|
|
||||||
|
// Cache the reference for fast type checking
|
||||||
|
mouse_button_enum_class = mouse_button_class;
|
||||||
|
Py_INCREF(mouse_button_enum_class);
|
||||||
|
|
||||||
|
// Add to module
|
||||||
|
if (PyModule_AddObject(module, "MouseButton", mouse_button_class) < 0) {
|
||||||
|
Py_DECREF(mouse_button_class);
|
||||||
|
Py_DECREF(globals);
|
||||||
|
Py_DECREF(locals);
|
||||||
|
mouse_button_enum_class = nullptr;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_DECREF(globals);
|
||||||
|
Py_DECREF(locals);
|
||||||
|
|
||||||
|
return mouse_button_class;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PyMouseButton::from_arg(PyObject* arg, sf::Mouse::Button* out_button) {
|
||||||
|
// Accept MouseButton enum member
|
||||||
|
if (mouse_button_enum_class && PyObject_IsInstance(arg, mouse_button_enum_class)) {
|
||||||
|
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_MOUSE_BUTTON_ENTRIES) {
|
||||||
|
*out_button = static_cast<sf::Mouse::Button>(val);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"Invalid MouseButton value: %ld. Must be 0-4.", val);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept int
|
||||||
|
if (PyLong_Check(arg)) {
|
||||||
|
long val = PyLong_AsLong(arg);
|
||||||
|
if (val == -1 && PyErr_Occurred()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (val >= 0 && val < NUM_MOUSE_BUTTON_ENTRIES) {
|
||||||
|
*out_button = static_cast<sf::Mouse::Button>(val);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"Invalid MouseButton value: %ld. Must be 0 (LEFT), 1 (RIGHT), 2 (MIDDLE), "
|
||||||
|
"3 (X1), or 4 (X2).", val);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept string (both new and legacy names)
|
||||||
|
if (PyUnicode_Check(arg)) {
|
||||||
|
const char* name = PyUnicode_AsUTF8(arg);
|
||||||
|
if (!name) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check all entries for both name and legacy match
|
||||||
|
for (int i = 0; i < NUM_MOUSE_BUTTON_ENTRIES; i++) {
|
||||||
|
if (strcmp(name, mouse_button_table[i].name) == 0 ||
|
||||||
|
strcmp(name, mouse_button_table[i].legacy) == 0) {
|
||||||
|
*out_button = static_cast<sf::Mouse::Button>(mouse_button_table[i].value);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"Unknown MouseButton: '%s'. Use MouseButton.LEFT, MouseButton.RIGHT, "
|
||||||
|
"MouseButton.MIDDLE, MouseButton.X1, MouseButton.X2, "
|
||||||
|
"or legacy strings 'left', 'right', 'middle', 'x1', 'x2'.", name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"MouseButton must be mcrfpy.MouseButton enum member, string, or int");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
37
src/PyMouseButton.h
Normal file
37
src/PyMouseButton.h
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
#include "Common.h"
|
||||||
|
#include "Python.h"
|
||||||
|
#include <SFML/Window/Mouse.hpp>
|
||||||
|
|
||||||
|
// Module-level MouseButton enum class (created at runtime using Python's IntEnum)
|
||||||
|
// Stored as a module attribute: mcrfpy.MouseButton
|
||||||
|
//
|
||||||
|
// Values map to sf::Mouse::Button:
|
||||||
|
// LEFT = 0 (corresponds to "left" in legacy API)
|
||||||
|
// RIGHT = 1 (corresponds to "right" in legacy API)
|
||||||
|
// MIDDLE = 2 (corresponds to "middle" in legacy API)
|
||||||
|
// X1 = 3 (extra button 1)
|
||||||
|
// X2 = 4 (extra button 2)
|
||||||
|
//
|
||||||
|
// The enum compares equal to both its name ("LEFT") and legacy string ("left")
|
||||||
|
|
||||||
|
class PyMouseButton {
|
||||||
|
public:
|
||||||
|
// Create the MouseButton enum class and add to module
|
||||||
|
// Returns the enum class (new reference), or NULL on error
|
||||||
|
static PyObject* create_enum_class(PyObject* module);
|
||||||
|
|
||||||
|
// Helper to extract mouse button from Python arg
|
||||||
|
// Accepts MouseButton enum, string (for backwards compatibility), int, or None
|
||||||
|
// Returns 1 on success, 0 on error (with exception set)
|
||||||
|
static int from_arg(PyObject* arg, sf::Mouse::Button* out_button);
|
||||||
|
|
||||||
|
// Convert sf::Mouse::Button to legacy string name (for passing to callbacks)
|
||||||
|
static const char* to_legacy_string(sf::Mouse::Button button);
|
||||||
|
|
||||||
|
// Cached reference to the MouseButton enum class for fast type checking
|
||||||
|
static PyObject* mouse_button_enum_class;
|
||||||
|
|
||||||
|
// Number of mouse buttons
|
||||||
|
static const int NUM_MOUSE_BUTTONS = 5;
|
||||||
|
};
|
||||||
201
stubs/mcrfpy.pyi
201
stubs/mcrfpy.pyi
|
|
@ -4,11 +4,212 @@ Core game engine interface for creating roguelike games with Python.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, List, Dict, Tuple, Optional, Callable, Union, overload
|
from typing import Any, List, Dict, Tuple, Optional, Callable, Union, overload
|
||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
# Type aliases
|
# Type aliases
|
||||||
UIElement = Union['Frame', 'Caption', 'Sprite', 'Grid', 'Line', 'Circle', 'Arc']
|
UIElement = Union['Frame', 'Caption', 'Sprite', 'Grid', 'Line', 'Circle', 'Arc']
|
||||||
Transition = Union[str, None]
|
Transition = Union[str, None]
|
||||||
|
|
||||||
|
# Enums
|
||||||
|
|
||||||
|
class Key(IntEnum):
|
||||||
|
"""Keyboard key codes.
|
||||||
|
|
||||||
|
These enum values compare equal to their legacy string equivalents
|
||||||
|
for backwards compatibility:
|
||||||
|
Key.ESCAPE == 'Escape' # True
|
||||||
|
Key.LEFT_SHIFT == 'LShift' # True
|
||||||
|
"""
|
||||||
|
# Letters
|
||||||
|
A = 0
|
||||||
|
B = 1
|
||||||
|
C = 2
|
||||||
|
D = 3
|
||||||
|
E = 4
|
||||||
|
F = 5
|
||||||
|
G = 6
|
||||||
|
H = 7
|
||||||
|
I = 8
|
||||||
|
J = 9
|
||||||
|
K = 10
|
||||||
|
L = 11
|
||||||
|
M = 12
|
||||||
|
N = 13
|
||||||
|
O = 14
|
||||||
|
P = 15
|
||||||
|
Q = 16
|
||||||
|
R = 17
|
||||||
|
S = 18
|
||||||
|
T = 19
|
||||||
|
U = 20
|
||||||
|
V = 21
|
||||||
|
W = 22
|
||||||
|
X = 23
|
||||||
|
Y = 24
|
||||||
|
Z = 25
|
||||||
|
# Number row
|
||||||
|
NUM_0 = 26
|
||||||
|
NUM_1 = 27
|
||||||
|
NUM_2 = 28
|
||||||
|
NUM_3 = 29
|
||||||
|
NUM_4 = 30
|
||||||
|
NUM_5 = 31
|
||||||
|
NUM_6 = 32
|
||||||
|
NUM_7 = 33
|
||||||
|
NUM_8 = 34
|
||||||
|
NUM_9 = 35
|
||||||
|
# Control keys
|
||||||
|
ESCAPE = 36
|
||||||
|
LEFT_CONTROL = 37
|
||||||
|
LEFT_SHIFT = 38
|
||||||
|
LEFT_ALT = 39
|
||||||
|
LEFT_SYSTEM = 40
|
||||||
|
RIGHT_CONTROL = 41
|
||||||
|
RIGHT_SHIFT = 42
|
||||||
|
RIGHT_ALT = 43
|
||||||
|
RIGHT_SYSTEM = 44
|
||||||
|
MENU = 45
|
||||||
|
# Punctuation
|
||||||
|
LEFT_BRACKET = 46
|
||||||
|
RIGHT_BRACKET = 47
|
||||||
|
SEMICOLON = 48
|
||||||
|
COMMA = 49
|
||||||
|
PERIOD = 50
|
||||||
|
APOSTROPHE = 51
|
||||||
|
SLASH = 52
|
||||||
|
BACKSLASH = 53
|
||||||
|
GRAVE = 54
|
||||||
|
EQUAL = 55
|
||||||
|
HYPHEN = 56
|
||||||
|
# Whitespace/editing
|
||||||
|
SPACE = 57
|
||||||
|
ENTER = 58
|
||||||
|
BACKSPACE = 59
|
||||||
|
TAB = 60
|
||||||
|
# Navigation
|
||||||
|
PAGE_UP = 61
|
||||||
|
PAGE_DOWN = 62
|
||||||
|
END = 63
|
||||||
|
HOME = 64
|
||||||
|
INSERT = 65
|
||||||
|
DELETE = 66
|
||||||
|
# Numpad operators
|
||||||
|
ADD = 67
|
||||||
|
SUBTRACT = 68
|
||||||
|
MULTIPLY = 69
|
||||||
|
DIVIDE = 70
|
||||||
|
# Arrows
|
||||||
|
LEFT = 71
|
||||||
|
RIGHT = 72
|
||||||
|
UP = 73
|
||||||
|
DOWN = 74
|
||||||
|
# Numpad numbers
|
||||||
|
NUMPAD_0 = 75
|
||||||
|
NUMPAD_1 = 76
|
||||||
|
NUMPAD_2 = 77
|
||||||
|
NUMPAD_3 = 78
|
||||||
|
NUMPAD_4 = 79
|
||||||
|
NUMPAD_5 = 80
|
||||||
|
NUMPAD_6 = 81
|
||||||
|
NUMPAD_7 = 82
|
||||||
|
NUMPAD_8 = 83
|
||||||
|
NUMPAD_9 = 84
|
||||||
|
# Function keys
|
||||||
|
F1 = 85
|
||||||
|
F2 = 86
|
||||||
|
F3 = 87
|
||||||
|
F4 = 88
|
||||||
|
F5 = 89
|
||||||
|
F6 = 90
|
||||||
|
F7 = 91
|
||||||
|
F8 = 92
|
||||||
|
F9 = 93
|
||||||
|
F10 = 94
|
||||||
|
F11 = 95
|
||||||
|
F12 = 96
|
||||||
|
F13 = 97
|
||||||
|
F14 = 98
|
||||||
|
F15 = 99
|
||||||
|
# Misc
|
||||||
|
PAUSE = 100
|
||||||
|
UNKNOWN = -1
|
||||||
|
|
||||||
|
class MouseButton(IntEnum):
|
||||||
|
"""Mouse button codes.
|
||||||
|
|
||||||
|
These enum values compare equal to their legacy string equivalents
|
||||||
|
for backwards compatibility:
|
||||||
|
MouseButton.LEFT == 'left' # True
|
||||||
|
MouseButton.RIGHT == 'right' # True
|
||||||
|
"""
|
||||||
|
LEFT = 0
|
||||||
|
RIGHT = 1
|
||||||
|
MIDDLE = 2
|
||||||
|
X1 = 3
|
||||||
|
X2 = 4
|
||||||
|
|
||||||
|
class InputState(IntEnum):
|
||||||
|
"""Input event states (pressed/released).
|
||||||
|
|
||||||
|
These enum values compare equal to their legacy string equivalents
|
||||||
|
for backwards compatibility:
|
||||||
|
InputState.PRESSED == 'start' # True
|
||||||
|
InputState.RELEASED == 'end' # True
|
||||||
|
"""
|
||||||
|
PRESSED = 0
|
||||||
|
RELEASED = 1
|
||||||
|
|
||||||
|
class Easing(IntEnum):
|
||||||
|
"""Easing functions for animations."""
|
||||||
|
LINEAR = 0
|
||||||
|
EASE_IN = 1
|
||||||
|
EASE_OUT = 2
|
||||||
|
EASE_IN_OUT = 3
|
||||||
|
EASE_IN_QUAD = 4
|
||||||
|
EASE_OUT_QUAD = 5
|
||||||
|
EASE_IN_OUT_QUAD = 6
|
||||||
|
EASE_IN_CUBIC = 7
|
||||||
|
EASE_OUT_CUBIC = 8
|
||||||
|
EASE_IN_OUT_CUBIC = 9
|
||||||
|
EASE_IN_QUART = 10
|
||||||
|
EASE_OUT_QUART = 11
|
||||||
|
EASE_IN_OUT_QUART = 12
|
||||||
|
EASE_IN_SINE = 13
|
||||||
|
EASE_OUT_SINE = 14
|
||||||
|
EASE_IN_OUT_SINE = 15
|
||||||
|
EASE_IN_EXPO = 16
|
||||||
|
EASE_OUT_EXPO = 17
|
||||||
|
EASE_IN_OUT_EXPO = 18
|
||||||
|
EASE_IN_CIRC = 19
|
||||||
|
EASE_OUT_CIRC = 20
|
||||||
|
EASE_IN_OUT_CIRC = 21
|
||||||
|
EASE_IN_ELASTIC = 22
|
||||||
|
EASE_OUT_ELASTIC = 23
|
||||||
|
EASE_IN_OUT_ELASTIC = 24
|
||||||
|
EASE_IN_BACK = 25
|
||||||
|
EASE_OUT_BACK = 26
|
||||||
|
EASE_IN_OUT_BACK = 27
|
||||||
|
EASE_IN_BOUNCE = 28
|
||||||
|
EASE_OUT_BOUNCE = 29
|
||||||
|
EASE_IN_OUT_BOUNCE = 30
|
||||||
|
|
||||||
|
class FOV(IntEnum):
|
||||||
|
"""Field of view algorithms for visibility calculations."""
|
||||||
|
BASIC = 0
|
||||||
|
DIAMOND = 1
|
||||||
|
SHADOW = 2
|
||||||
|
PERMISSIVE_0 = 3
|
||||||
|
PERMISSIVE_1 = 4
|
||||||
|
PERMISSIVE_2 = 5
|
||||||
|
PERMISSIVE_3 = 6
|
||||||
|
PERMISSIVE_4 = 7
|
||||||
|
PERMISSIVE_5 = 8
|
||||||
|
PERMISSIVE_6 = 9
|
||||||
|
PERMISSIVE_7 = 10
|
||||||
|
PERMISSIVE_8 = 11
|
||||||
|
RESTRICTIVE = 12
|
||||||
|
SYMMETRIC_SHADOWCAST = 13
|
||||||
|
|
||||||
# Classes
|
# Classes
|
||||||
|
|
||||||
class Color:
|
class Color:
|
||||||
|
|
|
||||||
138
tests/unit/test_input_enums.py
Normal file
138
tests/unit/test_input_enums.py
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Test Key, MouseButton, and InputState enum functionality.
|
||||||
|
|
||||||
|
Tests the new input-related enums that provide type-safe alternatives to
|
||||||
|
string-based key codes, mouse buttons, and event states.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import mcrfpy
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def test_key_enum():
|
||||||
|
"""Test Key enum members and backwards compatibility."""
|
||||||
|
print("Testing Key enum...")
|
||||||
|
|
||||||
|
# Test that enum exists and has expected members
|
||||||
|
assert hasattr(mcrfpy, 'Key'), "mcrfpy.Key should exist"
|
||||||
|
assert hasattr(mcrfpy.Key, 'A'), "Key.A should exist"
|
||||||
|
assert hasattr(mcrfpy.Key, 'ESCAPE'), "Key.ESCAPE should exist"
|
||||||
|
assert hasattr(mcrfpy.Key, 'LEFT_SHIFT'), "Key.LEFT_SHIFT should exist"
|
||||||
|
|
||||||
|
# Test int values
|
||||||
|
assert int(mcrfpy.Key.A) == 0, "Key.A should be 0"
|
||||||
|
assert int(mcrfpy.Key.ESCAPE) == 36, "Key.ESCAPE should be 36"
|
||||||
|
|
||||||
|
# Test backwards compatibility with legacy strings
|
||||||
|
assert mcrfpy.Key.A == "A", "Key.A should equal 'A'"
|
||||||
|
assert mcrfpy.Key.ESCAPE == "Escape", "Key.ESCAPE should equal 'Escape'"
|
||||||
|
assert mcrfpy.Key.LEFT_SHIFT == "LShift", "Key.LEFT_SHIFT should equal 'LShift'"
|
||||||
|
assert mcrfpy.Key.RIGHT_CONTROL == "RControl", "Key.RIGHT_CONTROL should equal 'RControl'"
|
||||||
|
assert mcrfpy.Key.NUM_1 == "Num1", "Key.NUM_1 should equal 'Num1'"
|
||||||
|
assert mcrfpy.Key.NUMPAD_5 == "Numpad5", "Key.NUMPAD_5 should equal 'Numpad5'"
|
||||||
|
assert mcrfpy.Key.F12 == "F12", "Key.F12 should equal 'F12'"
|
||||||
|
assert mcrfpy.Key.SPACE == "Space", "Key.SPACE should equal 'Space'"
|
||||||
|
|
||||||
|
# Test that enum name also matches
|
||||||
|
assert mcrfpy.Key.ESCAPE == "ESCAPE", "Key.ESCAPE should also equal 'ESCAPE'"
|
||||||
|
|
||||||
|
print(" All Key tests passed")
|
||||||
|
|
||||||
|
|
||||||
|
def test_mouse_button_enum():
|
||||||
|
"""Test MouseButton enum members and backwards compatibility."""
|
||||||
|
print("Testing MouseButton enum...")
|
||||||
|
|
||||||
|
# Test that enum exists and has expected members
|
||||||
|
assert hasattr(mcrfpy, 'MouseButton'), "mcrfpy.MouseButton should exist"
|
||||||
|
assert hasattr(mcrfpy.MouseButton, 'LEFT'), "MouseButton.LEFT should exist"
|
||||||
|
assert hasattr(mcrfpy.MouseButton, 'RIGHT'), "MouseButton.RIGHT should exist"
|
||||||
|
assert hasattr(mcrfpy.MouseButton, 'MIDDLE'), "MouseButton.MIDDLE should exist"
|
||||||
|
|
||||||
|
# Test int values
|
||||||
|
assert int(mcrfpy.MouseButton.LEFT) == 0, "MouseButton.LEFT should be 0"
|
||||||
|
assert int(mcrfpy.MouseButton.RIGHT) == 1, "MouseButton.RIGHT should be 1"
|
||||||
|
assert int(mcrfpy.MouseButton.MIDDLE) == 2, "MouseButton.MIDDLE should be 2"
|
||||||
|
|
||||||
|
# Test backwards compatibility with legacy strings
|
||||||
|
assert mcrfpy.MouseButton.LEFT == "left", "MouseButton.LEFT should equal 'left'"
|
||||||
|
assert mcrfpy.MouseButton.RIGHT == "right", "MouseButton.RIGHT should equal 'right'"
|
||||||
|
assert mcrfpy.MouseButton.MIDDLE == "middle", "MouseButton.MIDDLE should equal 'middle'"
|
||||||
|
assert mcrfpy.MouseButton.X1 == "x1", "MouseButton.X1 should equal 'x1'"
|
||||||
|
assert mcrfpy.MouseButton.X2 == "x2", "MouseButton.X2 should equal 'x2'"
|
||||||
|
|
||||||
|
# Test that enum name also matches
|
||||||
|
assert mcrfpy.MouseButton.LEFT == "LEFT", "MouseButton.LEFT should also equal 'LEFT'"
|
||||||
|
|
||||||
|
print(" All MouseButton tests passed")
|
||||||
|
|
||||||
|
|
||||||
|
def test_input_state_enum():
|
||||||
|
"""Test InputState enum members and backwards compatibility."""
|
||||||
|
print("Testing InputState enum...")
|
||||||
|
|
||||||
|
# Test that enum exists and has expected members
|
||||||
|
assert hasattr(mcrfpy, 'InputState'), "mcrfpy.InputState should exist"
|
||||||
|
assert hasattr(mcrfpy.InputState, 'PRESSED'), "InputState.PRESSED should exist"
|
||||||
|
assert hasattr(mcrfpy.InputState, 'RELEASED'), "InputState.RELEASED should exist"
|
||||||
|
|
||||||
|
# Test int values
|
||||||
|
assert int(mcrfpy.InputState.PRESSED) == 0, "InputState.PRESSED should be 0"
|
||||||
|
assert int(mcrfpy.InputState.RELEASED) == 1, "InputState.RELEASED should be 1"
|
||||||
|
|
||||||
|
# Test backwards compatibility with legacy strings
|
||||||
|
assert mcrfpy.InputState.PRESSED == "start", "InputState.PRESSED should equal 'start'"
|
||||||
|
assert mcrfpy.InputState.RELEASED == "end", "InputState.RELEASED should equal 'end'"
|
||||||
|
|
||||||
|
# Test that enum name also matches
|
||||||
|
assert mcrfpy.InputState.PRESSED == "PRESSED", "InputState.PRESSED should also equal 'PRESSED'"
|
||||||
|
assert mcrfpy.InputState.RELEASED == "RELEASED", "InputState.RELEASED should also equal 'RELEASED'"
|
||||||
|
|
||||||
|
print(" All InputState tests passed")
|
||||||
|
|
||||||
|
|
||||||
|
def test_enum_repr():
|
||||||
|
"""Test that enum repr/str work correctly."""
|
||||||
|
print("Testing enum repr/str...")
|
||||||
|
|
||||||
|
# Test repr
|
||||||
|
assert "Key.ESCAPE" in repr(mcrfpy.Key.ESCAPE), f"repr should contain 'Key.ESCAPE', got {repr(mcrfpy.Key.ESCAPE)}"
|
||||||
|
assert "MouseButton.LEFT" in repr(mcrfpy.MouseButton.LEFT), f"repr should contain 'MouseButton.LEFT'"
|
||||||
|
assert "InputState.PRESSED" in repr(mcrfpy.InputState.PRESSED), f"repr should contain 'InputState.PRESSED'"
|
||||||
|
|
||||||
|
# Test str
|
||||||
|
assert str(mcrfpy.Key.ESCAPE) == "ESCAPE", f"str(Key.ESCAPE) should be 'ESCAPE', got {str(mcrfpy.Key.ESCAPE)}"
|
||||||
|
assert str(mcrfpy.MouseButton.LEFT) == "LEFT", f"str(MouseButton.LEFT) should be 'LEFT'"
|
||||||
|
assert str(mcrfpy.InputState.PRESSED) == "PRESSED", f"str(InputState.PRESSED) should be 'PRESSED'"
|
||||||
|
|
||||||
|
print(" All repr/str tests passed")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run all enum tests."""
|
||||||
|
print("=" * 50)
|
||||||
|
print("Input Enum Unit Tests")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
test_key_enum()
|
||||||
|
test_mouse_button_enum()
|
||||||
|
test_input_state_enum()
|
||||||
|
test_enum_repr()
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 50)
|
||||||
|
print("All tests PASSED")
|
||||||
|
print("=" * 50)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
except AssertionError as e:
|
||||||
|
print(f"\nTest FAILED: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nUnexpected error: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue