From 222ee83009c73e1d882579c6efdb1cdc05fcfa11 Mon Sep 17 00:00:00 2001 From: John McCardle Date: Sat, 7 Feb 2026 22:15:28 +0000 Subject: [PATCH] Update Input and Events wiki with current API: Key/InputState/MouseButton enums, scene.on_key, Vector-based callbacks --- Input-and-Events.md | 426 +++++++++++++++++++++++++++----------------- 1 file changed, 264 insertions(+), 162 deletions(-) diff --git a/Input-and-Events.md b/Input-and-Events.md index a8ee742..d4df406 100644 --- a/Input-and-Events.md +++ b/Input-and-Events.md @@ -1,162 +1,264 @@ -# Input and Events - -McRogueFace provides keyboard, mouse, and window event handling through Python callbacks. Events are dispatched through the scene system, allowing different scenes to have different input handlers. - -**Related Pages:** -- [[UI-Widget-Patterns]] - Modal dialogs, buttons, hotbars -- [[Grid-Interaction-Patterns]] - Entity selection, movement, context menus -- [[Writing-Tests]] - Testing input with automation API - -**Key Files:** -- `src/GameEngine.cpp::processEvent()` - Event dispatch -- `src/Scene.cpp::sUserInput()` - Scene input handling -- `src/McRFPy_API.cpp` - Python callback registration - ---- - -## Keyboard Input - -Register a scene-level callback that fires on every key press/release: - -```python -import mcrfpy - -def handle_key(key: str, pressed: bool): - """ - key: Key name (e.g., "W", "Space", "Escape", "Up") - pressed: True on press, False on release - """ - if key == "Escape" and pressed: - mcrfpy.setScene("menu") - -mcrfpy.keypressScene(handle_key) -``` - -### Key Names - -Common key names from SFML: -- Letters: `"A"` through `"Z"` -- Numbers: `"Num0"` through `"Num9"` -- Arrows: `"Up"`, `"Down"`, `"Left"`, `"Right"` -- Special: `"Space"`, `"Enter"`, `"Escape"`, `"Tab"`, `"LShift"`, `"RShift"`, `"LControl"`, `"RControl"` -- Function: `"F1"` through `"F12"` - ---- - -## Mouse Input - -### Element Event Handlers - -All UIDrawables support mouse event callbacks: - -| Property | Signature | When Called | -|----------|-----------|-------------| -| `on_click` | `(x, y, button) -> None` | Mouse button pressed on element | -| `on_enter` | `() -> None` | Mouse enters element bounds | -| `on_exit` | `() -> None` | Mouse leaves element bounds | -| `on_move` | `(x, y) -> None` | Mouse moves within element | - -```python -frame = mcrfpy.Frame(pos=(100, 100), size=(200, 50)) - -frame.on_click = lambda x, y, btn: print(f"Clicked at ({x}, {y}) with button {btn}") -frame.on_enter = lambda: print("Mouse entered") -frame.on_exit = lambda: print("Mouse exited") -``` - -The `hovered` property (read-only) indicates whether the mouse is currently over an element. - -### Grid Cell Events - -Grids provide cell-level mouse events in addition to element events: - -| Property | Signature | Description | -|----------|-----------|-------------| -| `on_cell_click` | `(grid_x, grid_y, button) -> None` | Cell clicked | -| `on_cell_enter` | `(grid_x, grid_y) -> None` | Mouse enters cell | -| `on_cell_exit` | `(grid_x, grid_y) -> None` | Mouse leaves cell | -| `hovered_cell` | `(x, y)` or `None` | Currently hovered cell (read-only) | - -See [[Grid-Interaction-Patterns]] for usage examples. - -### Mouse Position - -```python -# Window coordinates (pixels) -x, y = mcrfpy.getMousePos() -``` - ---- - -## Event Priority - -### Click Dispatch Order - -Clicks are dispatched in reverse render order (front to back): - -1. **UI elements with highest z_index** receive clicks first -2. If handled (callback returns truthy), propagation stops -3. **Entities on grids** receive clicks next -4. **Grid cells** receive clicks last - -### Keyboard Priority - -Keyboard events go only to the current scene's registered callback. There is no concept of "focused" UI elements for keyboard input. - ---- - -## Window Events - -### Resize Events - -Window resize events are not currently exposed directly. Workaround using timer polling: - -```python -last_size = mcrfpy.getWindowSize() - -def check_resize(dt): - global last_size - current = mcrfpy.getWindowSize() - if current != last_size: - on_resize(current) - last_size = current - -mcrfpy.setTimer("resize_check", check_resize, 100) -``` - ---- - -## Testing Input - -Use the automation API to simulate input in tests: - -```python -from mcrfpy import automation - -automation.keypress("W", True) # Press W -automation.keypress("W", False) # Release W -automation.click(100, 200, button=0) # Left-click at position -``` - -See [[Writing-Tests]] for complete testing patterns. - ---- - -## API Reference - -**Module Functions:** -- `mcrfpy.keypressScene(callback)` - Register keyboard handler -- `mcrfpy.getMousePos() -> (int, int)` - Get mouse position -- `mcrfpy.getWindowSize() -> (int, int)` - Get window dimensions - -**UIDrawable Properties:** -- `on_click`, `on_enter`, `on_exit`, `on_move` - Event handlers -- `hovered` - Mouse hover state (read-only) - -**Grid Properties:** -- `on_cell_click`, `on_cell_enter`, `on_cell_exit` - Cell event handlers -- `hovered_cell` - Currently hovered cell (read-only) - ---- - -*Last updated: 2025-11-29* \ No newline at end of file +# Input and Events + +McRogueFace provides keyboard, mouse, and window event handling through Python callbacks. Events are dispatched through the scene system, allowing different scenes to have different input handlers. + +**Related Pages:** +- [[UI-Widget-Patterns]] - Modal dialogs, buttons, hotbars +- [[Grid-Interaction-Patterns]] - Entity selection, movement, context menus +- [[Writing-Tests]] - Testing input with automation API + +**Key Files:** +- `src/GameEngine.cpp::processEvent()` - Event dispatch +- `src/PyScene.cpp` - Scene input handling +- `src/PyCallable.cpp` - Callback wrappers (PyClickCallable, PyHoverCallable, PyCellCallable) + +--- + +## Keyboard Input + +Register a keyboard handler on a Scene object. The callback receives enum values, not strings: + +```python +import mcrfpy + +scene = mcrfpy.Scene("game") + +def handle_key(key, action): + """ + key: mcrfpy.Key enum (e.g., Key.W, Key.ESCAPE) + action: mcrfpy.InputState enum (PRESSED or RELEASED) + """ + if key == mcrfpy.Key.ESCAPE and action == mcrfpy.InputState.PRESSED: + menu_scene.activate() + elif key == mcrfpy.Key.W and action == mcrfpy.InputState.PRESSED: + player_move(0, -1) + +scene.on_key = handle_key +mcrfpy.current_scene = scene +``` + +### Key Enum (`mcrfpy.Key`) + +All keyboard keys are available as attributes of the `mcrfpy.Key` enum: + +- **Letters:** `Key.A` through `Key.Z` +- **Numbers:** `Key.Num0` through `Key.Num9` +- **Arrows:** `Key.UP`, `Key.DOWN`, `Key.LEFT`, `Key.RIGHT` +- **Special:** `Key.SPACE`, `Key.ENTER`, `Key.ESCAPE`, `Key.TAB`, `Key.BACKSPACE`, `Key.DELETE` +- **Modifiers:** `Key.LSHIFT`, `Key.RSHIFT`, `Key.LCTRL`, `Key.RCTRL`, `Key.LALT`, `Key.RALT` +- **Function:** `Key.F1` through `Key.F15` + +### InputState Enum (`mcrfpy.InputState`) + +| Value | Meaning | +|-------|---------| +| `InputState.PRESSED` | Key/button was just pressed down | +| `InputState.RELEASED` | Key/button was just released | + +**Legacy compatibility:** Enum values support string comparison for backwards compatibility: `InputState.PRESSED == "start"` returns `True`. However, using the enum directly is preferred. + +--- + +## Mouse Input + +### Element Click Handlers + +All UIDrawable elements (Frame, Caption, Sprite, Grid, Line, Circle, Arc) support mouse event callbacks: + +| Property | Signature | When Called | +|----------|-----------|-------------| +| `on_click` | `(pos: Vector, button: MouseButton, action: InputState)` | Mouse button pressed/released on element | +| `on_enter` | `(pos: Vector)` | Mouse enters element bounds | +| `on_exit` | `(pos: Vector)` | Mouse leaves element bounds | +| `on_move` | `(pos: Vector)` | Mouse moves within element | + +```python +frame = mcrfpy.Frame(pos=(100, 100), size=(200, 50)) + +def on_frame_click(pos, button, action): + if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED: + print(f"Left-clicked at ({pos.x}, {pos.y})") + elif button == mcrfpy.MouseButton.RIGHT and action == mcrfpy.InputState.PRESSED: + print("Right-clicked!") + +frame.on_click = on_frame_click +frame.on_enter = lambda pos: print(f"Mouse entered at ({pos.x}, {pos.y})") +frame.on_exit = lambda pos: print("Mouse exited") +frame.on_move = lambda pos: print(f"Mouse at ({pos.x}, {pos.y})") +``` + +The `hovered` property (read-only) indicates whether the mouse is currently over an element. + +### MouseButton Enum (`mcrfpy.MouseButton`) + +| Value | Meaning | +|-------|---------| +| `MouseButton.LEFT` | Left mouse button | +| `MouseButton.RIGHT` | Right mouse button | +| `MouseButton.MIDDLE` | Middle mouse button (scroll wheel click) | +| `MouseButton.SCROLL_UP` | Scroll wheel up | +| `MouseButton.SCROLL_DOWN` | Scroll wheel down | + +### Grid Cell Events + +Grids provide cell-level mouse events with grid coordinates: + +| Property | Signature | Description | +|----------|-----------|-------------| +| `on_cell_click` | `(cell_pos: Vector, button: MouseButton, action: InputState)` | Cell clicked | +| `on_cell_enter` | `(cell_pos: Vector)` | Mouse enters cell | +| `on_cell_exit` | `(cell_pos: Vector)` | Mouse leaves cell | +| `hovered_cell` | `(x, y)` tuple or `None` | Currently hovered cell (read-only) | + +```python +grid = mcrfpy.Grid(grid_size=(20, 15), pos=(50, 50), size=(400, 300)) + +def on_cell_click(cell_pos, button, action): + if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED: + x, y = int(cell_pos.x), int(cell_pos.y) + point = grid.at(x, y) + print(f"Cell ({x}, {y}): walkable={point.walkable}") + +grid.on_cell_click = on_cell_click +grid.on_cell_enter = lambda cell_pos: print(f"Entered cell ({cell_pos.x}, {cell_pos.y})") +grid.on_cell_exit = lambda cell_pos: print(f"Left cell ({cell_pos.x}, {cell_pos.y})") +``` + +See [[Grid-Interaction-Patterns]] for usage examples. + +--- + +## Event Priority + +### Click Dispatch Order + +Clicks are dispatched in reverse render order (front to back): + +1. **UI elements with highest z_index** receive clicks first +2. If handled (callback exists), propagation stops +3. **Entities on grids** receive clicks next +4. **Grid cells** receive clicks last + +### Keyboard Priority + +Keyboard events go only to the current scene's `on_key` handler. There is no concept of "focused" UI elements for keyboard input. + +--- + +## Common Patterns + +### WASD Movement + +```python +scene = mcrfpy.Scene("game") + +def handle_key(key, action): + if action != mcrfpy.InputState.PRESSED: + return + moves = { + mcrfpy.Key.W: (0, -1), + mcrfpy.Key.A: (-1, 0), + mcrfpy.Key.S: (0, 1), + mcrfpy.Key.D: (1, 0), + } + if key in moves: + dx, dy = moves[key] + player_move(dx, dy) + +scene.on_key = handle_key +``` + +### Button Widget + +```python +def make_button(text, x, y, callback): + btn = mcrfpy.Frame(pos=(x, y), size=(120, 40), + fill_color=mcrfpy.Color(60, 60, 80)) + label = mcrfpy.Caption(text=text, x=10, y=8) + btn.children.append(label) + + def on_click(pos, button, action): + if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED: + callback() + + btn.on_click = on_click + btn.on_enter = lambda pos: setattr(btn, 'fill_color', mcrfpy.Color(80, 80, 110)) + btn.on_exit = lambda pos: setattr(btn, 'fill_color', mcrfpy.Color(60, 60, 80)) + return btn +``` + +### Scene Switching with Keys + +```python +game_scene = mcrfpy.Scene("game") +menu_scene = mcrfpy.Scene("menu") + +def game_keys(key, action): + if key == mcrfpy.Key.ESCAPE and action == mcrfpy.InputState.PRESSED: + menu_scene.activate() + +def menu_keys(key, action): + if key == mcrfpy.Key.ESCAPE and action == mcrfpy.InputState.PRESSED: + game_scene.activate() + +game_scene.on_key = game_keys +menu_scene.on_key = menu_keys +``` + +--- + +## Testing Input + +Use the automation API to simulate input in tests: + +```python +from mcrfpy import automation + +# Keyboard +automation.keyDown("w") # Press W +automation.keyUp("w") # Release W +automation.press("space") # Press and release + +# Mouse +automation.click(100, 200, button='left') +automation.click(100, 200, button='right') +automation.moveTo(300, 400) # Move mouse + +# Screenshots for verification +automation.screenshot("test_result.png") +``` + +See [[Writing-Tests]] for complete testing patterns. + +--- + +## API Quick Reference + +**Scene:** +- `scene.on_key = handler` - Register keyboard handler `(key: Key, action: InputState)` + +**UIDrawable (Frame, Caption, Sprite, Grid, etc.):** +- `on_click` - Click handler: `(pos: Vector, button: MouseButton, action: InputState)` +- `on_enter` - Mouse enter: `(pos: Vector)` +- `on_exit` - Mouse leave: `(pos: Vector)` +- `on_move` - Mouse move: `(pos: Vector)` +- `hovered` - Read-only bool + +**Grid cell events:** +- `on_cell_click` - Cell click: `(cell_pos: Vector, button: MouseButton, action: InputState)` +- `on_cell_enter` - Cell enter: `(cell_pos: Vector)` +- `on_cell_exit` - Cell leave: `(cell_pos: Vector)` +- `hovered_cell` - Read-only `(x, y)` tuple or `None` + +**Enums:** +- `mcrfpy.Key` - Keyboard key codes (A-Z, Num0-9, F1-F15, arrows, modifiers) +- `mcrfpy.MouseButton` - LEFT, RIGHT, MIDDLE, SCROLL_UP, SCROLL_DOWN +- `mcrfpy.InputState` - PRESSED, RELEASED + +**Deprecated (still functional but not recommended):** +- `mcrfpy.keypressScene(callback)` - Use `scene.on_key` instead +- Old callback signature `(key: str, pressed: bool)` - Use enum-based signature instead + +--- + +*Last updated: 2026-02-07* \ No newline at end of file