Remove legacy string enum comparisons from InputState/Key/MouseButton, closes #306

Removed custom __eq__/__ne__ that allowed comparing enums to legacy string
names (e.g., Key.ESCAPE == "Escape"). Removed _legacy_names dicts and
to_legacy_string() functions. Kept from_legacy_string() in PyKey.cpp as
it's used by C++ event dispatch. Updated ~50 Python test/demo/cookbook
files to use enum members instead of string comparisons. Also updates
grid.position -> grid.pos in files that had both types of changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
John McCardle 2026-04-09 22:19:02 -04:00
commit 6d5e99a114
52 changed files with 372 additions and 533 deletions

View file

@ -4,24 +4,19 @@
// Static storage for cached enum class reference // Static storage for cached enum class reference
PyObject* PyInputState::input_state_enum_class = nullptr; PyObject* PyInputState::input_state_enum_class = nullptr;
// InputState entries - maps enum name to value and legacy string // InputState entries - maps enum name to value
struct InputStateEntry { struct InputStateEntry {
const char* name; // Python enum name (UPPER_SNAKE_CASE) const char* name; // Python enum name (UPPER_SNAKE_CASE)
int value; // Integer value int value; // Integer value
const char* legacy; // Legacy string name for backwards compatibility
}; };
static const InputStateEntry input_state_table[] = { static const InputStateEntry input_state_table[] = {
{"PRESSED", 0, "start"}, {"PRESSED", 0},
{"RELEASED", 1, "end"}, {"RELEASED", 1},
}; };
static const int NUM_INPUT_STATE_ENTRIES = sizeof(input_state_table) / sizeof(input_state_table[0]); 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) { PyObject* PyInputState::create_enum_class(PyObject* module) {
// Build the enum definition dynamically from the table // Build the enum definition dynamically from the table
std::ostringstream code; std::ostringstream code;
@ -31,13 +26,8 @@ PyObject* PyInputState::create_enum_class(PyObject* module) {
code << " \"\"\"Enum representing input event states (pressed/released).\n"; code << " \"\"\"Enum representing input event states (pressed/released).\n";
code << " \n"; code << " \n";
code << " Values:\n"; code << " Values:\n";
code << " PRESSED: Key or button was pressed (legacy: 'start')\n"; code << " PRESSED: Key or button was pressed\n";
code << " RELEASED: Key or button was released (legacy: 'end')\n"; code << " RELEASED: Key or button was released\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"; code << " \"\"\"\n";
// Add enum members // Add enum members
@ -45,42 +35,10 @@ PyObject* PyInputState::create_enum_class(PyObject* module) {
code << " " << input_state_table[i].name << " = " << input_state_table[i].value << "\n"; code << " " << input_state_table[i].name << " = " << input_state_table[i].value << "\n";
} }
// Add legacy names and custom methods AFTER class creation code << "\n";
// (IntEnum doesn't allow dict attributes during class definition) code << "InputState.__hash__ = lambda self: hash(int(self))\n";
code << "\n# Add legacy name mapping after class creation\n"; code << "InputState.__repr__ = lambda self: f\"{type(self).__name__}.{self.name}\"\n";
code << "InputState._legacy_names = {\n"; code << "InputState.__str__ = lambda self: self.name\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
def _InputState_ne(self, other):
result = type(self).__eq__(self, other)
if result is NotImplemented:
return result
return not result
InputState.__ne__ = _InputState_ne
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(); std::string code_str = code.str();
@ -167,25 +125,22 @@ int PyInputState::from_arg(PyObject* arg, bool* out_pressed) {
return 0; return 0;
} }
// Accept string (both new and legacy names) // Accept string (enum name only)
if (PyUnicode_Check(arg)) { if (PyUnicode_Check(arg)) {
const char* name = PyUnicode_AsUTF8(arg); const char* name = PyUnicode_AsUTF8(arg);
if (!name) { if (!name) {
return 0; return 0;
} }
// Check all entries for both name and legacy match
for (int i = 0; i < NUM_INPUT_STATE_ENTRIES; i++) { for (int i = 0; i < NUM_INPUT_STATE_ENTRIES; i++) {
if (strcmp(name, input_state_table[i].name) == 0 || 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); *out_pressed = (input_state_table[i].value == 0);
return 1; return 1;
} }
} }
PyErr_Format(PyExc_ValueError, PyErr_Format(PyExc_ValueError,
"Unknown InputState: '%s'. Use InputState.PRESSED, InputState.RELEASED, " "Unknown InputState: '%s'. Use InputState.PRESSED or InputState.RELEASED.", name);
"or legacy strings 'start', 'end'.", name);
return 0; return 0;
} }

View file

@ -6,10 +6,8 @@
// Stored as a module attribute: mcrfpy.InputState // Stored as a module attribute: mcrfpy.InputState
// //
// Values: // Values:
// PRESSED = 0 (corresponds to "start" in legacy API) // PRESSED = 0
// RELEASED = 1 (corresponds to "end" in legacy API) // RELEASED = 1
//
// The enum compares equal to both its name ("PRESSED") and legacy string ("start")
class PyInputState { class PyInputState {
public: public:
@ -18,14 +16,11 @@ public:
static PyObject* create_enum_class(PyObject* module); static PyObject* create_enum_class(PyObject* module);
// Helper to extract input state from Python arg // Helper to extract input state from Python arg
// Accepts InputState enum, string (for backwards compatibility), int, or None // Accepts InputState enum, string (enum name), or int
// Returns 1 on success, 0 on error (with exception set) // Returns 1 on success, 0 on error (with exception set)
// out_pressed is set to true for PRESSED/start, false for RELEASED/end // out_pressed is set to true for PRESSED, false for RELEASED
static int from_arg(PyObject* arg, bool* out_pressed); 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 // Cached reference to the InputState enum class for fast type checking
static PyObject* input_state_enum_class; static PyObject* input_state_enum_class;

View file

@ -143,15 +143,6 @@ static const KeyEntry key_table[] = {
static const int NUM_KEY_ENTRIES = sizeof(key_table) / sizeof(key_table[0]); 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) { sf::Keyboard::Key PyKey::from_legacy_string(const char* name) {
for (int i = 0; i < NUM_KEY_ENTRIES; i++) { for (int i = 0; i < NUM_KEY_ENTRIES; i++) {
if (strcmp(key_table[i].legacy, name) == 0 || if (strcmp(key_table[i].legacy, name) == 0 ||
@ -181,11 +172,6 @@ PyObject* PyKey::create_enum_class(PyObject* module) {
code << " Navigation: LEFT, RIGHT, UP, DOWN, HOME, END, PAGE_UP, PAGE_DOWN\n"; code << " Navigation: LEFT, RIGHT, UP, DOWN, HOME, END, PAGE_UP, PAGE_DOWN\n";
code << " Editing: ENTER, BACKSPACE, DELETE, INSERT, TAB, SPACE\n"; code << " Editing: ENTER, BACKSPACE, DELETE, INSERT, TAB, SPACE\n";
code << " Symbols: COMMA, PERIOD, SLASH, SEMICOLON, etc.\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"; code << " \"\"\"\n";
// Add enum members // Add enum members
@ -193,42 +179,10 @@ PyObject* PyKey::create_enum_class(PyObject* module) {
code << " " << key_table[i].name << " = " << key_table[i].value << "\n"; code << " " << key_table[i].name << " = " << key_table[i].value << "\n";
} }
// Add legacy names and custom methods AFTER class creation code << "\n";
// (IntEnum doesn't allow dict attributes during class definition) code << "Key.__hash__ = lambda self: hash(int(self))\n";
code << "\n# Add legacy name mapping after class creation\n"; code << "Key.__repr__ = lambda self: f\"{type(self).__name__}.{self.name}\"\n";
code << "Key._legacy_names = {\n"; code << "Key.__str__ = lambda self: self.name\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
def _Key_ne(self, other):
result = type(self).__eq__(self, other)
if result is NotImplemented:
return result
return not result
Key.__ne__ = _Key_ne
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(); std::string code_str = code.str();
@ -315,25 +269,22 @@ int PyKey::from_arg(PyObject* arg, sf::Keyboard::Key* out_key) {
return 0; return 0;
} }
// Accept string (both new and legacy names) // Accept string (enum name only)
if (PyUnicode_Check(arg)) { if (PyUnicode_Check(arg)) {
const char* name = PyUnicode_AsUTF8(arg); const char* name = PyUnicode_AsUTF8(arg);
if (!name) { if (!name) {
return 0; return 0;
} }
// Check all entries for both name and legacy match
for (int i = 0; i < NUM_KEY_ENTRIES; i++) { for (int i = 0; i < NUM_KEY_ENTRIES; i++) {
if (strcmp(name, key_table[i].name) == 0 || 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); *out_key = static_cast<sf::Keyboard::Key>(key_table[i].value);
return 1; return 1;
} }
} }
PyErr_Format(PyExc_ValueError, PyErr_Format(PyExc_ValueError,
"Unknown Key: '%s'. Use Key enum members (e.g., Key.ESCAPE, Key.A) " "Unknown Key: '%s'. Use Key enum members (e.g., Key.ESCAPE, Key.A).", name);
"or legacy strings (e.g., 'Escape', 'A').", name);
return 0; return 0;
} }

View file

@ -6,7 +6,6 @@
// Stored as a module attribute: mcrfpy.Key // Stored as a module attribute: mcrfpy.Key
// //
// Values map to sf::Keyboard::Key enum values. // Values map to sf::Keyboard::Key enum values.
// The enum compares equal to both its name ("ESCAPE") and legacy string ("Escape")
// //
// Naming convention: // Naming convention:
// - Letters: A, B, C, ... Z // - Letters: A, B, C, ... Z
@ -24,14 +23,11 @@ public:
static PyObject* create_enum_class(PyObject* module); static PyObject* create_enum_class(PyObject* module);
// Helper to extract key from Python arg // Helper to extract key from Python arg
// Accepts Key enum, string (for backwards compatibility), int, or None // Accepts Key enum, string (enum name), or int
// Returns 1 on success, 0 on error (with exception set) // Returns 1 on success, 0 on error (with exception set)
static int from_arg(PyObject* arg, sf::Keyboard::Key* out_key); static int from_arg(PyObject* arg, sf::Keyboard::Key* out_key);
// Convert sf::Keyboard::Key to legacy string name (for passing to callbacks) // Convert string name to sf::Keyboard::Key (used by C++ event dispatch)
static const char* to_legacy_string(sf::Keyboard::Key key);
// Convert legacy string to sf::Keyboard::Key
// Returns sf::Keyboard::Unknown if not found // Returns sf::Keyboard::Unknown if not found
static sf::Keyboard::Key from_legacy_string(const char* name); static sf::Keyboard::Key from_legacy_string(const char* name);

View file

@ -4,11 +4,10 @@
// Static storage for cached enum class reference // Static storage for cached enum class reference
PyObject* PyMouseButton::mouse_button_enum_class = nullptr; PyObject* PyMouseButton::mouse_button_enum_class = nullptr;
// MouseButton entries - maps enum name to value and legacy string // MouseButton entries - maps enum name to value
struct MouseButtonEntry { struct MouseButtonEntry {
const char* name; // Python enum name (UPPER_SNAKE_CASE) const char* name; // Python enum name (UPPER_SNAKE_CASE)
int value; // Integer value (matches sf::Mouse::Button) int value; // Integer value (matches sf::Mouse::Button)
const char* legacy; // Legacy string name for backwards compatibility
}; };
// Custom values for scroll wheel (beyond sf::Mouse::Button range) // Custom values for scroll wheel (beyond sf::Mouse::Button range)
@ -16,26 +15,17 @@ static const int SCROLL_UP_VALUE = 10;
static const int SCROLL_DOWN_VALUE = 11; static const int SCROLL_DOWN_VALUE = 11;
static const MouseButtonEntry mouse_button_table[] = { static const MouseButtonEntry mouse_button_table[] = {
{"LEFT", sf::Mouse::Left, "left"}, {"LEFT", sf::Mouse::Left},
{"RIGHT", sf::Mouse::Right, "right"}, {"RIGHT", sf::Mouse::Right},
{"MIDDLE", sf::Mouse::Middle, "middle"}, {"MIDDLE", sf::Mouse::Middle},
{"X1", sf::Mouse::XButton1, "x1"}, {"X1", sf::Mouse::XButton1},
{"X2", sf::Mouse::XButton2, "x2"}, {"X2", sf::Mouse::XButton2},
{"SCROLL_UP", SCROLL_UP_VALUE, "wheel_up"}, {"SCROLL_UP", SCROLL_UP_VALUE},
{"SCROLL_DOWN", SCROLL_DOWN_VALUE, "wheel_down"}, {"SCROLL_DOWN", SCROLL_DOWN_VALUE},
}; };
static const int NUM_MOUSE_BUTTON_ENTRIES = sizeof(mouse_button_table) / sizeof(mouse_button_table[0]); 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) { PyObject* PyMouseButton::create_enum_class(PyObject* module) {
// Build the enum definition dynamically from the table // Build the enum definition dynamically from the table
std::ostringstream code; std::ostringstream code;
@ -45,19 +35,13 @@ PyObject* PyMouseButton::create_enum_class(PyObject* module) {
code << " \"\"\"Enum representing mouse buttons and scroll wheel.\n"; code << " \"\"\"Enum representing mouse buttons and scroll wheel.\n";
code << " \n"; code << " \n";
code << " Values:\n"; code << " Values:\n";
code << " LEFT: Left mouse button (legacy: 'left')\n"; code << " LEFT: Left mouse button\n";
code << " RIGHT: Right mouse button (legacy: 'right')\n"; code << " RIGHT: Right mouse button\n";
code << " MIDDLE: Middle mouse button / scroll wheel click (legacy: 'middle')\n"; code << " MIDDLE: Middle mouse button / scroll wheel click\n";
code << " X1: Extra mouse button 1 (legacy: 'x1')\n"; code << " X1: Extra mouse button 1\n";
code << " X2: Extra mouse button 2 (legacy: 'x2')\n"; code << " X2: Extra mouse button 2\n";
code << " SCROLL_UP: Scroll wheel up (legacy: 'wheel_up')\n"; code << " SCROLL_UP: Scroll wheel up\n";
code << " SCROLL_DOWN: Scroll wheel down (legacy: 'wheel_down')\n"; code << " SCROLL_DOWN: Scroll wheel down\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 << " MouseButton.SCROLL_UP == 'wheel_up' # True\n";
code << " \"\"\"\n"; code << " \"\"\"\n";
// Add enum members // Add enum members
@ -65,42 +49,10 @@ PyObject* PyMouseButton::create_enum_class(PyObject* module) {
code << " " << mouse_button_table[i].name << " = " << mouse_button_table[i].value << "\n"; code << " " << mouse_button_table[i].name << " = " << mouse_button_table[i].value << "\n";
} }
// Add legacy names and custom methods AFTER class creation code << "\n";
// (IntEnum doesn't allow dict attributes during class definition) code << "MouseButton.__hash__ = lambda self: hash(int(self))\n";
code << "\n# Add legacy name mapping after class creation\n"; code << "MouseButton.__repr__ = lambda self: f\"{type(self).__name__}.{self.name}\"\n";
code << "MouseButton._legacy_names = {\n"; code << "MouseButton.__str__ = lambda self: self.name\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
def _MouseButton_ne(self, other):
result = type(self).__eq__(self, other)
if result is NotImplemented:
return result
return not result
MouseButton.__ne__ = _MouseButton_ne
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(); std::string code_str = code.str();
@ -195,17 +147,15 @@ int PyMouseButton::from_arg(PyObject* arg, sf::Mouse::Button* out_button) {
return 0; return 0;
} }
// Accept string (both new and legacy names) // Accept string (enum name only)
if (PyUnicode_Check(arg)) { if (PyUnicode_Check(arg)) {
const char* name = PyUnicode_AsUTF8(arg); const char* name = PyUnicode_AsUTF8(arg);
if (!name) { if (!name) {
return 0; return 0;
} }
// Check all entries for both name and legacy match
for (int i = 0; i < NUM_MOUSE_BUTTON_ENTRIES; i++) { for (int i = 0; i < NUM_MOUSE_BUTTON_ENTRIES; i++) {
if (strcmp(name, mouse_button_table[i].name) == 0 || 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); *out_button = static_cast<sf::Mouse::Button>(mouse_button_table[i].value);
return 1; return 1;
} }
@ -213,8 +163,7 @@ int PyMouseButton::from_arg(PyObject* arg, sf::Mouse::Button* out_button) {
PyErr_Format(PyExc_ValueError, PyErr_Format(PyExc_ValueError,
"Unknown MouseButton: '%s'. Use MouseButton.LEFT, MouseButton.RIGHT, " "Unknown MouseButton: '%s'. Use MouseButton.LEFT, MouseButton.RIGHT, "
"MouseButton.MIDDLE, MouseButton.X1, MouseButton.X2, " "MouseButton.MIDDLE, MouseButton.X1, MouseButton.X2.", name);
"or legacy strings 'left', 'right', 'middle', 'x1', 'x2'.", name);
return 0; return 0;
} }

View file

@ -6,13 +6,11 @@
// Stored as a module attribute: mcrfpy.MouseButton // Stored as a module attribute: mcrfpy.MouseButton
// //
// Values map to sf::Mouse::Button: // Values map to sf::Mouse::Button:
// LEFT = 0 (corresponds to "left" in legacy API) // LEFT = 0
// RIGHT = 1 (corresponds to "right" in legacy API) // RIGHT = 1
// MIDDLE = 2 (corresponds to "middle" in legacy API) // MIDDLE = 2
// X1 = 3 (extra button 1) // X1 = 3
// X2 = 4 (extra button 2) // X2 = 4
//
// The enum compares equal to both its name ("LEFT") and legacy string ("left")
class PyMouseButton { class PyMouseButton {
public: public:
@ -21,13 +19,10 @@ public:
static PyObject* create_enum_class(PyObject* module); static PyObject* create_enum_class(PyObject* module);
// Helper to extract mouse button from Python arg // Helper to extract mouse button from Python arg
// Accepts MouseButton enum, string (for backwards compatibility), int, or None // Accepts MouseButton enum, string (enum name), or int
// Returns 1 on success, 0 on error (with exception set) // Returns 1 on success, 0 on error (with exception set)
static int from_arg(PyObject* arg, sf::Mouse::Button* out_button); 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 // Cached reference to the MouseButton enum class for fast type checking
static PyObject* mouse_button_enum_class; static PyObject* mouse_button_enum_class;

View file

@ -90,7 +90,7 @@ print("=" * 60)
# Exit handler # Exit handler
def handle_key(key, state): def handle_key(key, state):
if key == "Escape" and state: if key == mcrfpy.Key.ESCAPE and state == mcrfpy.InputState.PRESSED:
print("\nBenchmark ended by user") print("\nBenchmark ended by user")
sys.exit(0) sys.exit(0)

View file

@ -183,24 +183,23 @@ class Calculator:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
# Switch to game scene # Switch to game scene
mcrfpy.current_scene = game_scene mcrfpy.current_scene = game_scene
return return
# Map keys to buttons # Map keys to buttons
key_map = { key_map = {
"Num0": "0", "Num1": "1", "Num2": "2", "Num3": "3", mcrfpy.Key.NUM_0: "0", mcrfpy.Key.NUM_1: "1", mcrfpy.Key.NUM_2: "2", mcrfpy.Key.NUM_3: "3",
"Num4": "4", "Num5": "5", "Num6": "6", "Num7": "7", mcrfpy.Key.NUM_4: "4", mcrfpy.Key.NUM_5: "5", mcrfpy.Key.NUM_6: "6", mcrfpy.Key.NUM_7: "7",
"Num8": "8", "Num9": "9", mcrfpy.Key.NUM_8: "8", mcrfpy.Key.NUM_9: "9",
"Period": ".", "Add": "+", "Subtract": "-", mcrfpy.Key.PERIOD: ".", mcrfpy.Key.ADD: "+", mcrfpy.Key.SUBTRACT: "-",
"Multiply": "*", "Divide": "/", mcrfpy.Key.MULTIPLY: "*", mcrfpy.Key.DIVIDE: "/",
"Enter": "=", "Return": "=", mcrfpy.Key.ENTER: "=",
"C": "C", "Backspace": "DEL", mcrfpy.Key.C: "C", mcrfpy.Key.BACKSPACE: "DEL",
"LParen": "(", "RParen": ")",
} }
if key in key_map: if key in key_map:
@ -271,10 +270,10 @@ class GamePlaceholder:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
# Switch to calculator # Switch to calculator
calculator.activate() calculator.activate()

View file

@ -444,25 +444,26 @@ class DialogueSystem:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
sys.exit(0) sys.exit(0)
elif key in ("Num1", "Num2", "Num3", "Num4"): elif key in (mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4):
idx = int(key[-1]) - 1 _num_idx = {mcrfpy.Key.NUM_1: 0, mcrfpy.Key.NUM_2: 1, mcrfpy.Key.NUM_3: 2, mcrfpy.Key.NUM_4: 3}
idx = _num_idx[key]
if idx < len(self.choice_list.choices): if idx < len(self.choice_list.choices):
self.choice_list.set_selected(idx) self.choice_list.set_selected(idx)
self.choice_list.confirm() self.choice_list.confirm()
elif key == "Up": elif key == mcrfpy.Key.UP:
self.choice_list.navigate(-1) self.choice_list.navigate(-1)
elif key == "Down": elif key == mcrfpy.Key.DOWN:
self.choice_list.navigate(1) self.choice_list.navigate(1)
elif key == "Enter": elif key == mcrfpy.Key.ENTER:
self.choice_list.confirm() self.choice_list.confirm()
elif key == "Space": elif key == mcrfpy.Key.SPACE:
self.dialogue_box.skip_animation() self.dialogue_box.skip_animation()
elif key == "R": elif key == mcrfpy.Key.R:
# Restart # Restart
self.npc.mood = "neutral" self.npc.mood = "neutral"
self.npc.trust = 50 self.npc.trust = 50

View file

@ -396,10 +396,10 @@ class ShopDemo:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
if self.manager.held_item: if self.manager.held_item:
self.manager.cancel_pickup() self.manager.cancel_pickup()
self.tooltip.text = "Cancelled" self.tooltip.text = "Cancelled"

View file

@ -206,7 +206,7 @@ class CookbookLauncher:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
category = self.categories[self.selected_category] category = self.categories[self.selected_category]

View file

@ -377,22 +377,22 @@ class AnimationDemo:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
sys.exit(0) sys.exit(0)
elif key == "Num1": elif key == mcrfpy.Key.NUM_1:
self.run_chain_demo() self.run_chain_demo()
elif key == "Num2": elif key == mcrfpy.Key.NUM_2:
self.run_group_demo() self.run_group_demo()
elif key == "Num3": elif key == mcrfpy.Key.NUM_3:
self.run_callback_demo() self.run_callback_demo()
elif key == "Num4": elif key == mcrfpy.Key.NUM_4:
self.run_loop_demo() self.run_loop_demo()
elif key == "Num5": elif key == mcrfpy.Key.NUM_5:
self.run_combined_demo() self.run_combined_demo()
elif key == "R": elif key == mcrfpy.Key.R:
self.reset_all() self.reset_all()
def activate(self): def activate(self):

View file

@ -365,38 +365,39 @@ class RotationDemo:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
sys.exit(0) sys.exit(0)
elif key == "Left": elif key == mcrfpy.Key.LEFT:
# Rotate left (counter-clockwise) # Rotate left (counter-clockwise)
name, element = self.elements[self.selected] name, element = self.elements[self.selected]
try: try:
element.rotation = (element.rotation - 15) % 360 element.rotation = (element.rotation - 15) % 360
except AttributeError: except AttributeError:
pass pass
elif key == "Right": elif key == mcrfpy.Key.RIGHT:
# Rotate right (clockwise) # Rotate right (clockwise)
name, element = self.elements[self.selected] name, element = self.elements[self.selected]
try: try:
element.rotation = (element.rotation + 15) % 360 element.rotation = (element.rotation + 15) % 360
except AttributeError: except AttributeError:
pass pass
elif key == "Up": elif key == mcrfpy.Key.UP:
self.rotation_speed = min(180, self.rotation_speed + 15) self.rotation_speed = min(180, self.rotation_speed + 15)
self.speed_label.text = f"Speed: {self.rotation_speed}°/sec" self.speed_label.text = f"Speed: {self.rotation_speed}°/sec"
elif key == "Down": elif key == mcrfpy.Key.DOWN:
self.rotation_speed = max(15, self.rotation_speed - 15) self.rotation_speed = max(15, self.rotation_speed - 15)
self.speed_label.text = f"Speed: {self.rotation_speed}°/sec" self.speed_label.text = f"Speed: {self.rotation_speed}°/sec"
elif key in ("Num1", "Num2", "Num3", "Num4"): elif key in (mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4):
self.selected = int(key[-1]) - 1 _num_idx = {mcrfpy.Key.NUM_1: 0, mcrfpy.Key.NUM_2: 1, mcrfpy.Key.NUM_3: 2, mcrfpy.Key.NUM_4: 3}
self.selected = _num_idx[key]
if self.selected < len(self.elements): if self.selected < len(self.elements):
self.selected_label.text = f"Selected: {self.elements[self.selected][0]}" self.selected_label.text = f"Selected: {self.elements[self.selected][0]}"
elif key == "O": elif key == mcrfpy.Key.O:
self._cycle_origin() self._cycle_origin()
elif key == "A": elif key == mcrfpy.Key.A:
self.auto_rotate = not self.auto_rotate self.auto_rotate = not self.auto_rotate
if self.auto_rotate: if self.auto_rotate:
self.auto_label.text = "Auto-rotate: On" self.auto_label.text = "Auto-rotate: On"
@ -404,7 +405,7 @@ class RotationDemo:
else: else:
self.auto_label.text = "Auto-rotate: Off" self.auto_label.text = "Auto-rotate: Off"
self.auto_label.fill_color = mcrfpy.Color(200, 100, 100) self.auto_label.fill_color = mcrfpy.Color(200, 100, 100)
elif key == "R": elif key == mcrfpy.Key.R:
# Reset all rotations # Reset all rotations
for name, element in self.elements: for name, element in self.elements:
try: try:

View file

@ -296,20 +296,21 @@ class ShaderDemo:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
sys.exit(0) sys.exit(0)
elif key == "Space": elif key == mcrfpy.Key.SPACE:
self.toggle_shaders() self.toggle_shaders()
elif key == "R": elif key == mcrfpy.Key.R:
# Re-enable all shaders # Re-enable all shaders
self.shaders_enabled = False self.shaders_enabled = False
self.toggle_shaders() self.toggle_shaders()
elif key in ("Num1", "Num2", "Num3", "Num4"): elif key in (mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4):
# Focus on specific shader (could zoom in) # Focus on specific shader (could zoom in)
idx = int(key[-1]) - 1 _num_idx = {mcrfpy.Key.NUM_1: 0, mcrfpy.Key.NUM_2: 1, mcrfpy.Key.NUM_3: 2, mcrfpy.Key.NUM_4: 3}
idx = _num_idx[key]
if idx < len(self.shader_frames): if idx < len(self.shader_frames):
self.status.text = f"Focused: Shader {idx + 1}" self.status.text = f"Focused: Shader {idx + 1}"

View file

@ -102,13 +102,13 @@ class Button:
if not self._enabled: if not self._enabled:
return return
if button == "left": if button == mcrfpy.MouseButton.LEFT:
if action == "start": if action == mcrfpy.InputState.PRESSED:
self.is_pressed = True self.is_pressed = True
self.frame.fill_color = self.press_color self.frame.fill_color = self.press_color
# Animate a subtle press effect # Animate a subtle press effect
self._animate_press() self._animate_press()
elif action == "end": elif action == mcrfpy.InputState.RELEASED:
self.is_pressed = False self.is_pressed = False
# Restore hover or normal state # Restore hover or normal state
if self.is_hovered: if self.is_hovered:

View file

@ -123,7 +123,7 @@ class ChoiceList:
idx = i # Capture index in closure idx = i # Capture index in closure
def make_click_handler(index): def make_click_handler(index):
def handler(pos, button, action): def handler(pos, button, action):
if button == "left" and action == "end": if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.RELEASED:
self.set_selected(index) self.set_selected(index)
if self.on_select: if self.on_select:
self.on_select(index, self._choices[index]) self.on_select(index, self._choices[index])

View file

@ -114,7 +114,7 @@ class GridContainer:
# Set up event handlers # Set up event handlers
def make_click(cx, cy): def make_click(cx, cy):
def handler(pos, button, action): def handler(pos, button, action):
if button == "left" and action == "end": if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.RELEASED:
self._on_cell_clicked(cx, cy) self._on_cell_clicked(cx, cy)
return handler return handler

View file

@ -138,7 +138,7 @@ class ItemSlot(mcrfpy.Frame):
return item.slot_type == self.slot_type return item.slot_type == self.slot_type
def _on_click(self, pos, button, action): def _on_click(self, pos, button, action):
if action != "start" or button != "left": if action != mcrfpy.InputState.PRESSED or button != mcrfpy.MouseButton.LEFT:
return return
if self.manager: if self.manager:
self.manager.handle_slot_click(self.slot_name) self.manager.handle_slot_click(self.slot_name)
@ -274,14 +274,14 @@ class ItemManager:
def _on_grid_click(self, grid_name, pos, button, action): def _on_grid_click(self, grid_name, pos, button, action):
"""Handle click on a registered grid.""" """Handle click on a registered grid."""
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
if button == "right": if button == mcrfpy.MouseButton.RIGHT:
self.cancel_pickup() self.cancel_pickup()
return return
if button != "left": if button != mcrfpy.MouseButton.LEFT:
return return
grid, item_map, color_layer = self.grids[grid_name] grid, item_map, color_layer = self.grids[grid_name]

View file

@ -194,7 +194,7 @@ class Modal:
def make_click(cb): def make_click(cb):
def handler(pos, button, action): def handler(pos, button, action):
if button == "left" and action == "end" and cb: if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.RELEASED and cb:
cb() cb()
return handler return handler
@ -208,7 +208,7 @@ class Modal:
def _on_overlay_click(self, pos, button, action): def _on_overlay_click(self, pos, button, action):
"""Handle clicks on overlay (outside modal).""" """Handle clicks on overlay (outside modal)."""
# Check if click is outside modal # Check if click is outside modal
if button == "left" and action == "end": if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.RELEASED:
mx, my = self.modal_frame.x, self.modal_frame.y mx, my = self.modal_frame.x, self.modal_frame.y
mw, mh = self.modal_frame.w, self.modal_frame.h mw, mh = self.modal_frame.w, self.modal_frame.h
px, py = pos.x, pos.y px, py = pos.x, pos.y

View file

@ -138,7 +138,7 @@ class ScrollableList:
# Set up click handler # Set up click handler
def make_click_handler(index): def make_click_handler(index):
def handler(pos, button, action): def handler(pos, button, action):
if button == "left" and action == "end": if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.RELEASED:
self.select(index) self.select(index)
return handler return handler

View file

@ -245,20 +245,20 @@ class ButtonDemo:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
sys.exit(0) sys.exit(0)
elif key == "Num1" and len(self.buttons) > 0: elif key == mcrfpy.Key.NUM_1 and len(self.buttons) > 0:
self.buttons[0].callback() self.buttons[0].callback()
elif key == "Num2" and len(self.buttons) > 1: elif key == mcrfpy.Key.NUM_2 and len(self.buttons) > 1:
self.buttons[1].callback() self.buttons[1].callback()
elif key == "Num3" and len(self.buttons) > 2: elif key == mcrfpy.Key.NUM_3 and len(self.buttons) > 2:
self.buttons[2].callback() self.buttons[2].callback()
elif key == "Num4" and len(self.buttons) > 3: elif key == mcrfpy.Key.NUM_4 and len(self.buttons) > 3:
self.buttons[3].callback() self.buttons[3].callback()
elif key == "D": elif key == mcrfpy.Key.D:
# Toggle disabled button # Toggle disabled button
self.disabled_btn.enabled = not self.disabled_btn.enabled self.disabled_btn.enabled = not self.disabled_btn.enabled
if self.disabled_btn.enabled: if self.disabled_btn.enabled:

View file

@ -230,31 +230,31 @@ class ChoiceListDemo:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
sys.exit(0) sys.exit(0)
# Get active list # Get active list
active = self.lists[self.active_list_idx] if self.lists else None active = self.lists[self.active_list_idx] if self.lists else None
if key == "Up" and active: if key == mcrfpy.Key.UP and active:
active.navigate(-1) active.navigate(-1)
elif key == "Down" and active: elif key == mcrfpy.Key.DOWN and active:
active.navigate(1) active.navigate(1)
elif key == "Enter" and active: elif key == mcrfpy.Key.ENTER and active:
active.confirm() active.confirm()
elif key == "Tab": elif key == mcrfpy.Key.TAB:
# Switch active list # Switch active list
self.active_list_idx = (self.active_list_idx + 1) % len(self.lists) self.active_list_idx = (self.active_list_idx + 1) % len(self.lists)
self._update_active_indicator() self._update_active_indicator()
elif key == "A": elif key == mcrfpy.Key.A:
# Add item to dynamic list # Add item to dynamic list
self.add_counter += 1 self.add_counter += 1
self.dynamic_list.add_choice(f"New Item {self.add_counter}") self.dynamic_list.add_choice(f"New Item {self.add_counter}")
self.dynamic_info.text = f"Items: {len(self.dynamic_list.choices)}" self.dynamic_info.text = f"Items: {len(self.dynamic_list.choices)}"
elif key == "R": elif key == mcrfpy.Key.R:
# Remove selected from dynamic list # Remove selected from dynamic list
if len(self.dynamic_list.choices) > 1: if len(self.dynamic_list.choices) > 1:
self.dynamic_list.remove_choice(self.dynamic_list.selected_index) self.dynamic_list.remove_choice(self.dynamic_list.selected_index)

View file

@ -187,7 +187,7 @@ class ClickPickupDemo:
def _on_grid_click(self, pos, button, action): def _on_grid_click(self, pos, button, action):
"""Handle grid click.""" """Handle grid click."""
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
cell = self._get_grid_cell(pos) cell = self._get_grid_cell(pos)
@ -196,13 +196,13 @@ class ClickPickupDemo:
x, y = cell x, y = cell
if button == "right": if button == mcrfpy.MouseButton.RIGHT:
# Cancel pickup # Cancel pickup
if self.held_entity: if self.held_entity:
self._cancel_pickup() self._cancel_pickup()
return return
if button != "left": if button != mcrfpy.MouseButton.LEFT:
return return
if self.held_entity is None: if self.held_entity is None:
@ -334,10 +334,10 @@ class ClickPickupDemo:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
if self.held_entity: if self.held_entity:
self._cancel_pickup() self._cancel_pickup()
return return

View file

@ -234,9 +234,9 @@ class DragDropFrameDemo:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
# Return to cookbook menu or exit # Return to cookbook menu or exit
try: try:
from cookbook_main import main from cookbook_main import main

View file

@ -142,7 +142,7 @@ class GridDragDropDemo:
def _on_grid_click(self, pos, button, action): def _on_grid_click(self, pos, button, action):
"""Handle grid click for drag start/end.""" """Handle grid click for drag start/end."""
if button != "left": if button != mcrfpy.MouseButton.LEFT:
return return
# Convert screen pos to grid cell # Convert screen pos to grid cell
@ -154,7 +154,7 @@ class GridDragDropDemo:
if not (0 <= grid_x < grid_w and 0 <= grid_y < grid_h): if not (0 <= grid_x < grid_w and 0 <= grid_y < grid_h):
return return
if action == "start": if action == mcrfpy.InputState.PRESSED:
# Start drag if there's an entity here # Start drag if there's an entity here
entity = self._get_entity_at(grid_x, grid_y) entity = self._get_entity_at(grid_x, grid_y)
if entity: if entity:
@ -166,7 +166,7 @@ class GridDragDropDemo:
# Highlight start cell yellow # Highlight start cell yellow
self.color_layer.set((grid_x, grid_y), (255, 255, 100, 200)) self.color_layer.set((grid_x, grid_y), (255, 255, 100, 200))
elif action == "end": elif action == mcrfpy.InputState.RELEASED:
if self.dragging_entity: if self.dragging_entity:
# Drop the entity # Drop the entity
target_cell = (grid_x, grid_y) target_cell = (grid_x, grid_y)
@ -224,9 +224,9 @@ class GridDragDropDemo:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
# Cancel any drag in progress # Cancel any drag in progress
if self.dragging_entity and self.drag_start_cell: if self.dragging_entity and self.drag_start_cell:
self.dragging_entity.grid_pos = self.drag_start_cell self.dragging_entity.grid_pos = self.drag_start_cell

View file

@ -275,15 +275,15 @@ class StatBarDemo:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
sys.exit(0) sys.exit(0)
# Number keys to modify bars # Number keys to modify bars
bar_keys = ['hp', 'mp', 'stamina', 'xp'] bar_keys = ['hp', 'mp', 'stamina', 'xp']
key_map = {"Num1": 0, "Num2": 1, "Num3": 2, "Num4": 3} key_map = {mcrfpy.Key.NUM_1: 0, mcrfpy.Key.NUM_2: 1, mcrfpy.Key.NUM_3: 2, mcrfpy.Key.NUM_4: 3}
if key in key_map: if key in key_map:
idx = key_map[key] idx = key_map[key]
@ -293,11 +293,11 @@ class StatBarDemo:
bar.set_value(bar.current - 10, animate=True) bar.set_value(bar.current - 10, animate=True)
self.status.text = f"Status: Decreased {bar_keys[idx].upper()}" self.status.text = f"Status: Decreased {bar_keys[idx].upper()}"
elif key == "F": elif key == mcrfpy.Key.F:
self.flash_bar.flash() self.flash_bar.flash()
self.status.text = "Status: Flash effect triggered!" self.status.text = "Status: Flash effect triggered!"
elif key == "R": elif key == mcrfpy.Key.R:
# Reset all bars # Reset all bars
self.bars['hp'].set_value(75, 100, animate=True) self.bars['hp'].set_value(75, 100, animate=True)
self.bars['mp'].set_value(50, 80, animate=True) self.bars['mp'].set_value(50, 80, animate=True)

View file

@ -182,18 +182,18 @@ class TextBoxDemo:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
sys.exit(0) sys.exit(0)
elif key == "Num1": elif key == mcrfpy.Key.NUM_1:
# Start typewriter animation # Start typewriter animation
self.typewriter_box.on_complete = self.on_typewriter_complete self.typewriter_box.on_complete = self.on_typewriter_complete
self.typewriter_box.set_text(self.sample_text, animate=True) self.typewriter_box.set_text(self.sample_text, animate=True)
self.completion_label.text = "Status: Playing..." self.completion_label.text = "Status: Playing..."
self.completion_label.fill_color = mcrfpy.Color(200, 200, 100) self.completion_label.fill_color = mcrfpy.Color(200, 200, 100)
elif key == "Num2": elif key == mcrfpy.Key.NUM_2:
# Change instant text # Change instant text
texts = [ texts = [
"This text appeared instantly. Press 2 to change it to different content.", "This text appeared instantly. Press 2 to change it to different content.",
@ -203,17 +203,17 @@ class TextBoxDemo:
] ]
import random import random
self.instant_box.set_text(random.choice(texts), animate=False) self.instant_box.set_text(random.choice(texts), animate=False)
elif key == "Num3": elif key == mcrfpy.Key.NUM_3:
# Skip animation # Skip animation
self.typewriter_box.skip_animation() self.typewriter_box.skip_animation()
self.completion_label.text = "Status: Skipped" self.completion_label.text = "Status: Skipped"
self.completion_label.fill_color = mcrfpy.Color(150, 150, 150) self.completion_label.fill_color = mcrfpy.Color(150, 150, 150)
elif key == "Num4": elif key == mcrfpy.Key.NUM_4:
# Clear text # Clear text
self.typewriter_box.clear() self.typewriter_box.clear()
self.completion_label.text = "Status: Cleared" self.completion_label.text = "Status: Cleared"
self.completion_label.fill_color = mcrfpy.Color(150, 150, 150) self.completion_label.fill_color = mcrfpy.Color(150, 150, 150)
elif key == "D": elif key == mcrfpy.Key.D:
# Cycle dialogue # Cycle dialogue
self.dialogue_index = (self.dialogue_index + 1) % len(self.dialogues) self.dialogue_index = (self.dialogue_index + 1) % len(self.dialogues)
speaker, text = self.dialogues[self.dialogue_index] speaker, text = self.dialogues[self.dialogue_index]

View file

@ -163,32 +163,32 @@ class ToastDemo:
def on_key(self, key, state): def on_key(self, key, state):
"""Handle keyboard input.""" """Handle keyboard input."""
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
sys.exit(0) sys.exit(0)
elif key == "Num1": elif key == mcrfpy.Key.NUM_1:
self.toast_count += 1 self.toast_count += 1
self.toasts.show(f"Default notification #{self.toast_count}") self.toasts.show(f"Default notification #{self.toast_count}")
self.update_stats() self.update_stats()
elif key == "Num2": elif key == mcrfpy.Key.NUM_2:
self.toast_count += 1 self.toast_count += 1
self.toasts.show_success("Operation completed successfully!") self.toasts.show_success("Operation completed successfully!")
self.update_stats() self.update_stats()
elif key == "Num3": elif key == mcrfpy.Key.NUM_3:
self.toast_count += 1 self.toast_count += 1
self.toasts.show_error("An error occurred!") self.toasts.show_error("An error occurred!")
self.update_stats() self.update_stats()
elif key == "Num4": elif key == mcrfpy.Key.NUM_4:
self.toast_count += 1 self.toast_count += 1
self.toasts.show_warning("Warning: Low health!") self.toasts.show_warning("Warning: Low health!")
self.update_stats() self.update_stats()
elif key == "Num5": elif key == mcrfpy.Key.NUM_5:
self.toast_count += 1 self.toast_count += 1
self.toasts.show_info("New quest available") self.toasts.show_info("New quest available")
self.update_stats() self.update_stats()
elif key == "S": elif key == mcrfpy.Key.S:
# Spam multiple toasts # Spam multiple toasts
messages = [ messages = [
"Game saved!", "Game saved!",
@ -201,7 +201,7 @@ class ToastDemo:
self.toast_count += 1 self.toast_count += 1
self.toasts.show(msg) self.toasts.show(msg)
self.update_stats() self.update_stats()
elif key == "C": elif key == mcrfpy.Key.C:
self.toasts.dismiss_all() self.toasts.dismiss_all()
self.update_stats() self.update_stats()

View file

@ -146,21 +146,24 @@ class DemoRunner:
self.create_menu() self.create_menu()
def handle_key(key, state): def handle_key(key, state):
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
# Number keys 1-9 for direct screen access # Number keys 1-9 for direct screen access
if key in [f"Num{n}" for n in "123456789"]: _num_key_map = {mcrfpy.Key.NUM_1: 0, mcrfpy.Key.NUM_2: 1, mcrfpy.Key.NUM_3: 2,
idx = int(key[-1]) - 1 mcrfpy.Key.NUM_4: 3, mcrfpy.Key.NUM_5: 4, mcrfpy.Key.NUM_6: 5,
mcrfpy.Key.NUM_7: 6, mcrfpy.Key.NUM_8: 7, mcrfpy.Key.NUM_9: 8}
if key in _num_key_map:
idx = _num_key_map[key]
if idx < len(self.screens): if idx < len(self.screens):
mcrfpy.setScene(self.screens[idx].scene_name) mcrfpy.setScene(self.screens[idx].scene_name)
# ESC returns to menu # ESC returns to menu
elif key == "Escape": elif key == mcrfpy.Key.ESCAPE:
menu.activate() menu.activate()
# Q quits # Q quits
elif key == "Q": elif key == mcrfpy.Key.Q:
sys.exit(0) sys.exit(0)
# Register keyboard handler on menu scene # Register keyboard handler on menu scene

View file

@ -162,18 +162,18 @@ def on_keypress(key, state):
"""Handle keyboard input""" """Handle keyboard input"""
global patrol_paused global patrol_paused
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "R": if key == mcrfpy.Key.R:
reset_vision() reset_vision()
elif key == "Space": elif key == mcrfpy.Key.SPACE:
patrol_paused = not patrol_paused patrol_paused = not patrol_paused
if patrol_paused: if patrol_paused:
update_status("Status: PAUSED") update_status("Status: PAUSED")
else: else:
update_status("Status: Patrolling") update_status("Status: Patrolling")
elif key == "Q": elif key == mcrfpy.Key.Q:
mcrfpy.current_scene = None mcrfpy.current_scene = None
def reset_vision(): def reset_vision():

View file

@ -31,14 +31,14 @@ class ModifierTracker:
self.ctrl = False self.ctrl = False
self.alt = False self.alt = False
def update(self, key: str, action: str): def update(self, key, action):
"""Call this from your key handler to update modifier state.""" """Call this from your key handler to update modifier state."""
if key in ("LShift", "RShift"): if key in (mcrfpy.Key.LEFT_SHIFT, mcrfpy.Key.RIGHT_SHIFT):
self.shift = (action == "start") self.shift = (action == mcrfpy.InputState.PRESSED)
elif key in ("LControl", "RControl"): elif key in (mcrfpy.Key.LEFT_CONTROL, mcrfpy.Key.RIGHT_CONTROL):
self.ctrl = (action == "start") self.ctrl = (action == mcrfpy.InputState.PRESSED)
elif key in ("LAlt", "RAlt"): elif key in (mcrfpy.Key.LEFT_ALT, mcrfpy.Key.RIGHT_ALT):
self.alt = (action == "start") self.alt = (action == mcrfpy.InputState.PRESSED)
# ============================================================================= # =============================================================================
@ -163,16 +163,16 @@ class FocusManager:
self.modifiers.update(key, action) self.modifiers.update(key, action)
# Only process on key press, not release (key repeat sends multiple "start") # Only process on key press, not release (key repeat sends multiple "start")
if action != "start": if action != mcrfpy.InputState.PRESSED:
return False return False
# Global: Escape closes modals # Global: Escape closes modals
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
if self.pop_modal(): if self.pop_modal():
return True return True
# Global: Tab cycles focus # Global: Tab cycles focus
if key == "Tab": if key == mcrfpy.Key.TAB:
direction = -1 if self.modifiers.shift else 1 direction = -1 if self.modifiers.shift else 1
self.cycle(direction) self.cycle(direction)
return True return True
@ -251,7 +251,7 @@ class FocusableGrid:
def _on_click(self, x, y, button, action): def _on_click(self, x, y, button, action):
"""Handle click to focus this grid.""" """Handle click to focus this grid."""
if self._focus_manager and action == "start": if self._focus_manager and action == mcrfpy.InputState.PRESSED:
self._focus_manager.focus(self._focus_index) self._focus_manager.focus(self._focus_index)
def _update_player_display(self): def _update_player_display(self):
@ -272,13 +272,13 @@ class FocusableGrid:
self.outline_frame.outline_color = FocusManager.UNFOCUS_COLOR self.outline_frame.outline_color = FocusManager.UNFOCUS_COLOR
self.outline_frame.outline = FocusManager.UNFOCUS_OUTLINE self.outline_frame.outline = FocusManager.UNFOCUS_OUTLINE
def handle_key(self, key: str, action: str) -> bool: def handle_key(self, key, action) -> bool:
"""Handle WASD movement.""" """Handle WASD movement."""
moves = { moves = {
"W": (0, -1), "Up": (0, -1), mcrfpy.Key.W: (0, -1), mcrfpy.Key.UP: (0, -1),
"A": (-1, 0), "Left": (-1, 0), mcrfpy.Key.A: (-1, 0), mcrfpy.Key.LEFT: (-1, 0),
"S": (0, 1), "Down": (0, 1), mcrfpy.Key.S: (0, 1), mcrfpy.Key.DOWN: (0, 1),
"D": (1, 0), "Right": (1, 0), mcrfpy.Key.D: (1, 0), mcrfpy.Key.RIGHT: (1, 0),
} }
if key in moves: if key in moves:
@ -373,7 +373,7 @@ class TextInputWidget:
def _on_click(self, x, y, button, action): def _on_click(self, x, y, button, action):
"""Handle click to focus.""" """Handle click to focus."""
if self._focus_manager and action == "start": if self._focus_manager and action == mcrfpy.InputState.PRESSED:
self._focus_manager.focus(self._focus_index) self._focus_manager.focus(self._focus_index)
def _update_display(self): def _update_display(self):
@ -404,7 +404,7 @@ class TextInputWidget:
self.cursor.visible = False self.cursor.visible = False
self._update_display() self._update_display()
def handle_key(self, key: str, action: str) -> bool: def handle_key(self, key, action) -> bool:
"""Handle text input and editing keys.""" """Handle text input and editing keys."""
if not self.focused: if not self.focused:
return False return False
@ -412,27 +412,27 @@ class TextInputWidget:
old_text = self.text old_text = self.text
handled = True handled = True
if key == "BackSpace": if key == mcrfpy.Key.BACKSPACE:
if self.cursor_pos > 0: if self.cursor_pos > 0:
self.text = self.text[:self.cursor_pos-1] + self.text[self.cursor_pos:] self.text = self.text[:self.cursor_pos-1] + self.text[self.cursor_pos:]
self.cursor_pos -= 1 self.cursor_pos -= 1
elif key == "Delete": elif key == mcrfpy.Key.DELETE:
if self.cursor_pos < len(self.text): if self.cursor_pos < len(self.text):
self.text = self.text[:self.cursor_pos] + self.text[self.cursor_pos+1:] self.text = self.text[:self.cursor_pos] + self.text[self.cursor_pos+1:]
elif key == "Left": elif key == mcrfpy.Key.LEFT:
self.cursor_pos = max(0, self.cursor_pos - 1) self.cursor_pos = max(0, self.cursor_pos - 1)
elif key == "Right": elif key == mcrfpy.Key.RIGHT:
self.cursor_pos = min(len(self.text), self.cursor_pos + 1) self.cursor_pos = min(len(self.text), self.cursor_pos + 1)
elif key == "Home": elif key == mcrfpy.Key.HOME:
self.cursor_pos = 0 self.cursor_pos = 0
elif key == "End": elif key == mcrfpy.Key.END:
self.cursor_pos = len(self.text) self.cursor_pos = len(self.text)
elif key in ("Return", "Tab"): elif key in (mcrfpy.Key.ENTER, mcrfpy.Key.TAB):
# Don't consume - let focus manager handle # Don't consume - let focus manager handle
handled = False handled = False
elif len(key) == 1 and key.isprintable(): elif len(key.name) == 1 and key.name.isprintable():
# Insert character # Insert character (key.name is "A"-"Z" for letter keys)
self.text = self.text[:self.cursor_pos] + key + self.text[self.cursor_pos:] self.text = self.text[:self.cursor_pos] + key.name.lower() + self.text[self.cursor_pos:]
self.cursor_pos += 1 self.cursor_pos += 1
else: else:
handled = False handled = False
@ -509,7 +509,7 @@ class MenuIcon:
if not self._focus_manager: if not self._focus_manager:
return return
if action == "start": if action == mcrfpy.InputState.PRESSED:
# If already focused, activate; otherwise just focus # If already focused, activate; otherwise just focus
if self._focus_manager.focus_index == self._focus_index: if self._focus_manager.focus_index == self._focus_index:
self._activate() self._activate()
@ -535,9 +535,9 @@ class MenuIcon:
self.frame.fill_color = mcrfpy.Color(60, 60, 80) self.frame.fill_color = mcrfpy.Color(60, 60, 80)
self.tooltip_caption.visible = False self.tooltip_caption.visible = False
def handle_key(self, key: str, action: str) -> bool: def handle_key(self, key, action) -> bool:
"""Handle activation keys.""" """Handle activation keys."""
if key in ("Space", "Return"): if key in (mcrfpy.Key.SPACE, mcrfpy.Key.ENTER):
self._activate() self._activate()
return True return True
return False return False

View file

@ -13,7 +13,7 @@ scene = mcrfpy.Scene("test_modern")
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(800, 600))) scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(800, 600)))
def my_handler(key, action): def my_handler(key, action):
if action == "start": if action == mcrfpy.InputState.PRESSED:
print(f" Key handler received: {key}") print(f" Key handler received: {key}")
scene.on_key = my_handler scene.on_key = my_handler

View file

@ -46,15 +46,15 @@ grid.entities.append(treasure)
# Movement handler using modern API # Movement handler using modern API
def handle_keys(key, state): def handle_keys(key, state):
if state == "start": if state == mcrfpy.InputState.PRESSED:
x, y = player.pos[0], player.pos[1] x, y = player.pos[0], player.pos[1]
if key == "W": if key == mcrfpy.Key.W:
player.pos = (x, y - 1) player.pos = (x, y - 1)
elif key == "S": elif key == mcrfpy.Key.S:
player.pos = (x, y + 1) player.pos = (x, y + 1)
elif key == "A": elif key == mcrfpy.Key.A:
player.pos = (x - 1, y) player.pos = (x - 1, y)
elif key == "D": elif key == mcrfpy.Key.D:
player.pos = (x + 1, y) player.pos = (x + 1, y)
scene.on_key = handle_keys scene.on_key = handle_keys

View file

@ -35,7 +35,7 @@ scene.children.append(grid)
# Add keyboard controls using modern API # Add keyboard controls using modern API
def move_around(key, state): def move_around(key, state):
if state == "start": if state == mcrfpy.InputState.PRESSED:
print(f"You pressed {key}") print(f"You pressed {key}")
scene.on_key = move_around scene.on_key = move_around

View file

@ -169,12 +169,15 @@ class GeometryDemoRunner:
self.create_menu() self.create_menu()
def handle_key(key, state): def handle_key(key, state):
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
# Number keys 1-9 for direct screen access # Number keys 1-9 for direct screen access
if key in [f"Num{n}" for n in "123456789"]: _num_key_map = {mcrfpy.Key.NUM_1: 0, mcrfpy.Key.NUM_2: 1, mcrfpy.Key.NUM_3: 2,
idx = int(key[-1]) - 1 mcrfpy.Key.NUM_4: 3, mcrfpy.Key.NUM_5: 4, mcrfpy.Key.NUM_6: 5,
mcrfpy.Key.NUM_7: 6, mcrfpy.Key.NUM_8: 7, mcrfpy.Key.NUM_9: 8}
if key in _num_key_map:
idx = _num_key_map[key]
if idx < len(self.screens): if idx < len(self.screens):
# Clean up ALL screen's timers first # Clean up ALL screen's timers first
for screen in self.screens: for screen in self.screens:
@ -185,13 +188,13 @@ class GeometryDemoRunner:
self.screens[idx].restart_timers() self.screens[idx].restart_timers()
# ESC returns to menu # ESC returns to menu
elif key == "Escape": elif key == mcrfpy.Key.ESCAPE:
for screen in self.screens: for screen in self.screens:
screen.cleanup() screen.cleanup()
geo_menu.activate() geo_menu.activate()
# Q quits # Q quits
elif key == "Q": elif key == mcrfpy.Key.Q:
sys.exit(0) sys.exit(0)
# Register keyboard handler on all scenes # Register keyboard handler on all scenes

View file

@ -41,14 +41,14 @@ class Draggable(mcrfpy.Frame):
self.tick.stop() self.tick.stop()
def minmax(self, pos, btn, event): def minmax(self, pos, btn, event):
if event != "start": return if event != mcrfpy.InputState.PRESSED: return
self.minimized = not self.minimized self.minimized = not self.minimized
self.minimize_btn.children[0].text = "+" if self.minimized else "-" self.minimize_btn.children[0].text = "+" if self.minimized else "-"
self.clock.visible = not self.minimized self.clock.visible = not self.minimized
self.h = 40 if self.minimized else 150 self.h = 40 if self.minimized else 150
def toggle_move(self, *args): def toggle_move(self, *args):
if not self.dragging and args[-1] == "start": if not self.dragging and args[-1] == mcrfpy.InputState.PRESSED:
self.dragging = True self.dragging = True
self.drag_start_pos = args[0] self.drag_start_pos = args[0]
self.on_move = self.update_pos self.on_move = self.update_pos
@ -66,7 +66,7 @@ class Draggable(mcrfpy.Frame):
self.clock.text = f"{str(self.time//60).zfill(2)}:{str(self.time%60).zfill(2)}" self.clock.text = f"{str(self.time//60).zfill(2)}:{str(self.time%60).zfill(2)}"
def spawn(*args): def spawn(*args):
if args[-1] != "start": return if args[-1] != mcrfpy.InputState.PRESSED: return
scene.children.append(Draggable((50, 100))) scene.children.append(Draggable((50, 100)))
add_btn = mcrfpy.Frame((5, 5), (32, 32), fill_color = (64, 12, 12), children=[mcrfpy.Caption((8, 0), text="+", font=mcrfpy.default_font, font_size=24)]) add_btn = mcrfpy.Frame((5, 5), (32, 32), fill_color = (64, 12, 12), children=[mcrfpy.Caption((8, 0), text="+", font=mcrfpy.default_font, font_size=24)])

View file

@ -160,21 +160,21 @@ def show_both():
def handle_keypress(key_str, state): def handle_keypress(key_str, state):
"""Handle keyboard input""" """Handle keyboard input"""
global mode global mode
if state == "end": return if state == mcrfpy.InputState.RELEASED: return
print(key_str) print(key_str)
if key_str == "Esc" or key_str == "Q": if key_str == mcrfpy.Key.ESCAPE or key_str == mcrfpy.Key.Q:
print("\nExiting...") print("\nExiting...")
sys.exit(0) sys.exit(0)
elif key_str == "A" or key_str == "1": elif key_str == mcrfpy.Key.A or key_str == mcrfpy.Key.NUM_1:
mode = "ASTAR" mode = "ASTAR"
show_astar() show_astar()
elif key_str == "D" or key_str == "2": elif key_str == mcrfpy.Key.D or key_str == mcrfpy.Key.NUM_2:
mode = "DIJKSTRA" mode = "DIJKSTRA"
show_dijkstra() show_dijkstra()
elif key_str == "B" or key_str == "3": elif key_str == mcrfpy.Key.B or key_str == mcrfpy.Key.NUM_3:
mode = "BOTH" mode = "BOTH"
show_both() show_both()
elif key_str == "Space": elif key_str == mcrfpy.Key.SPACE:
# Refresh current mode # Refresh current mode
if mode == "ASTAR": if mode == "ASTAR":
show_astar() show_astar()
@ -203,7 +203,7 @@ ui.append(grid)
# Scale and position # Scale and position
grid.size = (600, 400) # 30*20, 20*20 grid.size = (600, 400) # 30*20, 20*20
grid.position = (100, 100) grid.pos = (100, 100)
# Add title # Add title
title = mcrfpy.Caption(pos=(250, 20), text="A* vs Dijkstra Pathfinding") title = mcrfpy.Caption(pos=(250, 20), text="A* vs Dijkstra Pathfinding")

View file

@ -152,21 +152,24 @@ def show_combination(index):
def handle_keypress(key_str, state): def handle_keypress(key_str, state):
"""Handle keyboard input""" """Handle keyboard input"""
global current_combo_index global current_combo_index
if state == "end": return if state == mcrfpy.InputState.RELEASED: return
if key_str == "Esc" or key_str == "Q": if key_str == mcrfpy.Key.ESCAPE or key_str == mcrfpy.Key.Q:
print("\nExiting...") print("\nExiting...")
sys.exit(0) sys.exit(0)
elif key_str == "Space" or key_str == "N": elif key_str == mcrfpy.Key.SPACE or key_str == mcrfpy.Key.N:
show_combination(current_combo_index + 1) show_combination(current_combo_index + 1)
elif key_str == "P": elif key_str == mcrfpy.Key.P:
show_combination(current_combo_index - 1) show_combination(current_combo_index - 1)
elif key_str == "R": elif key_str == mcrfpy.Key.R:
show_combination(current_combo_index) show_combination(current_combo_index)
elif key_str in "123456": else:
combo_num = int(key_str) - 1 # 0-based index num_keys = {mcrfpy.Key.NUM_1: 0, mcrfpy.Key.NUM_2: 1, mcrfpy.Key.NUM_3: 2,
if combo_num < len(all_combinations): mcrfpy.Key.NUM_4: 3, mcrfpy.Key.NUM_5: 4, mcrfpy.Key.NUM_6: 5}
show_combination(combo_num) if key_str in num_keys:
combo_num = num_keys[key_str]
if combo_num < len(all_combinations):
show_combination(combo_num)
# Create the demo # Create the demo
print("Dijkstra All Paths Demo") print("Dijkstra All Paths Demo")
@ -183,7 +186,7 @@ ui.append(grid)
# Scale and position # Scale and position
grid.size = (560, 400) grid.size = (560, 400)
grid.position = (120, 100) grid.pos = (120, 100)
# Add title # Add title
title = mcrfpy.Caption(pos=(200, 20), text="Dijkstra - All Paths (Valid & Invalid)") title = mcrfpy.Caption(pos=(200, 20), text="Dijkstra - All Paths (Valid & Invalid)")

View file

@ -169,15 +169,15 @@ def show_path(index):
def handle_keypress(key_str, state): def handle_keypress(key_str, state):
"""Handle keyboard input""" """Handle keyboard input"""
global current_path_index global current_path_index
if state == "end": return if state == mcrfpy.InputState.RELEASED: return
if key_str == "Esc": if key_str == mcrfpy.Key.ESCAPE:
print("\nExiting...") print("\nExiting...")
sys.exit(0) sys.exit(0)
elif key_str == "N" or key_str == "Space": elif key_str == mcrfpy.Key.N or key_str == mcrfpy.Key.SPACE:
show_path(current_path_index + 1) show_path(current_path_index + 1)
elif key_str == "P": elif key_str == mcrfpy.Key.P:
show_path(current_path_index - 1) show_path(current_path_index - 1)
elif key_str == "R": elif key_str == mcrfpy.Key.R:
show_path(current_path_index) show_path(current_path_index)
# Create the demo # Create the demo
@ -194,7 +194,7 @@ ui.append(grid)
# Scale and position # Scale and position
grid.size = (560, 400) grid.size = (560, 400)
grid.position = (120, 100) grid.pos = (120, 100)
# Add title # Add title
title = mcrfpy.Caption(pos=(200, 20), text="Dijkstra Pathfinding - Cycle Paths") title = mcrfpy.Caption(pos=(200, 20), text="Dijkstra Pathfinding - Cycle Paths")

View file

@ -79,7 +79,7 @@ perspective_names = ["Omniscient", "Player", "Enemy"]
# UI Setup # UI Setup
ui = visibility_demo.children ui = visibility_demo.children
ui.append(grid) ui.append(grid)
grid.position = (50, 100) grid.pos = (50, 100)
grid.size = (900, 600) # 30*30, 20*30 grid.size = (900, 600) # 30*30, 20*30
# Title # Title
@ -138,49 +138,48 @@ def cycle_perspective():
# Key handlers # Key handlers
def handle_keys(key, state): def handle_keys(key, state):
"""Handle keyboard input""" """Handle keyboard input"""
if state == "end": return if state == mcrfpy.InputState.RELEASED: return
key = key.lower()
# Player movement (WASD) # Player movement (WASD)
if key == "w": if key == mcrfpy.Key.W:
move_entity(player, 0, -1) move_entity(player, 0, -1)
elif key == "s": elif key == mcrfpy.Key.S:
move_entity(player, 0, 1) move_entity(player, 0, 1)
elif key == "a": elif key == mcrfpy.Key.A:
move_entity(player, -1, 0) move_entity(player, -1, 0)
elif key == "d": elif key == mcrfpy.Key.D:
move_entity(player, 1, 0) move_entity(player, 1, 0)
# Enemy movement (Arrows) # Enemy movement (Arrows)
elif key == "up": elif key == mcrfpy.Key.UP:
move_entity(enemy, 0, -1) move_entity(enemy, 0, -1)
elif key == "down": elif key == mcrfpy.Key.DOWN:
move_entity(enemy, 0, 1) move_entity(enemy, 0, 1)
elif key == "left": elif key == mcrfpy.Key.LEFT:
move_entity(enemy, -1, 0) move_entity(enemy, -1, 0)
elif key == "right": elif key == mcrfpy.Key.RIGHT:
move_entity(enemy, 1, 0) move_entity(enemy, 1, 0)
# Tab to cycle perspective # Tab to cycle perspective
elif key == "tab": elif key == mcrfpy.Key.TAB:
cycle_perspective() cycle_perspective()
# Space to update visibility # Space to update visibility
elif key == "space": elif key == mcrfpy.Key.SPACE:
player.update_visibility() player.update_visibility()
enemy.update_visibility() enemy.update_visibility()
print("Updated visibility for both entities") print("Updated visibility for both entities")
# R to reset # R to reset
elif key == "r": elif key == mcrfpy.Key.R:
player.x, player.y = 5, 10 player.x, player.y = 5, 10
enemy.x, enemy.y = 25, 10 enemy.x, enemy.y = 25, 10
player.update_visibility() player.update_visibility()
enemy.update_visibility() enemy.update_visibility()
update_info() update_info()
print("Reset positions") print("Reset positions")
# Q to quit # Q to quit
elif key == "q": elif key == mcrfpy.Key.Q:
print("Exiting...") print("Exiting...")
sys.exit(0) sys.exit(0)

View file

@ -456,18 +456,17 @@ class ProcgenDemoBase(ABC):
elif key == mcrfpy.Key.ESCAPE: elif key == mcrfpy.Key.ESCAPE:
self._return_to_menu() self._return_to_menu()
else: else:
# Number keys for layer toggles - convert to string for parsing # Number keys for layer toggles
key_str = str(key) if not isinstance(key, str) else key _num_key_map = {mcrfpy.Key.NUM_1: 1, mcrfpy.Key.NUM_2: 2, mcrfpy.Key.NUM_3: 3,
if key_str.startswith("Key.NUM") or (len(key_str) == 1 and key_str.isdigit()): mcrfpy.Key.NUM_4: 4, mcrfpy.Key.NUM_5: 5, mcrfpy.Key.NUM_6: 6,
try: mcrfpy.Key.NUM_7: 7, mcrfpy.Key.NUM_8: 8, mcrfpy.Key.NUM_9: 9}
num = int(key_str[-1]) if key in _num_key_map:
if 1 <= num <= len(self.layer_defs): num = _num_key_map[key]
layer_def = self.layer_defs[num - 1] if 1 <= num <= len(self.layer_defs):
toggle = self.widgets.get(f"layer_{layer_def.name}") layer_def = self.layer_defs[num - 1]
if toggle: toggle = self.widgets.get(f"layer_{layer_def.name}")
toggle.toggle() if toggle:
except (ValueError, IndexError): toggle.toggle()
pass
def _return_to_menu(self): def _return_to_menu(self):
"""Return to demo menu.""" """Return to demo menu."""

View file

@ -160,17 +160,14 @@ class DemoLauncher:
if action != mcrfpy.InputState.PRESSED: if action != mcrfpy.InputState.PRESSED:
return return
# Convert key to string for easier comparison
key_str = str(key) if not isinstance(key, str) else key
# Number keys to launch demos directly # Number keys to launch demos directly
if key_str.startswith("Key.NUM") or (len(key_str) == 1 and key_str.isdigit()): _num_key_map = {mcrfpy.Key.NUM_1: 1, mcrfpy.Key.NUM_2: 2, mcrfpy.Key.NUM_3: 3,
try: mcrfpy.Key.NUM_4: 4, mcrfpy.Key.NUM_5: 5, mcrfpy.Key.NUM_6: 6,
num = int(key_str[-1]) mcrfpy.Key.NUM_7: 7, mcrfpy.Key.NUM_8: 8, mcrfpy.Key.NUM_9: 9}
if 1 <= num <= len(self.DEMOS): if key in _num_key_map:
self._launch_demo(num - 1) num = _num_key_map[key]
except (ValueError, IndexError): if 1 <= num <= len(self.DEMOS):
pass self._launch_demo(num - 1)
elif key == mcrfpy.Key.ESCAPE: elif key == mcrfpy.Key.ESCAPE:
sys.exit(0) sys.exit(0)

View file

@ -27,7 +27,7 @@ def test_minimal():
print("Attempting to call on_click...") print("Attempting to call on_click...")
try: try:
obj.on_click((50, 50), "left", "start") obj.on_click((50, 50), mcrfpy.MouseButton.LEFT, mcrfpy.InputState.PRESSED)
print("Call succeeded!") print("Call succeeded!")
except Exception as e: except Exception as e:
print(f"Exception: {type(e).__name__}: {e}") print(f"Exception: {type(e).__name__}: {e}")
@ -51,7 +51,7 @@ def test_without_callback_clear():
obj = MyFrame(pos=(100, 100), size=(100, 100)) obj = MyFrame(pos=(100, 100), size=(100, 100))
print("Calling...") print("Calling...")
obj.on_click((50, 50), "left", "start") obj.on_click((50, 50), mcrfpy.MouseButton.LEFT, mcrfpy.InputState.PRESSED)
print("Deleting without clearing callback...") print("Deleting without clearing callback...")
del obj del obj
@ -69,7 +69,7 @@ def test_added_to_scene():
scene.children.append(obj) scene.children.append(obj)
print("Calling via scene.children[0]...") print("Calling via scene.children[0]...")
scene.children[0].on_click((50, 50), "left", "start") scene.children[0].on_click((50, 50), mcrfpy.MouseButton.LEFT, mcrfpy.InputState.PRESSED)
print("About to exit...") print("About to exit...")
sys.exit(0) sys.exit(0)

View file

@ -120,7 +120,7 @@ ui.append(cached_shader)
# Keyboard handler # Keyboard handler
def on_key(key, state): def on_key(key, state):
if state == "start" and key in ("Q", "Escape"): if state == mcrfpy.InputState.PRESSED and key in (mcrfpy.Key.Q, mcrfpy.Key.ESCAPE):
print("PASS: Shader POC test complete - exiting") print("PASS: Shader POC test complete - exiting")
sys.exit(0) sys.exit(0)

View file

@ -55,15 +55,15 @@ test_count = 0
def on_key(key, state): def on_key(key, state):
global test_count global test_count
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
if key == "Num1" or key == "1": if key == mcrfpy.Key.NUM_1:
test_frame.shader_enabled = True test_frame.shader_enabled = True
status.text = "Status: Shader ENABLED" status.text = "Status: Shader ENABLED"
update_display() update_display()
test_count += 1 test_count += 1
elif key == "Num2" or key == "2": elif key == mcrfpy.Key.NUM_2:
test_frame.shader_enabled = False test_frame.shader_enabled = False
status.text = "Status: Shader DISABLED" status.text = "Status: Shader DISABLED"
update_display() update_display()
@ -76,7 +76,7 @@ def on_key(key, state):
else: else:
status.text = "Status: Shader DISABLED - Position OK!" status.text = "Status: Shader DISABLED - Position OK!"
status.fill_color = (100, 255, 100, 255) status.fill_color = (100, 255, 100, 255)
elif key in ("Q", "Escape"): elif key in (mcrfpy.Key.Q, mcrfpy.Key.ESCAPE):
if test_frame.x == 200 and test_frame.y == 200: if test_frame.x == 200 and test_frame.y == 200:
print(f"PASS: Position remained correct after {test_count} toggles") print(f"PASS: Position remained correct after {test_count} toggles")
else: else:

View file

@ -100,7 +100,7 @@ enemy.sprite_index = 69 # E
# UI setup # UI setup
ui = chain_test.children ui = chain_test.children
ui.append(grid) ui.append(grid)
grid.position = (100, 100) grid.pos = (100, 100)
grid.size = (600, 450) grid.size = (600, 450)
title = mcrfpy.Caption(pos=(300, 20), text="Animation Chaining Test") title = mcrfpy.Caption(pos=(300, 20), text="Animation Chaining Test")
@ -179,23 +179,21 @@ def update_camera(timer, runtime):
def handle_input(key, state): def handle_input(key, state):
global camera_follow global camera_follow
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
key = key.lower() if key == mcrfpy.Key.Q:
if key == "q":
sys.exit(0) sys.exit(0)
elif key == "num1": elif key == mcrfpy.Key.NUM_1:
animate_player() animate_player()
elif key == "num2": elif key == mcrfpy.Key.NUM_2:
animate_enemy() animate_enemy()
elif key == "num3": elif key == mcrfpy.Key.NUM_3:
animate_both() animate_both()
elif key == "c": elif key == mcrfpy.Key.C:
camera_follow = not camera_follow camera_follow = not camera_follow
info.text = f"Camera follow: {'ON' if camera_follow else 'OFF'}" info.text = f"Camera follow: {'ON' if camera_follow else 'OFF'}"
elif key == "r": elif key == mcrfpy.Key.R:
# Reset positions # Reset positions
player.x, player.y = 2, 2 player.x, player.y = 2, 2
enemy.x, enemy.y = 17, 12 enemy.x, enemy.y = 17, 12

View file

@ -17,12 +17,12 @@ def test_click_callback_signature(pos, button, action):
results.append(("on_click pos is Vector", False)) results.append(("on_click pos is Vector", False))
print(f"FAIL: on_click receives {type(pos).__name__} instead of Vector: {pos}") print(f"FAIL: on_click receives {type(pos).__name__} instead of Vector: {pos}")
# Verify button and action types # Verify button and action types (enums since #306)
if isinstance(button, str) and isinstance(action, str): if isinstance(button, mcrfpy.MouseButton) and isinstance(action, mcrfpy.InputState):
results.append(("on_click button/action are strings", True)) results.append(("on_click button/action are enums", True))
print(f"PASS: button={button!r}, action={action!r}") print(f"PASS: button={button!r}, action={action!r}")
else: else:
results.append(("on_click button/action are strings", False)) results.append(("on_click button/action are enums", False))
print(f"FAIL: button={type(button).__name__}, action={type(action).__name__}") print(f"FAIL: button={type(button).__name__}, action={type(action).__name__}")
# #230 - Hover callbacks now receive only (pos), not (pos, button, action) # #230 - Hover callbacks now receive only (pos), not (pos, button, action)
@ -112,7 +112,7 @@ print("\n--- Simulating callback calls ---")
# Test that the callbacks are set up correctly # Test that the callbacks are set up correctly
# on_click still takes (pos, button, action) # on_click still takes (pos, button, action)
test_click_callback_signature(mcrfpy.Vector(150, 150), "left", "start") test_click_callback_signature(mcrfpy.Vector(150, 150), mcrfpy.MouseButton.LEFT, mcrfpy.InputState.PRESSED)
# #230 - Hover callbacks now take only (pos) # #230 - Hover callbacks now take only (pos)
test_on_enter_callback_signature(mcrfpy.Vector(100, 100)) test_on_enter_callback_signature(mcrfpy.Vector(100, 100))
test_on_exit_callback_signature(mcrfpy.Vector(300, 300)) test_on_exit_callback_signature(mcrfpy.Vector(300, 300))

View file

@ -44,7 +44,7 @@ entity.sprite_index = 64 # @
# UI setup # UI setup
ui = test_anim.children ui = test_anim.children
ui.append(grid) ui.append(grid)
grid.position = (100, 100) grid.pos = (100, 100)
grid.size = (450, 450) # 15 * 30 pixels per cell grid.size = (450, 450) # 15 * 30 pixels per cell
# Title # Title
@ -160,23 +160,21 @@ def test_immediate_position():
# Input handler # Input handler
def handle_input(key, state): def handle_input(key, state):
if state != "start": if state != mcrfpy.InputState.PRESSED:
return return
key = key.lower() if key == mcrfpy.Key.Q:
if key == "q":
print("Exiting test...") print("Exiting test...")
sys.exit(0) sys.exit(0)
elif key == "space": elif key == mcrfpy.Key.SPACE:
if not animating: if not animating:
start_animation() start_animation()
else: else:
print("Animation already in progress!") print("Animation already in progress!")
elif key == "t": elif key == mcrfpy.Key.T:
# Test immediate position change # Test immediate position change
test_immediate_position() test_immediate_position()
elif key == "r": elif key == mcrfpy.Key.R:
# Reset position # Reset position
entity.x = 5 entity.x = 5
entity.y = 5 entity.y = 5

View file

@ -1,8 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Test Key, MouseButton, and InputState enum functionality. """Test Key, MouseButton, and InputState enum functionality.
Tests the new input-related enums that provide type-safe alternatives to Tests the input-related enums that provide type-safe key codes,
string-based key codes, mouse buttons, and event states. mouse buttons, and event states. Legacy string comparison was
removed in #306 -- these enums now use standard IntEnum comparison.
""" """
import mcrfpy import mcrfpy
@ -10,7 +11,7 @@ import sys
def test_key_enum(): def test_key_enum():
"""Test Key enum members and backwards compatibility.""" """Test Key enum members and int values."""
print("Testing Key enum...") print("Testing Key enum...")
# Test that enum exists and has expected members # Test that enum exists and has expected members
@ -23,24 +24,19 @@ def test_key_enum():
assert int(mcrfpy.Key.A) == 0, "Key.A should be 0" assert int(mcrfpy.Key.A) == 0, "Key.A should be 0"
assert int(mcrfpy.Key.ESCAPE) == 36, "Key.ESCAPE should be 36" assert int(mcrfpy.Key.ESCAPE) == 36, "Key.ESCAPE should be 36"
# Test backwards compatibility with legacy strings # Test enum self-comparison
assert mcrfpy.Key.A == "A", "Key.A should equal 'A'" assert mcrfpy.Key.ESCAPE == mcrfpy.Key.ESCAPE, "Key.ESCAPE should equal itself"
assert mcrfpy.Key.ESCAPE == "Escape", "Key.ESCAPE should equal 'Escape'" assert mcrfpy.Key.A != mcrfpy.Key.ESCAPE, "Key.A should not equal Key.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 # Verify legacy string comparison is removed (#306)
assert mcrfpy.Key.ESCAPE == "ESCAPE", "Key.ESCAPE should also equal 'ESCAPE'" assert not (mcrfpy.Key.ESCAPE == "Escape"), "Legacy string comparison should be removed"
assert not (mcrfpy.Key.A == "A"), "Legacy string comparison should be removed"
print(" All Key tests passed") print(" All Key tests passed")
def test_mouse_button_enum(): def test_mouse_button_enum():
"""Test MouseButton enum members and backwards compatibility.""" """Test MouseButton enum members."""
print("Testing MouseButton enum...") print("Testing MouseButton enum...")
# Test that enum exists and has expected members # Test that enum exists and has expected members
@ -54,21 +50,19 @@ def test_mouse_button_enum():
assert int(mcrfpy.MouseButton.RIGHT) == 1, "MouseButton.RIGHT should be 1" assert int(mcrfpy.MouseButton.RIGHT) == 1, "MouseButton.RIGHT should be 1"
assert int(mcrfpy.MouseButton.MIDDLE) == 2, "MouseButton.MIDDLE should be 2" assert int(mcrfpy.MouseButton.MIDDLE) == 2, "MouseButton.MIDDLE should be 2"
# Test backwards compatibility with legacy strings # Test enum self-comparison
assert mcrfpy.MouseButton.LEFT == "left", "MouseButton.LEFT should equal 'left'" assert mcrfpy.MouseButton.LEFT == mcrfpy.MouseButton.LEFT
assert mcrfpy.MouseButton.RIGHT == "right", "MouseButton.RIGHT should equal 'right'" assert mcrfpy.MouseButton.LEFT != mcrfpy.MouseButton.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 # Verify legacy string comparison is removed (#306)
assert mcrfpy.MouseButton.LEFT == "LEFT", "MouseButton.LEFT should also equal 'LEFT'" assert not (mcrfpy.MouseButton.LEFT == "left"), "Legacy string comparison should be removed"
assert not (mcrfpy.MouseButton.RIGHT == "right"), "Legacy string comparison should be removed"
print(" All MouseButton tests passed") print(" All MouseButton tests passed")
def test_input_state_enum(): def test_input_state_enum():
"""Test InputState enum members and backwards compatibility.""" """Test InputState enum members."""
print("Testing InputState enum...") print("Testing InputState enum...")
# Test that enum exists and has expected members # Test that enum exists and has expected members
@ -80,13 +74,13 @@ def test_input_state_enum():
assert int(mcrfpy.InputState.PRESSED) == 0, "InputState.PRESSED should be 0" assert int(mcrfpy.InputState.PRESSED) == 0, "InputState.PRESSED should be 0"
assert int(mcrfpy.InputState.RELEASED) == 1, "InputState.RELEASED should be 1" assert int(mcrfpy.InputState.RELEASED) == 1, "InputState.RELEASED should be 1"
# Test backwards compatibility with legacy strings # Test enum self-comparison
assert mcrfpy.InputState.PRESSED == "start", "InputState.PRESSED should equal 'start'" assert mcrfpy.InputState.PRESSED == mcrfpy.InputState.PRESSED
assert mcrfpy.InputState.RELEASED == "end", "InputState.RELEASED should equal 'end'" assert mcrfpy.InputState.PRESSED != mcrfpy.InputState.RELEASED
# Test that enum name also matches # Verify legacy string comparison is removed (#306)
assert mcrfpy.InputState.PRESSED == "PRESSED", "InputState.PRESSED should also equal 'PRESSED'" assert not (mcrfpy.InputState.PRESSED == "start"), "Legacy string comparison should be removed"
assert mcrfpy.InputState.RELEASED == "RELEASED", "InputState.RELEASED should also equal 'RELEASED'" assert not (mcrfpy.InputState.RELEASED == "end"), "Legacy string comparison should be removed"
print(" All InputState tests passed") print(" All InputState tests passed")

View file

@ -70,81 +70,81 @@ def handle_key(key, action):
"""Handle keyboard input for scene transitions.""" """Handle keyboard input for scene transitions."""
global current_transition, transition_duration global current_transition, transition_duration
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
current_scene = (mcrfpy.current_scene.name if mcrfpy.current_scene else None) current_scene = (mcrfpy.current_scene.name if mcrfpy.current_scene else None)
# Number keys set transition type # Number keys set transition type
keyselections = { keyselections = {
"Num1": mcrfpy.Transition.FADE, mcrfpy.Key.NUM_1: mcrfpy.Transition.FADE,
"Num2": mcrfpy.Transition.SLIDE_LEFT, mcrfpy.Key.NUM_2: mcrfpy.Transition.SLIDE_LEFT,
"Num3": mcrfpy.Transition.SLIDE_RIGHT, mcrfpy.Key.NUM_3: mcrfpy.Transition.SLIDE_RIGHT,
"Num4": mcrfpy.Transition.SLIDE_UP, mcrfpy.Key.NUM_4: mcrfpy.Transition.SLIDE_UP,
"Num5": mcrfpy.Transition.SLIDE_DOWN, mcrfpy.Key.NUM_5: mcrfpy.Transition.SLIDE_DOWN,
"Num6": mcrfpy.Transition.NONE mcrfpy.Key.NUM_6: mcrfpy.Transition.NONE
} }
if key in keyselections: if key in keyselections:
current_transition = keyselections[key] current_transition = keyselections[key]
print(f"Transition set to: {current_transition}") print(f"Transition set to: {current_transition}")
#if key == "Num1": #if key == mcrfpy.Key.NUM_1:
# current_transition = "fade" # current_transition = "fade"
# print("Transition set to: fade") # print("Transition set to: fade")
#elif key == "Num2": #elif key == mcrfpy.Key.NUM_2:
# current_transition = "slide_left" # current_transition = "slide_left"
# print("Transition set to: slide_left") # print("Transition set to: slide_left")
#elif key == "Num3": #elif key == mcrfpy.Key.NUM_3:
# current_transition = "slide_right" # current_transition = "slide_right"
# print("Transition set to: slide_right") # print("Transition set to: slide_right")
#elif key == "Num4": #elif key == mcrfpy.Key.NUM_4:
# current_transition = "slide_up" # current_transition = "slide_up"
# print("Transition set to: slide_up") # print("Transition set to: slide_up")
#elif key == "Num5": #elif key == mcrfpy.Key.NUM_5:
# current_transition = "slide_down" # current_transition = "slide_down"
# print("Transition set to: slide_down") # print("Transition set to: slide_down")
#elif key == "Num6": #elif key == mcrfpy.Key.NUM_6:
# current_transition = None # Instant # current_transition = None # Instant
# print("Transition set to: instant") # print("Transition set to: instant")
# Letter keys change scene # Letter keys change scene
keytransitions = { keytransitions = {
"R": red_scene, mcrfpy.Key.R: red_scene,
"B": blue_scene, mcrfpy.Key.B: blue_scene,
"G": green_scene, mcrfpy.Key.G: green_scene,
"M": menu_scene mcrfpy.Key.M: menu_scene
} }
if key in keytransitions: if key in keytransitions:
if mcrfpy.current_scene != keytransitions[key]: if mcrfpy.current_scene != keytransitions[key]:
keytransitions[key].activate(current_transition, transition_duration) keytransitions[key].activate(current_transition, transition_duration)
#elif key == "R": #elif key == mcrfpy.Key.R:
# if current_scene != "red_scene": # if current_scene != "red_scene":
# print(f"Transitioning to red_scene with {current_transition}") # print(f"Transitioning to red_scene with {current_transition}")
# if current_transition: # if current_transition:
# mcrfpy.setScene("red_scene", current_transition, transition_duration) # mcrfpy.setScene("red_scene", current_transition, transition_duration)
# else: # else:
# red_scene.activate() # red_scene.activate()
#elif key == "B": #elif key == mcrfpy.Key.B:
# if current_scene != "blue_scene": # if current_scene != "blue_scene":
# print(f"Transitioning to blue_scene with {current_transition}") # print(f"Transitioning to blue_scene with {current_transition}")
# if current_transition: # if current_transition:
# mcrfpy.setScene("blue_scene", current_transition, transition_duration) # mcrfpy.setScene("blue_scene", current_transition, transition_duration)
# else: # else:
# blue_scene.activate() # blue_scene.activate()
#elif key == "G": #elif key == mcrfpy.Key.G:
# if current_scene != "green_scene": # if current_scene != "green_scene":
# print(f"Transitioning to green_scene with {current_transition}") # print(f"Transitioning to green_scene with {current_transition}")
# if current_transition: # if current_transition:
# mcrfpy.setScene("green_scene", current_transition, transition_duration) # mcrfpy.setScene("green_scene", current_transition, transition_duration)
# else: # else:
# green_scene.activate() # green_scene.activate()
#elif key == "M": #elif key == mcrfpy.Key.M:
# if current_scene != "menu_scene": # if current_scene != "menu_scene":
# print(f"Transitioning to menu_scene with {current_transition}") # print(f"Transitioning to menu_scene with {current_transition}")
# if current_transition: # if current_transition:
# mcrfpy.setScene("menu_scene", current_transition, transition_duration) # mcrfpy.setScene("menu_scene", current_transition, transition_duration)
# else: # else:
# menu_scene.activate() # menu_scene.activate()
elif key == "Escape": elif key == mcrfpy.Key.ESCAPE:
print("Exiting...") print("Exiting...")
sys.exit(0) sys.exit(0)

View file

@ -78,9 +78,9 @@ def create_demo():
# Keyboard handler # Keyboard handler
def handle_keys(scene_name, key): def handle_keys(scene_name, key):
if not focus_mgr.handle_key(key): if not focus_mgr.handle_key(key):
if key == "Tab": if key == mcrfpy.Key.TAB:
focus_mgr.focus_next() focus_mgr.focus_next()
elif key == "Escape": elif key == mcrfpy.Key.ESCAPE:
print("\nFinal values:") print("\nFinal values:")
for i, inp in enumerate(inputs): for i, inp in enumerate(inputs):
print(f" Field {i+1}: '{inp.get_text()}'") print(f" Field {i+1}: '{inp.get_text()}'")

View file

@ -45,11 +45,13 @@ def test_key_enum_members():
check("IE: Key enum members", test_key_enum_members) check("IE: Key enum members", test_key_enum_members)
def test_inputstate_legacy(): def test_inputstate_values():
assert mcrfpy.InputState.PRESSED == "start" assert int(mcrfpy.InputState.PRESSED) == 0
assert mcrfpy.InputState.RELEASED == "end" assert int(mcrfpy.InputState.RELEASED) == 1
# Legacy string comparison removed (#306)
assert mcrfpy.InputState.PRESSED != "start"
check("IE: InputState legacy string compat", test_inputstate_legacy) check("IE: InputState enum values", test_inputstate_values)
def test_click_handler(): def test_click_handler():
frame = mcrfpy.Frame(pos=(100, 100), size=(200, 50)) frame = mcrfpy.Frame(pos=(100, 100), size=(200, 50))