Update Input and Events wiki with current API: Key/InputState/MouseButton enums, scene.on_key, Vector-based callbacks

John McCardle 2026-02-07 22:15:28 +00:00
commit 222ee83009

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