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

View file

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

View file

@ -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<int>(key)) {
return key_table[i].legacy;
}
}
return "Unknown";
}
sf::Keyboard::Key PyKey::from_legacy_string(const char* name) {
for (int i = 0; i < NUM_KEY_ENTRIES; i++) {
if (strcmp(key_table[i].legacy, name) == 0 ||
@ -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<sf::Keyboard::Key>(key_table[i].value);
return 1;
}
}
PyErr_Format(PyExc_ValueError,
"Unknown Key: '%s'. Use Key enum members (e.g., Key.ESCAPE, Key.A) "
"or legacy strings (e.g., 'Escape', 'A').", name);
"Unknown Key: '%s'. Use Key enum members (e.g., Key.ESCAPE, Key.A).", name);
return 0;
}

View file

@ -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);

View file

@ -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<int>(button)) {
return mouse_button_table[i].legacy;
}
}
return "left"; // Default fallback
}
PyObject* PyMouseButton::create_enum_class(PyObject* module) {
// Build the enum definition dynamically from the table
std::ostringstream code;
@ -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<sf::Mouse::Button>(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;
}

View file

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

View file

@ -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)

View file

@ -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()

View file

@ -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

View file

@ -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"

View file

@ -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]

View file

@ -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):

View file

@ -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:

View file

@ -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}"

View file

@ -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:

View file

@ -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])

View file

@ -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

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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]

View file

@ -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()

View file

@ -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

View file

@ -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():

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)])

View file

@ -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")

View file

@ -152,19 +152,22 @@ 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
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)
@ -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)")

View file

@ -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")

View file

@ -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,40 +138,39 @@ 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()
@ -180,7 +179,7 @@ def handle_keys(key, state):
print("Reset positions")
# Q to quit
elif key == "q":
elif key == mcrfpy.Key.Q:
print("Exiting...")
sys.exit(0)

View file

@ -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])
# 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()
except (ValueError, IndexError):
pass
def _return_to_menu(self):
"""Return to demo menu."""

View file

@ -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])
_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)
except (ValueError, IndexError):
pass
elif key == mcrfpy.Key.ESCAPE:
sys.exit(0)

View file

@ -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)

View file

@ -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)

View file

@ -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:

View file

@ -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

View file

@ -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))

View file

@ -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

View file

@ -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")

View file

@ -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)

View file

@ -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()}'")

View file

@ -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))