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

@ -9,83 +9,123 @@ McRogueFace provides keyboard, mouse, and window event handling through Python c
**Key Files:**
- `src/GameEngine.cpp::processEvent()` - Event dispatch
- `src/Scene.cpp::sUserInput()` - Scene input handling
- `src/McRFPy_API.cpp` - Python callback registration
- `src/PyScene.cpp` - Scene input handling
- `src/PyCallable.cpp` - Callback wrappers (PyClickCallable, PyHoverCallable, PyCellCallable)
---
## Keyboard Input
Register a scene-level callback that fires on every key press/release:
Register a keyboard handler on a Scene object. The callback receives enum values, not strings:
```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")
scene = mcrfpy.Scene("game")
mcrfpy.keypressScene(handle_key)
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 Names
### Key Enum (`mcrfpy.Key`)
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"`
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 Event Handlers
### Element Click Handlers
All UIDrawables support mouse event callbacks:
All UIDrawable elements (Frame, Caption, Sprite, Grid, Line, Circle, Arc) 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 |
| `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))
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")
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 in addition to element events:
Grids provide cell-level mouse events with grid coordinates:
| 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
| `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
# Window coordinates (pixels)
x, y = mcrfpy.getMousePos()
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
@ -95,33 +135,74 @@ x, y = mcrfpy.getMousePos()
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
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 registered callback. There is no concept of "focused" UI elements for keyboard input.
Keyboard events go only to the current scene's `on_key` handler. There is no concept of "focused" UI elements for keyboard input.
---
## Window Events
## Common Patterns
### Resize Events
Window resize events are not currently exposed directly. Workaround using timer polling:
### WASD Movement
```python
last_size = mcrfpy.getWindowSize()
scene = mcrfpy.Scene("game")
def check_resize(dt):
global last_size
current = mcrfpy.getWindowSize()
if current != last_size:
on_resize(current)
last_size = current
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)
mcrfpy.setTimer("resize_check", check_resize, 100)
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
```
---
@ -133,30 +214,51 @@ 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
# 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 Reference
## API Quick Reference
**Module Functions:**
- `mcrfpy.keypressScene(callback)` - Register keyboard handler
- `mcrfpy.getMousePos() -> (int, int)` - Get mouse position
- `mcrfpy.getWindowSize() -> (int, int)` - Get window dimensions
**Scene:**
- `scene.on_key = handler` - Register keyboard handler `(key: Key, action: InputState)`
**UIDrawable Properties:**
- `on_click`, `on_enter`, `on_exit`, `on_move` - Event handlers
- `hovered` - Mouse hover state (read-only)
**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 Properties:**
- `on_cell_click`, `on_cell_enter`, `on_cell_exit` - Cell event handlers
- `hovered_cell` - Currently hovered cell (read-only)
**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: 2025-11-29*
*Last updated: 2026-02-07*