Compare commits
4 commits
ad5c999998
...
6d5e99a114
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d5e99a114 | |||
| 354faca838 | |||
| c15d836e79 | |||
| 4a3854dac1 |
75 changed files with 508 additions and 612 deletions
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
## Executive Summary
|
||||
|
||||
The McRogueFace Python API exposes **44 exported types**, **14 internal types**, **10 enums**, **13 module-level functions**, **7 module-level properties**, and **5 singleton instances** through the `mcrfpy` module.
|
||||
The McRogueFace Python API exposes **46 exported types**, **14 internal types**, **10 enums**, **13 module-level functions**, **7 module-level properties**, and **5 singleton instances** through the `mcrfpy` module.
|
||||
|
||||
Overall, the API is remarkably consistent. Properties and methods use snake_case throughout the type system. The major inconsistencies are concentrated in a few areas:
|
||||
|
||||
|
|
@ -930,7 +930,7 @@ Some types use raw string docstrings for methods instead of MCRF_METHOD macros.
|
|||
|
||||
| Category | Count |
|
||||
|----------|-------|
|
||||
| Exported types | 44 |
|
||||
| Exported types | 46 |
|
||||
| Internal types | 14 |
|
||||
| Enums | 10 |
|
||||
| Module functions | 13 |
|
||||
|
|
|
|||
|
|
@ -154,13 +154,13 @@ void Animation::startEntity(std::shared_ptr<UIEntity> target) {
|
|||
}
|
||||
else if constexpr (std::is_same_v<T, int>) {
|
||||
// For entities, we might need to handle sprite_index differently
|
||||
if (targetProperty == "sprite_index" || targetProperty == "sprite_number") {
|
||||
if (targetProperty == "sprite_index") {
|
||||
startValue = target->sprite.getSpriteIndex();
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::vector<int>>) {
|
||||
// For sprite animation frame lists, get current sprite index
|
||||
if (targetProperty == "sprite_index" || targetProperty == "sprite_number") {
|
||||
if (targetProperty == "sprite_index") {
|
||||
startValue = target->sprite.getSpriteIndex();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
std::string targetProperty; // Property name to animate (e.g., "x", "color.r", "sprite_number")
|
||||
std::string targetProperty; // Property name to animate (e.g., "x", "color.r", "sprite_index")
|
||||
AnimationValue startValue; // Starting value (captured when animation starts)
|
||||
AnimationValue targetValue; // Target value to animate to
|
||||
float duration; // Animation duration in seconds
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ namespace mcrfpydef {
|
|||
" property: Property name to animate. Valid properties depend on target type:\n"
|
||||
" - Position/Size: 'x', 'y', 'w', 'h', 'pos', 'size'\n"
|
||||
" - Appearance: 'fill_color', 'outline_color', 'outline', 'opacity'\n"
|
||||
" - Sprite: 'sprite_index', 'sprite_number', 'scale'\n"
|
||||
" - Sprite: 'sprite_index', 'scale'\n"
|
||||
" - Grid: 'center', 'zoom'\n"
|
||||
" - Caption: 'text'\n"
|
||||
" - Sub-properties: 'fill_color.r', 'fill_color.g', 'fill_color.b', 'fill_color.a'\n"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1578,7 +1578,6 @@ PyGetSetDef UIEntity::getsetters[] = {
|
|||
"Get: Returns the Grid or None. "
|
||||
"Set: Assign a Grid to move entity, or None to remove from grid.", NULL},
|
||||
{"sprite_index", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite index on the texture on the display", NULL},
|
||||
{"sprite_number", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite index (DEPRECATED: use sprite_index instead)", NULL},
|
||||
{"visible", (getter)UIEntity_get_visible, (setter)UIEntity_set_visible, "Visibility flag", NULL},
|
||||
{"opacity", (getter)UIEntity_get_opacity, (setter)UIEntity_set_opacity, "Opacity (0.0 = transparent, 1.0 = opaque)", NULL},
|
||||
{"name", (getter)UIEntity_get_name, (setter)UIEntity_set_name, "Name for finding elements", NULL},
|
||||
|
|
@ -1675,7 +1674,7 @@ bool UIEntity::setProperty(const std::string& name, float value) {
|
|||
}
|
||||
|
||||
bool UIEntity::setProperty(const std::string& name, int value) {
|
||||
if (name == "sprite_index" || name == "sprite_number") {
|
||||
if (name == "sprite_index") {
|
||||
sprite.setSpriteIndex(value);
|
||||
if (grid) grid->markDirty(); // #144 - Content change
|
||||
return true;
|
||||
|
|
@ -1718,7 +1717,7 @@ bool UIEntity::hasProperty(const std::string& name) const {
|
|||
return true;
|
||||
}
|
||||
// Int properties
|
||||
if (name == "sprite_index" || name == "sprite_number") {
|
||||
if (name == "sprite_index") {
|
||||
return true;
|
||||
}
|
||||
// #106: Shader uniform properties - delegate to sprite
|
||||
|
|
|
|||
|
|
@ -1041,23 +1041,6 @@ PyObject* UIGrid::get_grid_h(PyUIGridObject* self, void* closure) {
|
|||
return PyLong_FromLong(self->data->grid_h);
|
||||
}
|
||||
|
||||
PyObject* UIGrid::get_position(PyUIGridObject* self, void* closure) {
|
||||
// #179 - Return position as Vector (consistent with get_size, get_grid_size)
|
||||
return PyVector(self->data->position).pyObject();
|
||||
}
|
||||
|
||||
int UIGrid::set_position(PyUIGridObject* self, PyObject* value, void* closure) {
|
||||
float x, y;
|
||||
if (!PyArg_ParseTuple(value, "ff", &x, &y)) {
|
||||
PyErr_SetString(PyExc_ValueError, "Position must be a tuple of two floats");
|
||||
return -1;
|
||||
}
|
||||
self->data->position = sf::Vector2f(x, y); // Update base class position
|
||||
self->data->box.setPosition(self->data->position); // Sync box position
|
||||
self->data->output.setPosition(self->data->position); // Sync output sprite position
|
||||
return 0;
|
||||
}
|
||||
|
||||
// #181 - Return size as Vector
|
||||
PyObject* UIGrid::get_size(PyUIGridObject* self, void* closure) {
|
||||
auto& box = self->data->box;
|
||||
|
|
@ -2591,7 +2574,6 @@ PyGetSetDef UIGrid::getsetters[] = {
|
|||
{"grid_size", (getter)UIGrid::get_grid_size, NULL, "Grid dimensions (grid_w, grid_h)", NULL},
|
||||
{"grid_w", (getter)UIGrid::get_grid_w, NULL, "Grid width in cells", NULL},
|
||||
{"grid_h", (getter)UIGrid::get_grid_h, NULL, "Grid height in cells", NULL},
|
||||
{"position", (getter)UIGrid::get_position, (setter)UIGrid::set_position, "Position of the grid (x, y)", NULL},
|
||||
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos, "Position of the grid as Vector", (void*)PyObjectsEnum::UIGRID},
|
||||
{"grid_pos", (getter)UIDrawable::get_grid_pos, (setter)UIDrawable::set_grid_pos, "Position in parent grid's tile coordinates (only when parent is Grid)", (void*)PyObjectsEnum::UIGRID},
|
||||
{"size", (getter)UIGrid::get_size, (setter)UIGrid::set_size, "Size of the grid as Vector (width, height)", NULL},
|
||||
|
|
@ -3207,14 +3189,7 @@ bool UIGrid::setProperty(const std::string& name, float value) {
|
|||
}
|
||||
|
||||
bool UIGrid::setProperty(const std::string& name, const sf::Vector2f& value) {
|
||||
if (name == "position") {
|
||||
position = value;
|
||||
box.setPosition(position);
|
||||
output.setPosition(position);
|
||||
markCompositeDirty(); // #144 - Position change, texture still valid
|
||||
return true;
|
||||
}
|
||||
else if (name == "size") {
|
||||
if (name == "size") {
|
||||
box.setSize(value);
|
||||
output.setTextureRect(sf::IntRect(0, 0, box.getSize().x, box.getSize().y));
|
||||
markDirty(); // #144 - Size change
|
||||
|
|
@ -3307,11 +3282,7 @@ bool UIGrid::getProperty(const std::string& name, float& value) const {
|
|||
}
|
||||
|
||||
bool UIGrid::getProperty(const std::string& name, sf::Vector2f& value) const {
|
||||
if (name == "position") {
|
||||
value = position;
|
||||
return true;
|
||||
}
|
||||
else if (name == "size") {
|
||||
if (name == "size") {
|
||||
value = box.getSize();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -3338,7 +3309,7 @@ bool UIGrid::hasProperty(const std::string& name) const {
|
|||
return true;
|
||||
}
|
||||
// Vector2f properties
|
||||
if (name == "position" || name == "size" || name == "center" || name == "origin") {
|
||||
if (name == "size" || name == "center" || name == "origin") {
|
||||
return true;
|
||||
}
|
||||
// #106: Shader uniform properties
|
||||
|
|
|
|||
|
|
@ -107,8 +107,6 @@ public:
|
|||
static PyObject* get_grid_size(PyUIGridObject* self, void* closure);
|
||||
static PyObject* get_grid_w(PyUIGridObject* self, void* closure);
|
||||
static PyObject* get_grid_h(PyUIGridObject* self, void* closure);
|
||||
static PyObject* get_position(PyUIGridObject* self, void* closure);
|
||||
static int set_position(PyUIGridObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_size(PyUIGridObject* self, void* closure);
|
||||
static int set_size(PyUIGridObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_center(PyUIGridObject* self, void* closure);
|
||||
|
|
|
|||
|
|
@ -406,7 +406,6 @@ PyGetSetDef UISprite::getsetters[] = {
|
|||
{"scale_x", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Horizontal scale factor", (void*)3},
|
||||
{"scale_y", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Vertical scale factor", (void*)4},
|
||||
{"sprite_index", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Which sprite on the texture is shown", NULL},
|
||||
{"sprite_number", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Sprite index (DEPRECATED: use sprite_index instead)", NULL},
|
||||
{"texture", (getter)UISprite::get_texture, (setter)UISprite::set_texture, "Texture object", NULL},
|
||||
{"on_click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
||||
MCRF_PROPERTY(on_click,
|
||||
|
|
@ -683,7 +682,7 @@ bool UISprite::setProperty(const std::string& name, float value) {
|
|||
}
|
||||
|
||||
bool UISprite::setProperty(const std::string& name, int value) {
|
||||
if (name == "sprite_index" || name == "sprite_number") {
|
||||
if (name == "sprite_index") {
|
||||
setSpriteIndex(value);
|
||||
markDirty(); // #144 - Content change
|
||||
return true;
|
||||
|
|
@ -745,7 +744,7 @@ bool UISprite::getProperty(const std::string& name, float& value) const {
|
|||
}
|
||||
|
||||
bool UISprite::getProperty(const std::string& name, int& value) const {
|
||||
if (name == "sprite_index" || name == "sprite_number") {
|
||||
if (name == "sprite_index") {
|
||||
value = sprite_index;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -765,7 +764,7 @@ bool UISprite::hasProperty(const std::string& name) const {
|
|||
return true;
|
||||
}
|
||||
// Int properties
|
||||
if (name == "sprite_index" || name == "sprite_number") {
|
||||
if (name == "sprite_index") {
|
||||
return true;
|
||||
}
|
||||
// Vector2f properties
|
||||
|
|
|
|||
|
|
@ -417,7 +417,6 @@ class Sprite(Drawable):
|
|||
|
||||
texture: Texture
|
||||
sprite_index: int
|
||||
sprite_number: int # Deprecated alias for sprite_index
|
||||
scale: float
|
||||
w: float # Read-only, computed from texture
|
||||
h: float # Read-only, computed from texture
|
||||
|
|
@ -865,7 +864,6 @@ class Entity(Drawable):
|
|||
grid_y: float
|
||||
texture: Texture
|
||||
sprite_index: int
|
||||
sprite_number: int # Deprecated alias for sprite_index
|
||||
grid: Optional[Grid]
|
||||
|
||||
def at(self, grid_x: float, grid_y: float) -> None:
|
||||
|
|
|
|||
|
|
@ -594,7 +594,6 @@ class Sprite(Drawable):
|
|||
|
||||
texture: Texture
|
||||
sprite_index: int
|
||||
sprite_number: int # Deprecated alias for sprite_index
|
||||
scale: float
|
||||
w: float # Read-only, computed from texture
|
||||
h: float # Read-only, computed from texture
|
||||
|
|
@ -1042,7 +1041,6 @@ class Entity(Drawable):
|
|||
grid_y: float
|
||||
texture: Texture
|
||||
sprite_index: int
|
||||
sprite_number: int # Deprecated alias for sprite_index
|
||||
grid: Optional[Grid]
|
||||
|
||||
def at(self, grid_x: float, grid_y: float) -> None:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)])
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ ui = dijkstra_debug.children
|
|||
ui.append(grid)
|
||||
|
||||
# Position and scale
|
||||
grid.position = (50, 50)
|
||||
grid.pos = (50, 50)
|
||||
grid.size = (400, 400) # 10*40
|
||||
|
||||
# Add title
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ ui.append(grid)
|
|||
|
||||
# Scale and position grid for better visibility
|
||||
grid.size = (560, 400) # 14*40, 10*40
|
||||
grid.position = (120, 60)
|
||||
grid.pos = (120, 60)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption(pos=(250, 10), text="Dijkstra Pathfinding Interactive")
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ ui.append(grid)
|
|||
|
||||
# Scale and position grid for better visibility
|
||||
grid.size = (560, 400) # 14*40, 10*40
|
||||
grid.position = (120, 60)
|
||||
grid.pos = (120, 60)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption(pos=(250, 10), text="Enhanced Dijkstra Pathfinding")
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ ui = dijkstra_test.children
|
|||
ui.append(grid)
|
||||
|
||||
# Position and scale grid
|
||||
grid.position = (50, 50)
|
||||
grid.pos = (50, 50)
|
||||
grid.size = (500, 300)
|
||||
|
||||
# Add title
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ entity.update_visibility()
|
|||
print("Setting up UI...")
|
||||
ui = vis_test.children
|
||||
ui.append(grid)
|
||||
grid.position = (50, 50)
|
||||
grid.pos = (50, 50)
|
||||
grid.size = (300, 300)
|
||||
|
||||
# Test perspective
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -39,14 +39,22 @@ def test_grid_vectors():
|
|||
assert center.y == 75.0, f"grid.center.y should be 75.0, got {center.y}"
|
||||
print(" PASS: grid.center returns Vector")
|
||||
|
||||
# Test grid.position returns a Vector
|
||||
position = grid.position
|
||||
print(f" grid.position = {position}")
|
||||
assert hasattr(position, 'x'), f"grid.position should have .x attribute, got {type(position)}"
|
||||
assert hasattr(position, 'y'), f"grid.position should have .y attribute, got {type(position)}"
|
||||
assert position.x == 100.0, f"grid.position.x should be 100.0, got {position.x}"
|
||||
assert position.y == 150.0, f"grid.position.y should be 150.0, got {position.y}"
|
||||
print(" PASS: grid.position returns Vector")
|
||||
# Test grid.pos returns a Vector
|
||||
pos = grid.pos
|
||||
print(f" grid.pos = {pos}")
|
||||
assert hasattr(pos, 'x'), f"grid.pos should have .x attribute, got {type(pos)}"
|
||||
assert hasattr(pos, 'y'), f"grid.pos should have .y attribute, got {type(pos)}"
|
||||
assert pos.x == 100.0, f"grid.pos.x should be 100.0, got {pos.x}"
|
||||
assert pos.y == 150.0, f"grid.pos.y should be 150.0, got {pos.y}"
|
||||
print(" PASS: grid.pos returns Vector")
|
||||
|
||||
# Verify grid.position alias was removed (#308)
|
||||
try:
|
||||
_ = grid.position
|
||||
print(" FAIL: grid.position should not exist but it does!")
|
||||
sys.exit(1)
|
||||
except AttributeError:
|
||||
print(" PASS: grid.position correctly removed (#308)")
|
||||
|
||||
print("Issue #179 tests PASSED!")
|
||||
|
||||
|
|
|
|||
86
tests/regression/issue_307_color_eq_test.py
Normal file
86
tests/regression/issue_307_color_eq_test.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
"""Regression test for issue #307: Color.__eq__/__ne__ for value comparison.
|
||||
|
||||
Color had __hash__ but no __eq__/__ne__, meaning two Colors with identical RGBA
|
||||
values compared by identity (id()) rather than by value. This violated the Python
|
||||
convention that objects with __hash__ should also implement __eq__.
|
||||
"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
errors = []
|
||||
|
||||
def check(condition, msg):
|
||||
if not condition:
|
||||
errors.append(msg)
|
||||
|
||||
# Basic equality
|
||||
c1 = mcrfpy.Color(255, 0, 0)
|
||||
c2 = mcrfpy.Color(255, 0, 0)
|
||||
c3 = mcrfpy.Color(0, 255, 0)
|
||||
|
||||
check(c1 == c2, "Same RGBA should be equal")
|
||||
check(not (c1 != c2), "Same RGBA should not be not-equal")
|
||||
check(c1 != c3, "Different RGBA should be not-equal")
|
||||
check(not (c1 == c3), "Different RGBA should not be equal")
|
||||
|
||||
# Alpha matters
|
||||
c4 = mcrfpy.Color(255, 0, 0, 128)
|
||||
check(c1 != c4, "Different alpha should be not-equal")
|
||||
|
||||
# Self-equality
|
||||
check(c1 == c1, "Color should equal itself")
|
||||
|
||||
# Equality with tuples
|
||||
check(c1 == (255, 0, 0), "Color should equal 3-tuple")
|
||||
check(c1 == (255, 0, 0, 255), "Color should equal 4-tuple with matching alpha")
|
||||
check(c1 != (255, 0, 0, 128), "Color should not equal tuple with different alpha")
|
||||
check(c1 != (0, 0, 0), "Color should not equal different tuple")
|
||||
|
||||
# Equality with lists
|
||||
check(c1 == [255, 0, 0], "Color should equal list")
|
||||
check(c1 == [255, 0, 0, 255], "Color should equal list with alpha")
|
||||
|
||||
# Reflected comparisons (tuple on left)
|
||||
check((255, 0, 0) == c1, "Reflected == should work")
|
||||
check(not ((255, 0, 0) != c1), "Reflected != should work for equal")
|
||||
check((0, 255, 0) != c1, "Reflected != should work for unequal")
|
||||
|
||||
# Non-comparable types return NotImplemented (Python converts to False/True)
|
||||
check(not (c1 == "red"), "Color vs string should not be equal")
|
||||
check(c1 != "red", "Color vs string should be not-equal")
|
||||
check(not (c1 == 42), "Color vs int should not be equal")
|
||||
check(not (c1 == None), "Color vs None should not be equal")
|
||||
|
||||
# Wrong-size tuples return NotImplemented
|
||||
check(not (c1 == (255,)), "Color vs 1-tuple should not be equal")
|
||||
check(not (c1 == (255, 0)), "Color vs 2-tuple should not be equal")
|
||||
check(not (c1 == (255, 0, 0, 255, 0)), "Color vs 5-tuple should not be equal")
|
||||
|
||||
# Ordering operators should raise TypeError
|
||||
try:
|
||||
c1 < c2
|
||||
errors.append("< should raise TypeError")
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
c1 > c2
|
||||
errors.append("> should raise TypeError")
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
# Hash consistency: equal objects must have equal hashes
|
||||
check(hash(c1) == hash(c2), "Equal colors must have equal hashes")
|
||||
|
||||
# Explicit black
|
||||
c_black = mcrfpy.Color(0, 0, 0)
|
||||
check(c_black == (0, 0, 0, 255), "Color(0,0,0) should equal (0,0,0,255)")
|
||||
|
||||
if errors:
|
||||
for e in errors:
|
||||
print(f"FAIL: {e}")
|
||||
print(f"\n{len(errors)} error(s)")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("PASS")
|
||||
sys.exit(0)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ def visual_test(timer, runtime):
|
|||
# Set up minimal UI for visual test
|
||||
ui = astar_test.children
|
||||
ui.append(grid)
|
||||
grid.position = (50, 50)
|
||||
grid.pos = (50, 50)
|
||||
grid.size = (400, 400)
|
||||
|
||||
astar_test.activate()
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ def check_visual(timer, runtime):
|
|||
# Set up minimal UI to test rendering
|
||||
ui = test.children
|
||||
ui.append(grid)
|
||||
grid.position = (50, 50)
|
||||
grid.pos = (50, 50)
|
||||
grid.size = (250, 250)
|
||||
|
||||
test.activate()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()}'")
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ print(f" Visible cells after move: {visible_count}")
|
|||
# Set up UI
|
||||
ui = visibility_test.children
|
||||
ui.append(grid)
|
||||
grid.position = (50, 50)
|
||||
grid.pos = (50, 50)
|
||||
grid.size = (600, 450) # 20*30, 15*30
|
||||
|
||||
# Add title
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ if path:
|
|||
# Set up UI
|
||||
ui = visual_test.children
|
||||
ui.append(grid)
|
||||
grid.position = (50, 50)
|
||||
grid.pos = (50, 50)
|
||||
grid.size = (250, 250)
|
||||
|
||||
# Add title
|
||||
|
|
|
|||
|
|
@ -35,11 +35,11 @@ def test_Entity():
|
|||
try:
|
||||
print(f" Entity1 pos: {entity1.pos}")
|
||||
print(f" Entity1 draw_pos: {entity1.draw_pos}")
|
||||
print(f" Entity1 sprite_number: {entity1.sprite_number}")
|
||||
|
||||
print(f" Entity1 sprite_index: {entity1.sprite_index}")
|
||||
|
||||
# Modify properties
|
||||
entity1.pos = mcrfpy.Vector(3, 3)
|
||||
entity1.sprite_number = 5
|
||||
entity1.sprite_index = 5
|
||||
print(" Entity properties modified")
|
||||
except Exception as e:
|
||||
print(f"X Entity property access failed: {e}")
|
||||
|
|
|
|||
|
|
@ -38,21 +38,21 @@ except AttributeError:
|
|||
except Exception as e:
|
||||
print(f"✗ Unexpected error setting texture: {e}")
|
||||
|
||||
# Test sprite_number property
|
||||
# Test sprite_index property
|
||||
try:
|
||||
print(f"Sprite2 sprite_number: {sprite2.sprite_number}")
|
||||
sprite2.sprite_number = 10
|
||||
print(f"✓ Changed sprite_number to: {sprite2.sprite_number}")
|
||||
print(f"Sprite2 sprite_index: {sprite2.sprite_index}")
|
||||
sprite2.sprite_index = 10
|
||||
print(f"sprite_index set to: {sprite2.sprite_index}")
|
||||
except Exception as e:
|
||||
print(f"✗ sprite_number property failed: {e}")
|
||||
print(f"sprite_index property failed: {e}")
|
||||
|
||||
# Test sprite index validation (Issue #33)
|
||||
try:
|
||||
# Try to set invalid sprite index
|
||||
sprite2.sprite_number = 9999
|
||||
print("✗ Should validate sprite index against texture range (Issue #33)")
|
||||
sprite2.sprite_index = 9999
|
||||
print("Should validate sprite index against texture range (Issue #33)")
|
||||
except Exception as e:
|
||||
print(f"✓ Sprite index validation works: {e}")
|
||||
print(f"Sprite index validation works: {e}")
|
||||
|
||||
# Create grid of sprites to show different indices
|
||||
y_offset = 100
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue