From 6d5e99a1146b13d8dd3759dc0df6fc4330e3bf60 Mon Sep 17 00:00:00 2001 From: John McCardle Date: Thu, 9 Apr 2026 22:19:02 -0400 Subject: [PATCH] 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 --- src/PyInputState.cpp | 69 +++----------- src/PyInputState.h | 13 +-- src/PyKey.cpp | 63 ++---------- src/PyKey.h | 8 +- src/PyMouseButton.cpp | 95 +++++-------------- src/PyMouseButton.h | 17 ++-- tests/benchmarks/benchmark_moving_entities.py | 2 +- tests/cookbook/apps/calculator.py | 23 +++-- tests/cookbook/apps/dialogue_system.py | 19 ++-- tests/cookbook/compound/shop_demo.py | 4 +- tests/cookbook/cookbook_main.py | 2 +- .../cookbook/features/demo_animation_chain.py | 16 ++-- tests/cookbook/features/demo_rotation.py | 23 ++--- tests/cookbook/features/demo_shaders.py | 13 +-- tests/cookbook/lib/button.py | 6 +- tests/cookbook/lib/choice_list.py | 2 +- tests/cookbook/lib/grid_container.py | 2 +- tests/cookbook/lib/item_manager.py | 8 +- tests/cookbook/lib/modal.py | 4 +- tests/cookbook/lib/scrollable_list.py | 2 +- tests/cookbook/primitives/demo_button.py | 14 +-- tests/cookbook/primitives/demo_choice_list.py | 16 ++-- .../cookbook/primitives/demo_click_pickup.py | 10 +- .../primitives/demo_drag_drop_frame.py | 4 +- .../primitives/demo_drag_drop_grid.py | 10 +- tests/cookbook/primitives/demo_stat_bar.py | 10 +- tests/cookbook/primitives/demo_text_box.py | 14 +-- tests/cookbook/primitives/demo_toast.py | 18 ++-- tests/demo/demo_main.py | 13 ++- tests/demo/perspective_patrol_demo.py | 8 +- tests/demo/screens/focus_system_demo.py | 62 ++++++------ tests/docs/test_features_scenes.py | 2 +- tests/docs/test_quickstart_entities.py | 10 +- tests/docs/test_quickstart_simple_scene.py | 2 +- tests/geometry_demo/geometry_main.py | 13 ++- tests/gui.py | 6 +- tests/integration/astar_vs_dijkstra.py | 14 +-- tests/integration/dijkstra_all_paths.py | 23 +++-- tests/integration/dijkstra_cycle_paths.py | 12 +-- tests/integration/interactive_visibility.py | 39 ++++---- tests/procgen_interactive/core/demo_base.py | 23 +++-- tests/procgen_interactive/main.py | 17 ++-- .../subclass_callback_segfault_test.py | 6 +- tests/shader_poc_test.py | 2 +- tests/shader_toggle_test.py | 8 +- tests/unit/test_animation_chaining.py | 20 ++-- tests/unit/test_callback_vector.py | 10 +- tests/unit/test_entity_animation.py | 16 ++-- tests/unit/test_input_enums.py | 54 +++++------ tests/unit/test_scene_transitions.py | 44 ++++----- tests/unit/test_text_input.py | 4 +- tests/wiki_snippets_verify.py | 10 +- 52 files changed, 372 insertions(+), 533 deletions(-) diff --git a/src/PyInputState.cpp b/src/PyInputState.cpp index ea7392f..3597113 100644 --- a/src/PyInputState.cpp +++ b/src/PyInputState.cpp @@ -4,24 +4,19 @@ // Static storage for cached enum class reference 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 { 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"}, + {"PRESSED", 0}, + {"RELEASED", 1}, }; 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; @@ -31,13 +26,8 @@ PyObject* PyInputState::create_enum_class(PyObject* module) { 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 << " PRESSED: Key or button was pressed\n"; + code << " RELEASED: Key or button was released\n"; code << " \"\"\"\n"; // 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"; } - // 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 - -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 -)"; + code << "\n"; + code << "InputState.__hash__ = lambda self: hash(int(self))\n"; + code << "InputState.__repr__ = lambda self: f\"{type(self).__name__}.{self.name}\"\n"; + code << "InputState.__str__ = lambda self: self.name\n"; std::string code_str = code.str(); @@ -167,25 +125,22 @@ int PyInputState::from_arg(PyObject* arg, bool* out_pressed) { return 0; } - // Accept string (both new and legacy names) + // Accept string (enum name only) 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) { + if (strcmp(name, input_state_table[i].name) == 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); + "Unknown InputState: '%s'. Use InputState.PRESSED or InputState.RELEASED.", name); return 0; } diff --git a/src/PyInputState.h b/src/PyInputState.h index 971cdbd..6042238 100644 --- a/src/PyInputState.h +++ b/src/PyInputState.h @@ -6,10 +6,8 @@ // 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") +// PRESSED = 0 +// RELEASED = 1 class PyInputState { public: @@ -18,14 +16,11 @@ public: 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 + // Accepts InputState enum, string (enum name), or int // 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); - // 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; diff --git a/src/PyKey.cpp b/src/PyKey.cpp index 54ea5ea..4544cf5 100644 --- a/src/PyKey.cpp +++ b/src/PyKey.cpp @@ -143,15 +143,6 @@ static const KeyEntry key_table[] = { 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(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 || @@ -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 << " 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 @@ -193,42 +179,10 @@ PyObject* PyKey::create_enum_class(PyObject* module) { 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 - -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 -)"; + code << "\n"; + code << "Key.__hash__ = lambda self: hash(int(self))\n"; + code << "Key.__repr__ = lambda self: f\"{type(self).__name__}.{self.name}\"\n"; + code << "Key.__str__ = lambda self: self.name\n"; std::string code_str = code.str(); @@ -315,25 +269,22 @@ int PyKey::from_arg(PyObject* arg, sf::Keyboard::Key* out_key) { return 0; } - // Accept string (both new and legacy names) + // Accept string (enum name only) 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) { + if (strcmp(name, key_table[i].name) == 0) { *out_key = static_cast(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); + "Unknown Key: '%s'. Use Key enum members (e.g., Key.ESCAPE, Key.A).", name); return 0; } diff --git a/src/PyKey.h b/src/PyKey.h index a77b960..c7b4668 100644 --- a/src/PyKey.h +++ b/src/PyKey.h @@ -6,7 +6,6 @@ // 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 @@ -24,14 +23,11 @@ public: static PyObject* create_enum_class(PyObject* module); // 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) 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 + // Convert string name to sf::Keyboard::Key (used by C++ event dispatch) // Returns sf::Keyboard::Unknown if not found static sf::Keyboard::Key from_legacy_string(const char* name); diff --git a/src/PyMouseButton.cpp b/src/PyMouseButton.cpp index 95b85b4..cdb387e 100644 --- a/src/PyMouseButton.cpp +++ b/src/PyMouseButton.cpp @@ -4,11 +4,10 @@ // Static storage for cached enum class reference 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 { 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 }; // 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 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"}, - {"SCROLL_UP", SCROLL_UP_VALUE, "wheel_up"}, - {"SCROLL_DOWN", SCROLL_DOWN_VALUE, "wheel_down"}, + {"LEFT", sf::Mouse::Left}, + {"RIGHT", sf::Mouse::Right}, + {"MIDDLE", sf::Mouse::Middle}, + {"X1", sf::Mouse::XButton1}, + {"X2", sf::Mouse::XButton2}, + {"SCROLL_UP", SCROLL_UP_VALUE}, + {"SCROLL_DOWN", SCROLL_DOWN_VALUE}, }; 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(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; @@ -45,19 +35,13 @@ PyObject* PyMouseButton::create_enum_class(PyObject* module) { code << " \"\"\"Enum representing mouse buttons and scroll wheel.\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 << " SCROLL_UP: Scroll wheel up (legacy: 'wheel_up')\n"; - code << " SCROLL_DOWN: Scroll wheel down (legacy: '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 << " LEFT: Left mouse button\n"; + code << " RIGHT: Right mouse button\n"; + code << " MIDDLE: Middle mouse button / scroll wheel click\n"; + code << " X1: Extra mouse button 1\n"; + code << " X2: Extra mouse button 2\n"; + code << " SCROLL_UP: Scroll wheel up\n"; + code << " SCROLL_DOWN: Scroll wheel down\n"; code << " \"\"\"\n"; // 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"; } - // 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 - -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 -)"; + code << "\n"; + code << "MouseButton.__hash__ = lambda self: hash(int(self))\n"; + code << "MouseButton.__repr__ = lambda self: f\"{type(self).__name__}.{self.name}\"\n"; + code << "MouseButton.__str__ = lambda self: self.name\n"; std::string code_str = code.str(); @@ -195,17 +147,15 @@ int PyMouseButton::from_arg(PyObject* arg, sf::Mouse::Button* out_button) { return 0; } - // Accept string (both new and legacy names) + // Accept string (enum name only) 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) { + if (strcmp(name, mouse_button_table[i].name) == 0) { *out_button = static_cast(mouse_button_table[i].value); return 1; } @@ -213,8 +163,7 @@ int PyMouseButton::from_arg(PyObject* arg, sf::Mouse::Button* out_button) { 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); + "MouseButton.MIDDLE, MouseButton.X1, MouseButton.X2.", name); return 0; } diff --git a/src/PyMouseButton.h b/src/PyMouseButton.h index 9eb2019..c02058b 100644 --- a/src/PyMouseButton.h +++ b/src/PyMouseButton.h @@ -6,13 +6,11 @@ // 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") +// LEFT = 0 +// RIGHT = 1 +// MIDDLE = 2 +// X1 = 3 +// X2 = 4 class PyMouseButton { public: @@ -21,13 +19,10 @@ public: 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 + // Accepts MouseButton enum, string (enum name), or int // 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; diff --git a/tests/benchmarks/benchmark_moving_entities.py b/tests/benchmarks/benchmark_moving_entities.py index a8e73b7..6465c01 100644 --- a/tests/benchmarks/benchmark_moving_entities.py +++ b/tests/benchmarks/benchmark_moving_entities.py @@ -90,7 +90,7 @@ print("=" * 60) # Exit handler 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") sys.exit(0) diff --git a/tests/cookbook/apps/calculator.py b/tests/cookbook/apps/calculator.py index b6d6b73..bfd63b0 100644 --- a/tests/cookbook/apps/calculator.py +++ b/tests/cookbook/apps/calculator.py @@ -183,24 +183,23 @@ class Calculator: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: # Switch to game scene mcrfpy.current_scene = game_scene return # Map keys to buttons key_map = { - "Num0": "0", "Num1": "1", "Num2": "2", "Num3": "3", - "Num4": "4", "Num5": "5", "Num6": "6", "Num7": "7", - "Num8": "8", "Num9": "9", - "Period": ".", "Add": "+", "Subtract": "-", - "Multiply": "*", "Divide": "/", - "Enter": "=", "Return": "=", - "C": "C", "Backspace": "DEL", - "LParen": "(", "RParen": ")", + mcrfpy.Key.NUM_0: "0", mcrfpy.Key.NUM_1: "1", mcrfpy.Key.NUM_2: "2", mcrfpy.Key.NUM_3: "3", + mcrfpy.Key.NUM_4: "4", mcrfpy.Key.NUM_5: "5", mcrfpy.Key.NUM_6: "6", mcrfpy.Key.NUM_7: "7", + mcrfpy.Key.NUM_8: "8", mcrfpy.Key.NUM_9: "9", + mcrfpy.Key.PERIOD: ".", mcrfpy.Key.ADD: "+", mcrfpy.Key.SUBTRACT: "-", + mcrfpy.Key.MULTIPLY: "*", mcrfpy.Key.DIVIDE: "/", + mcrfpy.Key.ENTER: "=", + mcrfpy.Key.C: "C", mcrfpy.Key.BACKSPACE: "DEL", } if key in key_map: @@ -271,10 +270,10 @@ class GamePlaceholder: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: # Switch to calculator calculator.activate() diff --git a/tests/cookbook/apps/dialogue_system.py b/tests/cookbook/apps/dialogue_system.py index 9f8ee93..1dd4a0f 100644 --- a/tests/cookbook/apps/dialogue_system.py +++ b/tests/cookbook/apps/dialogue_system.py @@ -444,25 +444,26 @@ class DialogueSystem: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: sys.exit(0) - elif key in ("Num1", "Num2", "Num3", "Num4"): - idx = int(key[-1]) - 1 + elif key in (mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4): + _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): self.choice_list.set_selected(idx) self.choice_list.confirm() - elif key == "Up": + elif key == mcrfpy.Key.UP: self.choice_list.navigate(-1) - elif key == "Down": + elif key == mcrfpy.Key.DOWN: self.choice_list.navigate(1) - elif key == "Enter": + elif key == mcrfpy.Key.ENTER: self.choice_list.confirm() - elif key == "Space": + elif key == mcrfpy.Key.SPACE: self.dialogue_box.skip_animation() - elif key == "R": + elif key == mcrfpy.Key.R: # Restart self.npc.mood = "neutral" self.npc.trust = 50 diff --git a/tests/cookbook/compound/shop_demo.py b/tests/cookbook/compound/shop_demo.py index 6ab7559..bbb3095 100644 --- a/tests/cookbook/compound/shop_demo.py +++ b/tests/cookbook/compound/shop_demo.py @@ -396,10 +396,10 @@ class ShopDemo: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: if self.manager.held_item: self.manager.cancel_pickup() self.tooltip.text = "Cancelled" diff --git a/tests/cookbook/cookbook_main.py b/tests/cookbook/cookbook_main.py index b21150a..05fc993 100644 --- a/tests/cookbook/cookbook_main.py +++ b/tests/cookbook/cookbook_main.py @@ -206,7 +206,7 @@ class CookbookLauncher: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return category = self.categories[self.selected_category] diff --git a/tests/cookbook/features/demo_animation_chain.py b/tests/cookbook/features/demo_animation_chain.py index bfd0c83..77e88a9 100644 --- a/tests/cookbook/features/demo_animation_chain.py +++ b/tests/cookbook/features/demo_animation_chain.py @@ -377,22 +377,22 @@ class AnimationDemo: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: sys.exit(0) - elif key == "Num1": + elif key == mcrfpy.Key.NUM_1: self.run_chain_demo() - elif key == "Num2": + elif key == mcrfpy.Key.NUM_2: self.run_group_demo() - elif key == "Num3": + elif key == mcrfpy.Key.NUM_3: self.run_callback_demo() - elif key == "Num4": + elif key == mcrfpy.Key.NUM_4: self.run_loop_demo() - elif key == "Num5": + elif key == mcrfpy.Key.NUM_5: self.run_combined_demo() - elif key == "R": + elif key == mcrfpy.Key.R: self.reset_all() def activate(self): diff --git a/tests/cookbook/features/demo_rotation.py b/tests/cookbook/features/demo_rotation.py index 2284bf7..63fd0f4 100644 --- a/tests/cookbook/features/demo_rotation.py +++ b/tests/cookbook/features/demo_rotation.py @@ -365,38 +365,39 @@ class RotationDemo: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: sys.exit(0) - elif key == "Left": + elif key == mcrfpy.Key.LEFT: # Rotate left (counter-clockwise) name, element = self.elements[self.selected] try: element.rotation = (element.rotation - 15) % 360 except AttributeError: pass - elif key == "Right": + elif key == mcrfpy.Key.RIGHT: # Rotate right (clockwise) name, element = self.elements[self.selected] try: element.rotation = (element.rotation + 15) % 360 except AttributeError: pass - elif key == "Up": + elif key == mcrfpy.Key.UP: self.rotation_speed = min(180, self.rotation_speed + 15) 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.speed_label.text = f"Speed: {self.rotation_speed}°/sec" - elif key in ("Num1", "Num2", "Num3", "Num4"): - self.selected = int(key[-1]) - 1 + elif key in (mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4): + _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): self.selected_label.text = f"Selected: {self.elements[self.selected][0]}" - elif key == "O": + elif key == mcrfpy.Key.O: self._cycle_origin() - elif key == "A": + elif key == mcrfpy.Key.A: self.auto_rotate = not self.auto_rotate if self.auto_rotate: self.auto_label.text = "Auto-rotate: On" @@ -404,7 +405,7 @@ class RotationDemo: else: self.auto_label.text = "Auto-rotate: Off" self.auto_label.fill_color = mcrfpy.Color(200, 100, 100) - elif key == "R": + elif key == mcrfpy.Key.R: # Reset all rotations for name, element in self.elements: try: diff --git a/tests/cookbook/features/demo_shaders.py b/tests/cookbook/features/demo_shaders.py index ea48ecd..1f758a7 100644 --- a/tests/cookbook/features/demo_shaders.py +++ b/tests/cookbook/features/demo_shaders.py @@ -296,20 +296,21 @@ class ShaderDemo: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: sys.exit(0) - elif key == "Space": + elif key == mcrfpy.Key.SPACE: self.toggle_shaders() - elif key == "R": + elif key == mcrfpy.Key.R: # Re-enable all shaders self.shaders_enabled = False 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) - 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): self.status.text = f"Focused: Shader {idx + 1}" diff --git a/tests/cookbook/lib/button.py b/tests/cookbook/lib/button.py index 1ab4fd6..3d10cbf 100644 --- a/tests/cookbook/lib/button.py +++ b/tests/cookbook/lib/button.py @@ -102,13 +102,13 @@ class Button: if not self._enabled: return - if button == "left": - if action == "start": + if button == mcrfpy.MouseButton.LEFT: + if action == mcrfpy.InputState.PRESSED: self.is_pressed = True self.frame.fill_color = self.press_color # Animate a subtle press effect self._animate_press() - elif action == "end": + elif action == mcrfpy.InputState.RELEASED: self.is_pressed = False # Restore hover or normal state if self.is_hovered: diff --git a/tests/cookbook/lib/choice_list.py b/tests/cookbook/lib/choice_list.py index 25ae030..8e8e529 100644 --- a/tests/cookbook/lib/choice_list.py +++ b/tests/cookbook/lib/choice_list.py @@ -123,7 +123,7 @@ class ChoiceList: idx = i # Capture index in closure def make_click_handler(index): 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) if self.on_select: self.on_select(index, self._choices[index]) diff --git a/tests/cookbook/lib/grid_container.py b/tests/cookbook/lib/grid_container.py index 0d7bfa4..63b52a5 100644 --- a/tests/cookbook/lib/grid_container.py +++ b/tests/cookbook/lib/grid_container.py @@ -114,7 +114,7 @@ class GridContainer: # Set up event handlers def make_click(cx, cy): 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) return handler diff --git a/tests/cookbook/lib/item_manager.py b/tests/cookbook/lib/item_manager.py index 437676d..d5a7dab 100644 --- a/tests/cookbook/lib/item_manager.py +++ b/tests/cookbook/lib/item_manager.py @@ -138,7 +138,7 @@ class ItemSlot(mcrfpy.Frame): return item.slot_type == self.slot_type def _on_click(self, pos, button, action): - if action != "start" or button != "left": + if action != mcrfpy.InputState.PRESSED or button != mcrfpy.MouseButton.LEFT: return if self.manager: self.manager.handle_slot_click(self.slot_name) @@ -274,14 +274,14 @@ class ItemManager: def _on_grid_click(self, grid_name, pos, button, action): """Handle click on a registered grid.""" - if action != "start": + if action != mcrfpy.InputState.PRESSED: return - if button == "right": + if button == mcrfpy.MouseButton.RIGHT: self.cancel_pickup() return - if button != "left": + if button != mcrfpy.MouseButton.LEFT: return grid, item_map, color_layer = self.grids[grid_name] diff --git a/tests/cookbook/lib/modal.py b/tests/cookbook/lib/modal.py index 62d1394..662fd1f 100644 --- a/tests/cookbook/lib/modal.py +++ b/tests/cookbook/lib/modal.py @@ -194,7 +194,7 @@ class Modal: def make_click(cb): 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() return handler @@ -208,7 +208,7 @@ class Modal: def _on_overlay_click(self, pos, button, action): """Handle clicks on overlay (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 mw, mh = self.modal_frame.w, self.modal_frame.h px, py = pos.x, pos.y diff --git a/tests/cookbook/lib/scrollable_list.py b/tests/cookbook/lib/scrollable_list.py index 4168ba8..8992dc9 100644 --- a/tests/cookbook/lib/scrollable_list.py +++ b/tests/cookbook/lib/scrollable_list.py @@ -138,7 +138,7 @@ class ScrollableList: # Set up click handler def make_click_handler(index): def handler(pos, button, action): - if button == "left" and action == "end": + if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.RELEASED: self.select(index) return handler diff --git a/tests/cookbook/primitives/demo_button.py b/tests/cookbook/primitives/demo_button.py index c58b429..37741c0 100644 --- a/tests/cookbook/primitives/demo_button.py +++ b/tests/cookbook/primitives/demo_button.py @@ -245,20 +245,20 @@ class ButtonDemo: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: 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() - elif key == "Num2" and len(self.buttons) > 1: + elif key == mcrfpy.Key.NUM_2 and len(self.buttons) > 1: 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() - elif key == "Num4" and len(self.buttons) > 3: + elif key == mcrfpy.Key.NUM_4 and len(self.buttons) > 3: self.buttons[3].callback() - elif key == "D": + elif key == mcrfpy.Key.D: # Toggle disabled button self.disabled_btn.enabled = not self.disabled_btn.enabled if self.disabled_btn.enabled: diff --git a/tests/cookbook/primitives/demo_choice_list.py b/tests/cookbook/primitives/demo_choice_list.py index 5d89db5..688a4b7 100644 --- a/tests/cookbook/primitives/demo_choice_list.py +++ b/tests/cookbook/primitives/demo_choice_list.py @@ -230,31 +230,31 @@ class ChoiceListDemo: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: sys.exit(0) # Get active list 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) - elif key == "Down" and active: + elif key == mcrfpy.Key.DOWN and active: active.navigate(1) - elif key == "Enter" and active: + elif key == mcrfpy.Key.ENTER and active: active.confirm() - elif key == "Tab": + elif key == mcrfpy.Key.TAB: # Switch active list self.active_list_idx = (self.active_list_idx + 1) % len(self.lists) self._update_active_indicator() - elif key == "A": + elif key == mcrfpy.Key.A: # Add item to dynamic list self.add_counter += 1 self.dynamic_list.add_choice(f"New Item {self.add_counter}") self.dynamic_info.text = f"Items: {len(self.dynamic_list.choices)}" - elif key == "R": + elif key == mcrfpy.Key.R: # Remove selected from dynamic list if len(self.dynamic_list.choices) > 1: self.dynamic_list.remove_choice(self.dynamic_list.selected_index) diff --git a/tests/cookbook/primitives/demo_click_pickup.py b/tests/cookbook/primitives/demo_click_pickup.py index 171646d..2775c21 100644 --- a/tests/cookbook/primitives/demo_click_pickup.py +++ b/tests/cookbook/primitives/demo_click_pickup.py @@ -187,7 +187,7 @@ class ClickPickupDemo: def _on_grid_click(self, pos, button, action): """Handle grid click.""" - if action != "start": + if action != mcrfpy.InputState.PRESSED: return cell = self._get_grid_cell(pos) @@ -196,13 +196,13 @@ class ClickPickupDemo: x, y = cell - if button == "right": + if button == mcrfpy.MouseButton.RIGHT: # Cancel pickup if self.held_entity: self._cancel_pickup() return - if button != "left": + if button != mcrfpy.MouseButton.LEFT: return if self.held_entity is None: @@ -334,10 +334,10 @@ class ClickPickupDemo: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: if self.held_entity: self._cancel_pickup() return diff --git a/tests/cookbook/primitives/demo_drag_drop_frame.py b/tests/cookbook/primitives/demo_drag_drop_frame.py index 804d6f8..65bb858 100644 --- a/tests/cookbook/primitives/demo_drag_drop_frame.py +++ b/tests/cookbook/primitives/demo_drag_drop_frame.py @@ -234,9 +234,9 @@ class DragDropFrameDemo: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: # Return to cookbook menu or exit try: from cookbook_main import main diff --git a/tests/cookbook/primitives/demo_drag_drop_grid.py b/tests/cookbook/primitives/demo_drag_drop_grid.py index efaddd1..ae729fe 100644 --- a/tests/cookbook/primitives/demo_drag_drop_grid.py +++ b/tests/cookbook/primitives/demo_drag_drop_grid.py @@ -142,7 +142,7 @@ class GridDragDropDemo: def _on_grid_click(self, pos, button, action): """Handle grid click for drag start/end.""" - if button != "left": + if button != mcrfpy.MouseButton.LEFT: return # 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): return - if action == "start": + if action == mcrfpy.InputState.PRESSED: # Start drag if there's an entity here entity = self._get_entity_at(grid_x, grid_y) if entity: @@ -166,7 +166,7 @@ class GridDragDropDemo: # Highlight start cell yellow self.color_layer.set((grid_x, grid_y), (255, 255, 100, 200)) - elif action == "end": + elif action == mcrfpy.InputState.RELEASED: if self.dragging_entity: # Drop the entity target_cell = (grid_x, grid_y) @@ -224,9 +224,9 @@ class GridDragDropDemo: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: # Cancel any drag in progress if self.dragging_entity and self.drag_start_cell: self.dragging_entity.grid_pos = self.drag_start_cell diff --git a/tests/cookbook/primitives/demo_stat_bar.py b/tests/cookbook/primitives/demo_stat_bar.py index 28d15a8..799e1f1 100644 --- a/tests/cookbook/primitives/demo_stat_bar.py +++ b/tests/cookbook/primitives/demo_stat_bar.py @@ -275,15 +275,15 @@ class StatBarDemo: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: sys.exit(0) # Number keys to modify bars 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: idx = key_map[key] @@ -293,11 +293,11 @@ class StatBarDemo: bar.set_value(bar.current - 10, animate=True) self.status.text = f"Status: Decreased {bar_keys[idx].upper()}" - elif key == "F": + elif key == mcrfpy.Key.F: self.flash_bar.flash() self.status.text = "Status: Flash effect triggered!" - elif key == "R": + elif key == mcrfpy.Key.R: # Reset all bars self.bars['hp'].set_value(75, 100, animate=True) self.bars['mp'].set_value(50, 80, animate=True) diff --git a/tests/cookbook/primitives/demo_text_box.py b/tests/cookbook/primitives/demo_text_box.py index a92befa..6cdba44 100644 --- a/tests/cookbook/primitives/demo_text_box.py +++ b/tests/cookbook/primitives/demo_text_box.py @@ -182,18 +182,18 @@ class TextBoxDemo: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: sys.exit(0) - elif key == "Num1": + elif key == mcrfpy.Key.NUM_1: # Start typewriter animation self.typewriter_box.on_complete = self.on_typewriter_complete self.typewriter_box.set_text(self.sample_text, animate=True) self.completion_label.text = "Status: Playing..." self.completion_label.fill_color = mcrfpy.Color(200, 200, 100) - elif key == "Num2": + elif key == mcrfpy.Key.NUM_2: # Change instant text texts = [ "This text appeared instantly. Press 2 to change it to different content.", @@ -203,17 +203,17 @@ class TextBoxDemo: ] import random self.instant_box.set_text(random.choice(texts), animate=False) - elif key == "Num3": + elif key == mcrfpy.Key.NUM_3: # Skip animation self.typewriter_box.skip_animation() self.completion_label.text = "Status: Skipped" self.completion_label.fill_color = mcrfpy.Color(150, 150, 150) - elif key == "Num4": + elif key == mcrfpy.Key.NUM_4: # Clear text self.typewriter_box.clear() self.completion_label.text = "Status: Cleared" self.completion_label.fill_color = mcrfpy.Color(150, 150, 150) - elif key == "D": + elif key == mcrfpy.Key.D: # Cycle dialogue self.dialogue_index = (self.dialogue_index + 1) % len(self.dialogues) speaker, text = self.dialogues[self.dialogue_index] diff --git a/tests/cookbook/primitives/demo_toast.py b/tests/cookbook/primitives/demo_toast.py index 0d80274..e863770 100644 --- a/tests/cookbook/primitives/demo_toast.py +++ b/tests/cookbook/primitives/demo_toast.py @@ -163,32 +163,32 @@ class ToastDemo: def on_key(self, key, state): """Handle keyboard input.""" - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: sys.exit(0) - elif key == "Num1": + elif key == mcrfpy.Key.NUM_1: self.toast_count += 1 self.toasts.show(f"Default notification #{self.toast_count}") self.update_stats() - elif key == "Num2": + elif key == mcrfpy.Key.NUM_2: self.toast_count += 1 self.toasts.show_success("Operation completed successfully!") self.update_stats() - elif key == "Num3": + elif key == mcrfpy.Key.NUM_3: self.toast_count += 1 self.toasts.show_error("An error occurred!") self.update_stats() - elif key == "Num4": + elif key == mcrfpy.Key.NUM_4: self.toast_count += 1 self.toasts.show_warning("Warning: Low health!") self.update_stats() - elif key == "Num5": + elif key == mcrfpy.Key.NUM_5: self.toast_count += 1 self.toasts.show_info("New quest available") self.update_stats() - elif key == "S": + elif key == mcrfpy.Key.S: # Spam multiple toasts messages = [ "Game saved!", @@ -201,7 +201,7 @@ class ToastDemo: self.toast_count += 1 self.toasts.show(msg) self.update_stats() - elif key == "C": + elif key == mcrfpy.Key.C: self.toasts.dismiss_all() self.update_stats() diff --git a/tests/demo/demo_main.py b/tests/demo/demo_main.py index aa1a47b..728fcdd 100644 --- a/tests/demo/demo_main.py +++ b/tests/demo/demo_main.py @@ -146,21 +146,24 @@ class DemoRunner: self.create_menu() def handle_key(key, state): - if state != "start": + if state != mcrfpy.InputState.PRESSED: return # Number keys 1-9 for direct screen access - if key in [f"Num{n}" for n in "123456789"]: - idx = int(key[-1]) - 1 + _num_key_map = {mcrfpy.Key.NUM_1: 0, mcrfpy.Key.NUM_2: 1, mcrfpy.Key.NUM_3: 2, + 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): mcrfpy.setScene(self.screens[idx].scene_name) # ESC returns to menu - elif key == "Escape": + elif key == mcrfpy.Key.ESCAPE: menu.activate() # Q quits - elif key == "Q": + elif key == mcrfpy.Key.Q: sys.exit(0) # Register keyboard handler on menu scene diff --git a/tests/demo/perspective_patrol_demo.py b/tests/demo/perspective_patrol_demo.py index e8ea0a6..234957c 100644 --- a/tests/demo/perspective_patrol_demo.py +++ b/tests/demo/perspective_patrol_demo.py @@ -162,18 +162,18 @@ def on_keypress(key, state): """Handle keyboard input""" global patrol_paused - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "R": + if key == mcrfpy.Key.R: reset_vision() - elif key == "Space": + elif key == mcrfpy.Key.SPACE: patrol_paused = not patrol_paused if patrol_paused: update_status("Status: PAUSED") else: update_status("Status: Patrolling") - elif key == "Q": + elif key == mcrfpy.Key.Q: mcrfpy.current_scene = None def reset_vision(): diff --git a/tests/demo/screens/focus_system_demo.py b/tests/demo/screens/focus_system_demo.py index 8de447c..e8fabda 100644 --- a/tests/demo/screens/focus_system_demo.py +++ b/tests/demo/screens/focus_system_demo.py @@ -31,14 +31,14 @@ class ModifierTracker: self.ctrl = 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.""" - if key in ("LShift", "RShift"): - self.shift = (action == "start") - elif key in ("LControl", "RControl"): - self.ctrl = (action == "start") - elif key in ("LAlt", "RAlt"): - self.alt = (action == "start") + if key in (mcrfpy.Key.LEFT_SHIFT, mcrfpy.Key.RIGHT_SHIFT): + self.shift = (action == mcrfpy.InputState.PRESSED) + elif key in (mcrfpy.Key.LEFT_CONTROL, mcrfpy.Key.RIGHT_CONTROL): + self.ctrl = (action == mcrfpy.InputState.PRESSED) + elif key in (mcrfpy.Key.LEFT_ALT, mcrfpy.Key.RIGHT_ALT): + self.alt = (action == mcrfpy.InputState.PRESSED) # ============================================================================= @@ -163,16 +163,16 @@ class FocusManager: self.modifiers.update(key, action) # Only process on key press, not release (key repeat sends multiple "start") - if action != "start": + if action != mcrfpy.InputState.PRESSED: return False # Global: Escape closes modals - if key == "Escape": + if key == mcrfpy.Key.ESCAPE: if self.pop_modal(): return True # Global: Tab cycles focus - if key == "Tab": + if key == mcrfpy.Key.TAB: direction = -1 if self.modifiers.shift else 1 self.cycle(direction) return True @@ -251,7 +251,7 @@ class FocusableGrid: def _on_click(self, x, y, button, action): """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) def _update_player_display(self): @@ -272,13 +272,13 @@ class FocusableGrid: self.outline_frame.outline_color = FocusManager.UNFOCUS_COLOR 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.""" moves = { - "W": (0, -1), "Up": (0, -1), - "A": (-1, 0), "Left": (-1, 0), - "S": (0, 1), "Down": (0, 1), - "D": (1, 0), "Right": (1, 0), + mcrfpy.Key.W: (0, -1), mcrfpy.Key.UP: (0, -1), + mcrfpy.Key.A: (-1, 0), mcrfpy.Key.LEFT: (-1, 0), + mcrfpy.Key.S: (0, 1), mcrfpy.Key.DOWN: (0, 1), + mcrfpy.Key.D: (1, 0), mcrfpy.Key.RIGHT: (1, 0), } if key in moves: @@ -373,7 +373,7 @@ class TextInputWidget: def _on_click(self, x, y, button, action): """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) def _update_display(self): @@ -404,7 +404,7 @@ class TextInputWidget: self.cursor.visible = False 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.""" if not self.focused: return False @@ -412,27 +412,27 @@ class TextInputWidget: old_text = self.text handled = True - if key == "BackSpace": + if key == mcrfpy.Key.BACKSPACE: if self.cursor_pos > 0: self.text = self.text[:self.cursor_pos-1] + self.text[self.cursor_pos:] self.cursor_pos -= 1 - elif key == "Delete": + elif key == mcrfpy.Key.DELETE: if self.cursor_pos < len(self.text): 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) - elif key == "Right": + elif key == mcrfpy.Key.RIGHT: self.cursor_pos = min(len(self.text), self.cursor_pos + 1) - elif key == "Home": + elif key == mcrfpy.Key.HOME: self.cursor_pos = 0 - elif key == "End": + elif key == mcrfpy.Key.END: 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 handled = False - elif len(key) == 1 and key.isprintable(): - # Insert character - self.text = self.text[:self.cursor_pos] + key + self.text[self.cursor_pos:] + elif len(key.name) == 1 and key.name.isprintable(): + # Insert character (key.name is "A"-"Z" for letter keys) + self.text = self.text[:self.cursor_pos] + key.name.lower() + self.text[self.cursor_pos:] self.cursor_pos += 1 else: handled = False @@ -509,7 +509,7 @@ class MenuIcon: if not self._focus_manager: return - if action == "start": + if action == mcrfpy.InputState.PRESSED: # If already focused, activate; otherwise just focus if self._focus_manager.focus_index == self._focus_index: self._activate() @@ -535,9 +535,9 @@ class MenuIcon: self.frame.fill_color = mcrfpy.Color(60, 60, 80) self.tooltip_caption.visible = False - def handle_key(self, key: str, action: str) -> bool: + def handle_key(self, key, action) -> bool: """Handle activation keys.""" - if key in ("Space", "Return"): + if key in (mcrfpy.Key.SPACE, mcrfpy.Key.ENTER): self._activate() return True return False diff --git a/tests/docs/test_features_scenes.py b/tests/docs/test_features_scenes.py index 89f6a6d..006f227 100644 --- a/tests/docs/test_features_scenes.py +++ b/tests/docs/test_features_scenes.py @@ -13,7 +13,7 @@ scene = mcrfpy.Scene("test_modern") scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(800, 600))) def my_handler(key, action): - if action == "start": + if action == mcrfpy.InputState.PRESSED: print(f" Key handler received: {key}") scene.on_key = my_handler diff --git a/tests/docs/test_quickstart_entities.py b/tests/docs/test_quickstart_entities.py index dfc668f..22e9f26 100644 --- a/tests/docs/test_quickstart_entities.py +++ b/tests/docs/test_quickstart_entities.py @@ -46,15 +46,15 @@ grid.entities.append(treasure) # Movement handler using modern API def handle_keys(key, state): - if state == "start": + if state == mcrfpy.InputState.PRESSED: x, y = player.pos[0], player.pos[1] - if key == "W": + if key == mcrfpy.Key.W: player.pos = (x, y - 1) - elif key == "S": + elif key == mcrfpy.Key.S: player.pos = (x, y + 1) - elif key == "A": + elif key == mcrfpy.Key.A: player.pos = (x - 1, y) - elif key == "D": + elif key == mcrfpy.Key.D: player.pos = (x + 1, y) scene.on_key = handle_keys diff --git a/tests/docs/test_quickstart_simple_scene.py b/tests/docs/test_quickstart_simple_scene.py index fdcb4f3..1c3081c 100644 --- a/tests/docs/test_quickstart_simple_scene.py +++ b/tests/docs/test_quickstart_simple_scene.py @@ -35,7 +35,7 @@ scene.children.append(grid) # Add keyboard controls using modern API def move_around(key, state): - if state == "start": + if state == mcrfpy.InputState.PRESSED: print(f"You pressed {key}") scene.on_key = move_around diff --git a/tests/geometry_demo/geometry_main.py b/tests/geometry_demo/geometry_main.py index 0a3fe21..ee25f4b 100644 --- a/tests/geometry_demo/geometry_main.py +++ b/tests/geometry_demo/geometry_main.py @@ -169,12 +169,15 @@ class GeometryDemoRunner: self.create_menu() def handle_key(key, state): - if state != "start": + if state != mcrfpy.InputState.PRESSED: return # Number keys 1-9 for direct screen access - if key in [f"Num{n}" for n in "123456789"]: - idx = int(key[-1]) - 1 + _num_key_map = {mcrfpy.Key.NUM_1: 0, mcrfpy.Key.NUM_2: 1, mcrfpy.Key.NUM_3: 2, + 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): # Clean up ALL screen's timers first for screen in self.screens: @@ -185,13 +188,13 @@ class GeometryDemoRunner: self.screens[idx].restart_timers() # ESC returns to menu - elif key == "Escape": + elif key == mcrfpy.Key.ESCAPE: for screen in self.screens: screen.cleanup() geo_menu.activate() # Q quits - elif key == "Q": + elif key == mcrfpy.Key.Q: sys.exit(0) # Register keyboard handler on all scenes diff --git a/tests/gui.py b/tests/gui.py index 34a2f6d..3d5260d 100644 --- a/tests/gui.py +++ b/tests/gui.py @@ -41,14 +41,14 @@ class Draggable(mcrfpy.Frame): self.tick.stop() def minmax(self, pos, btn, event): - if event != "start": return + if event != mcrfpy.InputState.PRESSED: return self.minimized = not self.minimized self.minimize_btn.children[0].text = "+" if self.minimized else "-" self.clock.visible = not self.minimized self.h = 40 if self.minimized else 150 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.drag_start_pos = args[0] 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)}" def spawn(*args): - if args[-1] != "start": return + if args[-1] != mcrfpy.InputState.PRESSED: return 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)]) diff --git a/tests/integration/astar_vs_dijkstra.py b/tests/integration/astar_vs_dijkstra.py index bf785f3..f85fb0b 100644 --- a/tests/integration/astar_vs_dijkstra.py +++ b/tests/integration/astar_vs_dijkstra.py @@ -160,21 +160,21 @@ def show_both(): def handle_keypress(key_str, state): """Handle keyboard input""" global mode - if state == "end": return + if state == mcrfpy.InputState.RELEASED: return 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...") 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" 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" 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" show_both() - elif key_str == "Space": + elif key_str == mcrfpy.Key.SPACE: # Refresh current mode if mode == "ASTAR": show_astar() @@ -203,7 +203,7 @@ ui.append(grid) # Scale and position grid.size = (600, 400) # 30*20, 20*20 -grid.position = (100, 100) +grid.pos = (100, 100) # Add title title = mcrfpy.Caption(pos=(250, 20), text="A* vs Dijkstra Pathfinding") diff --git a/tests/integration/dijkstra_all_paths.py b/tests/integration/dijkstra_all_paths.py index f523b48..aa7db71 100644 --- a/tests/integration/dijkstra_all_paths.py +++ b/tests/integration/dijkstra_all_paths.py @@ -152,21 +152,24 @@ def show_combination(index): def handle_keypress(key_str, state): """Handle keyboard input""" 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...") 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) - elif key_str == "P": + elif key_str == mcrfpy.Key.P: show_combination(current_combo_index - 1) - elif key_str == "R": + elif key_str == mcrfpy.Key.R: show_combination(current_combo_index) - elif key_str in "123456": - combo_num = int(key_str) - 1 # 0-based index - if combo_num < len(all_combinations): - show_combination(combo_num) + else: + num_keys = {mcrfpy.Key.NUM_1: 0, mcrfpy.Key.NUM_2: 1, mcrfpy.Key.NUM_3: 2, + mcrfpy.Key.NUM_4: 3, mcrfpy.Key.NUM_5: 4, mcrfpy.Key.NUM_6: 5} + 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 print("Dijkstra All Paths Demo") @@ -183,7 +186,7 @@ ui.append(grid) # Scale and position grid.size = (560, 400) -grid.position = (120, 100) +grid.pos = (120, 100) # Add title title = mcrfpy.Caption(pos=(200, 20), text="Dijkstra - All Paths (Valid & Invalid)") diff --git a/tests/integration/dijkstra_cycle_paths.py b/tests/integration/dijkstra_cycle_paths.py index a37c088..6086174 100644 --- a/tests/integration/dijkstra_cycle_paths.py +++ b/tests/integration/dijkstra_cycle_paths.py @@ -169,15 +169,15 @@ def show_path(index): def handle_keypress(key_str, state): """Handle keyboard input""" global current_path_index - if state == "end": return - if key_str == "Esc": + if state == mcrfpy.InputState.RELEASED: return + if key_str == mcrfpy.Key.ESCAPE: print("\nExiting...") 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) - elif key_str == "P": + elif key_str == mcrfpy.Key.P: show_path(current_path_index - 1) - elif key_str == "R": + elif key_str == mcrfpy.Key.R: show_path(current_path_index) # Create the demo @@ -194,7 +194,7 @@ ui.append(grid) # Scale and position grid.size = (560, 400) -grid.position = (120, 100) +grid.pos = (120, 100) # Add title title = mcrfpy.Caption(pos=(200, 20), text="Dijkstra Pathfinding - Cycle Paths") diff --git a/tests/integration/interactive_visibility.py b/tests/integration/interactive_visibility.py index 7207b64..56c83db 100644 --- a/tests/integration/interactive_visibility.py +++ b/tests/integration/interactive_visibility.py @@ -79,7 +79,7 @@ perspective_names = ["Omniscient", "Player", "Enemy"] # UI Setup ui = visibility_demo.children ui.append(grid) -grid.position = (50, 100) +grid.pos = (50, 100) grid.size = (900, 600) # 30*30, 20*30 # Title @@ -138,49 +138,48 @@ def cycle_perspective(): # Key handlers def handle_keys(key, state): """Handle keyboard input""" - if state == "end": return - key = key.lower() + if state == mcrfpy.InputState.RELEASED: return # Player movement (WASD) - if key == "w": + if key == mcrfpy.Key.W: move_entity(player, 0, -1) - elif key == "s": + elif key == mcrfpy.Key.S: move_entity(player, 0, 1) - elif key == "a": + elif key == mcrfpy.Key.A: move_entity(player, -1, 0) - elif key == "d": + elif key == mcrfpy.Key.D: move_entity(player, 1, 0) - + # Enemy movement (Arrows) - elif key == "up": + elif key == mcrfpy.Key.UP: move_entity(enemy, 0, -1) - elif key == "down": + elif key == mcrfpy.Key.DOWN: move_entity(enemy, 0, 1) - elif key == "left": + elif key == mcrfpy.Key.LEFT: move_entity(enemy, -1, 0) - elif key == "right": + elif key == mcrfpy.Key.RIGHT: move_entity(enemy, 1, 0) - + # Tab to cycle perspective - elif key == "tab": + elif key == mcrfpy.Key.TAB: cycle_perspective() - + # Space to update visibility - elif key == "space": + elif key == mcrfpy.Key.SPACE: player.update_visibility() enemy.update_visibility() print("Updated visibility for both entities") - + # R to reset - elif key == "r": + elif key == mcrfpy.Key.R: player.x, player.y = 5, 10 enemy.x, enemy.y = 25, 10 player.update_visibility() enemy.update_visibility() update_info() print("Reset positions") - + # Q to quit - elif key == "q": + elif key == mcrfpy.Key.Q: print("Exiting...") sys.exit(0) diff --git a/tests/procgen_interactive/core/demo_base.py b/tests/procgen_interactive/core/demo_base.py index 939e585..e01543f 100644 --- a/tests/procgen_interactive/core/demo_base.py +++ b/tests/procgen_interactive/core/demo_base.py @@ -456,18 +456,17 @@ class ProcgenDemoBase(ABC): elif key == mcrfpy.Key.ESCAPE: self._return_to_menu() else: - # Number keys for layer toggles - convert to string for parsing - key_str = str(key) if not isinstance(key, str) else key - if key_str.startswith("Key.NUM") or (len(key_str) == 1 and key_str.isdigit()): - try: - num = int(key_str[-1]) - if 1 <= num <= len(self.layer_defs): - layer_def = self.layer_defs[num - 1] - toggle = self.widgets.get(f"layer_{layer_def.name}") - if toggle: - toggle.toggle() - except (ValueError, IndexError): - pass + # Number keys for layer toggles + _num_key_map = {mcrfpy.Key.NUM_1: 1, mcrfpy.Key.NUM_2: 2, mcrfpy.Key.NUM_3: 3, + mcrfpy.Key.NUM_4: 4, mcrfpy.Key.NUM_5: 5, mcrfpy.Key.NUM_6: 6, + mcrfpy.Key.NUM_7: 7, mcrfpy.Key.NUM_8: 8, mcrfpy.Key.NUM_9: 9} + if key in _num_key_map: + num = _num_key_map[key] + if 1 <= num <= len(self.layer_defs): + layer_def = self.layer_defs[num - 1] + toggle = self.widgets.get(f"layer_{layer_def.name}") + if toggle: + toggle.toggle() def _return_to_menu(self): """Return to demo menu.""" diff --git a/tests/procgen_interactive/main.py b/tests/procgen_interactive/main.py index eedc677..dc556f5 100644 --- a/tests/procgen_interactive/main.py +++ b/tests/procgen_interactive/main.py @@ -160,17 +160,14 @@ class DemoLauncher: if action != mcrfpy.InputState.PRESSED: 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 - if key_str.startswith("Key.NUM") or (len(key_str) == 1 and key_str.isdigit()): - try: - num = int(key_str[-1]) - if 1 <= num <= len(self.DEMOS): - self._launch_demo(num - 1) - except (ValueError, IndexError): - pass + _num_key_map = {mcrfpy.Key.NUM_1: 1, mcrfpy.Key.NUM_2: 2, mcrfpy.Key.NUM_3: 3, + mcrfpy.Key.NUM_4: 4, mcrfpy.Key.NUM_5: 5, mcrfpy.Key.NUM_6: 6, + mcrfpy.Key.NUM_7: 7, mcrfpy.Key.NUM_8: 8, mcrfpy.Key.NUM_9: 9} + if key in _num_key_map: + num = _num_key_map[key] + if 1 <= num <= len(self.DEMOS): + self._launch_demo(num - 1) elif key == mcrfpy.Key.ESCAPE: sys.exit(0) diff --git a/tests/regression/subclass_callback_segfault_test.py b/tests/regression/subclass_callback_segfault_test.py index 19bed76..a5fb6e9 100644 --- a/tests/regression/subclass_callback_segfault_test.py +++ b/tests/regression/subclass_callback_segfault_test.py @@ -27,7 +27,7 @@ def test_minimal(): print("Attempting to call on_click...") try: - obj.on_click((50, 50), "left", "start") + obj.on_click((50, 50), mcrfpy.MouseButton.LEFT, mcrfpy.InputState.PRESSED) print("Call succeeded!") except Exception as 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)) 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...") del obj @@ -69,7 +69,7 @@ def test_added_to_scene(): scene.children.append(obj) 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...") sys.exit(0) diff --git a/tests/shader_poc_test.py b/tests/shader_poc_test.py index 4d869ab..379b015 100644 --- a/tests/shader_poc_test.py +++ b/tests/shader_poc_test.py @@ -120,7 +120,7 @@ ui.append(cached_shader) # Keyboard handler 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") sys.exit(0) diff --git a/tests/shader_toggle_test.py b/tests/shader_toggle_test.py index e87a51f..3f11a36 100644 --- a/tests/shader_toggle_test.py +++ b/tests/shader_toggle_test.py @@ -55,15 +55,15 @@ test_count = 0 def on_key(key, state): global test_count - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - if key == "Num1" or key == "1": + if key == mcrfpy.Key.NUM_1: test_frame.shader_enabled = True status.text = "Status: Shader ENABLED" update_display() test_count += 1 - elif key == "Num2" or key == "2": + elif key == mcrfpy.Key.NUM_2: test_frame.shader_enabled = False status.text = "Status: Shader DISABLED" update_display() @@ -76,7 +76,7 @@ def on_key(key, state): else: status.text = "Status: Shader DISABLED - Position OK!" 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: print(f"PASS: Position remained correct after {test_count} toggles") else: diff --git a/tests/unit/test_animation_chaining.py b/tests/unit/test_animation_chaining.py index 20bbc6a..7d12a8c 100644 --- a/tests/unit/test_animation_chaining.py +++ b/tests/unit/test_animation_chaining.py @@ -100,7 +100,7 @@ enemy.sprite_index = 69 # E # UI setup ui = chain_test.children ui.append(grid) -grid.position = (100, 100) +grid.pos = (100, 100) grid.size = (600, 450) title = mcrfpy.Caption(pos=(300, 20), text="Animation Chaining Test") @@ -179,23 +179,21 @@ def update_camera(timer, runtime): def handle_input(key, state): global camera_follow - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - - key = key.lower() - - if key == "q": + + if key == mcrfpy.Key.Q: sys.exit(0) - elif key == "num1": + elif key == mcrfpy.Key.NUM_1: animate_player() - elif key == "num2": + elif key == mcrfpy.Key.NUM_2: animate_enemy() - elif key == "num3": + elif key == mcrfpy.Key.NUM_3: animate_both() - elif key == "c": + elif key == mcrfpy.Key.C: camera_follow = not camera_follow info.text = f"Camera follow: {'ON' if camera_follow else 'OFF'}" - elif key == "r": + elif key == mcrfpy.Key.R: # Reset positions player.x, player.y = 2, 2 enemy.x, enemy.y = 17, 12 diff --git a/tests/unit/test_callback_vector.py b/tests/unit/test_callback_vector.py index e895e0c..9b50293 100644 --- a/tests/unit/test_callback_vector.py +++ b/tests/unit/test_callback_vector.py @@ -17,12 +17,12 @@ def test_click_callback_signature(pos, button, action): results.append(("on_click pos is Vector", False)) print(f"FAIL: on_click receives {type(pos).__name__} instead of Vector: {pos}") - # Verify button and action types - if isinstance(button, str) and isinstance(action, str): - results.append(("on_click button/action are strings", True)) + # Verify button and action types (enums since #306) + if isinstance(button, mcrfpy.MouseButton) and isinstance(action, mcrfpy.InputState): + results.append(("on_click button/action are enums", True)) print(f"PASS: button={button!r}, action={action!r}") 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__}") # #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 # 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) test_on_enter_callback_signature(mcrfpy.Vector(100, 100)) test_on_exit_callback_signature(mcrfpy.Vector(300, 300)) diff --git a/tests/unit/test_entity_animation.py b/tests/unit/test_entity_animation.py index f44d98b..dfd28d0 100644 --- a/tests/unit/test_entity_animation.py +++ b/tests/unit/test_entity_animation.py @@ -44,7 +44,7 @@ entity.sprite_index = 64 # @ # UI setup ui = test_anim.children ui.append(grid) -grid.position = (100, 100) +grid.pos = (100, 100) grid.size = (450, 450) # 15 * 30 pixels per cell # Title @@ -160,23 +160,21 @@ def test_immediate_position(): # Input handler def handle_input(key, state): - if state != "start": + if state != mcrfpy.InputState.PRESSED: return - - key = key.lower() - - if key == "q": + + if key == mcrfpy.Key.Q: print("Exiting test...") sys.exit(0) - elif key == "space": + elif key == mcrfpy.Key.SPACE: if not animating: start_animation() else: print("Animation already in progress!") - elif key == "t": + elif key == mcrfpy.Key.T: # Test immediate position change test_immediate_position() - elif key == "r": + elif key == mcrfpy.Key.R: # Reset position entity.x = 5 entity.y = 5 diff --git a/tests/unit/test_input_enums.py b/tests/unit/test_input_enums.py index d1e8334..19fb8a8 100644 --- a/tests/unit/test_input_enums.py +++ b/tests/unit/test_input_enums.py @@ -1,8 +1,9 @@ #!/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. +Tests the input-related enums that provide type-safe key codes, +mouse buttons, and event states. Legacy string comparison was +removed in #306 -- these enums now use standard IntEnum comparison. """ import mcrfpy @@ -10,7 +11,7 @@ import sys def test_key_enum(): - """Test Key enum members and backwards compatibility.""" + """Test Key enum members and int values.""" print("Testing Key enum...") # 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.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 enum self-comparison + assert mcrfpy.Key.ESCAPE == mcrfpy.Key.ESCAPE, "Key.ESCAPE should equal itself" + assert mcrfpy.Key.A != mcrfpy.Key.ESCAPE, "Key.A should not equal Key.ESCAPE" - # Test that enum name also matches - assert mcrfpy.Key.ESCAPE == "ESCAPE", "Key.ESCAPE should also equal 'ESCAPE'" + # Verify legacy string comparison is removed (#306) + 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") def test_mouse_button_enum(): - """Test MouseButton enum members and backwards compatibility.""" + """Test MouseButton enum members.""" print("Testing MouseButton enum...") # 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.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 enum self-comparison + assert mcrfpy.MouseButton.LEFT == mcrfpy.MouseButton.LEFT + assert mcrfpy.MouseButton.LEFT != mcrfpy.MouseButton.RIGHT - # Test that enum name also matches - assert mcrfpy.MouseButton.LEFT == "LEFT", "MouseButton.LEFT should also equal 'LEFT'" + # Verify legacy string comparison is removed (#306) + 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") def test_input_state_enum(): - """Test InputState enum members and backwards compatibility.""" + """Test InputState enum members.""" print("Testing InputState enum...") # 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.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 enum self-comparison + assert mcrfpy.InputState.PRESSED == mcrfpy.InputState.PRESSED + assert mcrfpy.InputState.PRESSED != mcrfpy.InputState.RELEASED - # 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'" + # Verify legacy string comparison is removed (#306) + assert not (mcrfpy.InputState.PRESSED == "start"), "Legacy string comparison should be removed" + assert not (mcrfpy.InputState.RELEASED == "end"), "Legacy string comparison should be removed" print(" All InputState tests passed") diff --git a/tests/unit/test_scene_transitions.py b/tests/unit/test_scene_transitions.py index 220a922..b650c26 100644 --- a/tests/unit/test_scene_transitions.py +++ b/tests/unit/test_scene_transitions.py @@ -70,81 +70,81 @@ def handle_key(key, action): """Handle keyboard input for scene transitions.""" global current_transition, transition_duration - if action != "start": + if action != mcrfpy.InputState.PRESSED: return current_scene = (mcrfpy.current_scene.name if mcrfpy.current_scene else None) # Number keys set transition type keyselections = { - "Num1": mcrfpy.Transition.FADE, - "Num2": mcrfpy.Transition.SLIDE_LEFT, - "Num3": mcrfpy.Transition.SLIDE_RIGHT, - "Num4": mcrfpy.Transition.SLIDE_UP, - "Num5": mcrfpy.Transition.SLIDE_DOWN, - "Num6": mcrfpy.Transition.NONE + mcrfpy.Key.NUM_1: mcrfpy.Transition.FADE, + mcrfpy.Key.NUM_2: mcrfpy.Transition.SLIDE_LEFT, + mcrfpy.Key.NUM_3: mcrfpy.Transition.SLIDE_RIGHT, + mcrfpy.Key.NUM_4: mcrfpy.Transition.SLIDE_UP, + mcrfpy.Key.NUM_5: mcrfpy.Transition.SLIDE_DOWN, + mcrfpy.Key.NUM_6: mcrfpy.Transition.NONE } if key in keyselections: current_transition = keyselections[key] print(f"Transition set to: {current_transition}") - #if key == "Num1": + #if key == mcrfpy.Key.NUM_1: # current_transition = "fade" # print("Transition set to: fade") - #elif key == "Num2": + #elif key == mcrfpy.Key.NUM_2: # current_transition = "slide_left" # print("Transition set to: slide_left") - #elif key == "Num3": + #elif key == mcrfpy.Key.NUM_3: # current_transition = "slide_right" # print("Transition set to: slide_right") - #elif key == "Num4": + #elif key == mcrfpy.Key.NUM_4: # current_transition = "slide_up" # print("Transition set to: slide_up") - #elif key == "Num5": + #elif key == mcrfpy.Key.NUM_5: # current_transition = "slide_down" # print("Transition set to: slide_down") - #elif key == "Num6": + #elif key == mcrfpy.Key.NUM_6: # current_transition = None # Instant # print("Transition set to: instant") # Letter keys change scene keytransitions = { - "R": red_scene, - "B": blue_scene, - "G": green_scene, - "M": menu_scene + mcrfpy.Key.R: red_scene, + mcrfpy.Key.B: blue_scene, + mcrfpy.Key.G: green_scene, + mcrfpy.Key.M: menu_scene } if key in keytransitions: if mcrfpy.current_scene != keytransitions[key]: keytransitions[key].activate(current_transition, transition_duration) - #elif key == "R": + #elif key == mcrfpy.Key.R: # if current_scene != "red_scene": # print(f"Transitioning to red_scene with {current_transition}") # if current_transition: # mcrfpy.setScene("red_scene", current_transition, transition_duration) # else: # red_scene.activate() - #elif key == "B": + #elif key == mcrfpy.Key.B: # if current_scene != "blue_scene": # print(f"Transitioning to blue_scene with {current_transition}") # if current_transition: # mcrfpy.setScene("blue_scene", current_transition, transition_duration) # else: # blue_scene.activate() - #elif key == "G": + #elif key == mcrfpy.Key.G: # if current_scene != "green_scene": # print(f"Transitioning to green_scene with {current_transition}") # if current_transition: # mcrfpy.setScene("green_scene", current_transition, transition_duration) # else: # green_scene.activate() - #elif key == "M": + #elif key == mcrfpy.Key.M: # if current_scene != "menu_scene": # print(f"Transitioning to menu_scene with {current_transition}") # if current_transition: # mcrfpy.setScene("menu_scene", current_transition, transition_duration) # else: # menu_scene.activate() - elif key == "Escape": + elif key == mcrfpy.Key.ESCAPE: print("Exiting...") sys.exit(0) diff --git a/tests/unit/test_text_input.py b/tests/unit/test_text_input.py index d4402c9..68009b0 100644 --- a/tests/unit/test_text_input.py +++ b/tests/unit/test_text_input.py @@ -78,9 +78,9 @@ def create_demo(): # Keyboard handler def handle_keys(scene_name, key): if not focus_mgr.handle_key(key): - if key == "Tab": + if key == mcrfpy.Key.TAB: focus_mgr.focus_next() - elif key == "Escape": + elif key == mcrfpy.Key.ESCAPE: print("\nFinal values:") for i, inp in enumerate(inputs): print(f" Field {i+1}: '{inp.get_text()}'") diff --git a/tests/wiki_snippets_verify.py b/tests/wiki_snippets_verify.py index f31053a..5e19929 100644 --- a/tests/wiki_snippets_verify.py +++ b/tests/wiki_snippets_verify.py @@ -45,11 +45,13 @@ def test_key_enum_members(): check("IE: Key enum members", test_key_enum_members) -def test_inputstate_legacy(): - assert mcrfpy.InputState.PRESSED == "start" - assert mcrfpy.InputState.RELEASED == "end" +def test_inputstate_values(): + assert int(mcrfpy.InputState.PRESSED) == 0 + 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(): frame = mcrfpy.Frame(pos=(100, 100), size=(200, 50))