Update Input and Events wiki with current API: Key/InputState/MouseButton enums, scene.on_key, Vector-based callbacks
parent
ae0993ef59
commit
222ee83009
1 changed files with 264 additions and 162 deletions
|
|
@ -1,162 +1,264 @@
|
||||||
# Input and Events
|
# 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.
|
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:**
|
**Related Pages:**
|
||||||
- [[UI-Widget-Patterns]] - Modal dialogs, buttons, hotbars
|
- [[UI-Widget-Patterns]] - Modal dialogs, buttons, hotbars
|
||||||
- [[Grid-Interaction-Patterns]] - Entity selection, movement, context menus
|
- [[Grid-Interaction-Patterns]] - Entity selection, movement, context menus
|
||||||
- [[Writing-Tests]] - Testing input with automation API
|
- [[Writing-Tests]] - Testing input with automation API
|
||||||
|
|
||||||
**Key Files:**
|
**Key Files:**
|
||||||
- `src/GameEngine.cpp::processEvent()` - Event dispatch
|
- `src/GameEngine.cpp::processEvent()` - Event dispatch
|
||||||
- `src/Scene.cpp::sUserInput()` - Scene input handling
|
- `src/PyScene.cpp` - Scene input handling
|
||||||
- `src/McRFPy_API.cpp` - Python callback registration
|
- `src/PyCallable.cpp` - Callback wrappers (PyClickCallable, PyHoverCallable, PyCellCallable)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Keyboard Input
|
## 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
|
```python
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
|
|
||||||
def handle_key(key: str, pressed: bool):
|
scene = mcrfpy.Scene("game")
|
||||||
"""
|
|
||||||
key: Key name (e.g., "W", "Space", "Escape", "Up")
|
def handle_key(key, action):
|
||||||
pressed: True on press, False on release
|
"""
|
||||||
"""
|
key: mcrfpy.Key enum (e.g., Key.W, Key.ESCAPE)
|
||||||
if key == "Escape" and pressed:
|
action: mcrfpy.InputState enum (PRESSED or RELEASED)
|
||||||
mcrfpy.setScene("menu")
|
"""
|
||||||
|
if key == mcrfpy.Key.ESCAPE and action == mcrfpy.InputState.PRESSED:
|
||||||
mcrfpy.keypressScene(handle_key)
|
menu_scene.activate()
|
||||||
```
|
elif key == mcrfpy.Key.W and action == mcrfpy.InputState.PRESSED:
|
||||||
|
player_move(0, -1)
|
||||||
### Key Names
|
|
||||||
|
scene.on_key = handle_key
|
||||||
Common key names from SFML:
|
mcrfpy.current_scene = scene
|
||||||
- Letters: `"A"` through `"Z"`
|
```
|
||||||
- Numbers: `"Num0"` through `"Num9"`
|
|
||||||
- Arrows: `"Up"`, `"Down"`, `"Left"`, `"Right"`
|
### Key Enum (`mcrfpy.Key`)
|
||||||
- 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`
|
||||||
## Mouse Input
|
- **Arrows:** `Key.UP`, `Key.DOWN`, `Key.LEFT`, `Key.RIGHT`
|
||||||
|
- **Special:** `Key.SPACE`, `Key.ENTER`, `Key.ESCAPE`, `Key.TAB`, `Key.BACKSPACE`, `Key.DELETE`
|
||||||
### Element Event Handlers
|
- **Modifiers:** `Key.LSHIFT`, `Key.RSHIFT`, `Key.LCTRL`, `Key.RCTRL`, `Key.LALT`, `Key.RALT`
|
||||||
|
- **Function:** `Key.F1` through `Key.F15`
|
||||||
All UIDrawables support mouse event callbacks:
|
|
||||||
|
### InputState Enum (`mcrfpy.InputState`)
|
||||||
| Property | Signature | When Called |
|
|
||||||
|----------|-----------|-------------|
|
| Value | Meaning |
|
||||||
| `on_click` | `(x, y, button) -> None` | Mouse button pressed on element |
|
|-------|---------|
|
||||||
| `on_enter` | `() -> None` | Mouse enters element bounds |
|
| `InputState.PRESSED` | Key/button was just pressed down |
|
||||||
| `on_exit` | `() -> None` | Mouse leaves element bounds |
|
| `InputState.RELEASED` | Key/button was just released |
|
||||||
| `on_move` | `(x, y) -> None` | Mouse moves within element |
|
|
||||||
|
**Legacy compatibility:** Enum values support string comparison for backwards compatibility: `InputState.PRESSED == "start"` returns `True`. However, using the enum directly is preferred.
|
||||||
```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}")
|
## Mouse Input
|
||||||
frame.on_enter = lambda: print("Mouse entered")
|
|
||||||
frame.on_exit = lambda: print("Mouse exited")
|
### Element Click Handlers
|
||||||
```
|
|
||||||
|
All UIDrawable elements (Frame, Caption, Sprite, Grid, Line, Circle, Arc) support mouse event callbacks:
|
||||||
The `hovered` property (read-only) indicates whether the mouse is currently over an element.
|
|
||||||
|
| Property | Signature | When Called |
|
||||||
### Grid Cell Events
|
|----------|-----------|-------------|
|
||||||
|
| `on_click` | `(pos: Vector, button: MouseButton, action: InputState)` | Mouse button pressed/released on element |
|
||||||
Grids provide cell-level mouse events in addition to element events:
|
| `on_enter` | `(pos: Vector)` | Mouse enters element bounds |
|
||||||
|
| `on_exit` | `(pos: Vector)` | Mouse leaves element bounds |
|
||||||
| Property | Signature | Description |
|
| `on_move` | `(pos: Vector)` | Mouse moves within element |
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `on_cell_click` | `(grid_x, grid_y, button) -> None` | Cell clicked |
|
```python
|
||||||
| `on_cell_enter` | `(grid_x, grid_y) -> None` | Mouse enters cell |
|
frame = mcrfpy.Frame(pos=(100, 100), size=(200, 50))
|
||||||
| `on_cell_exit` | `(grid_x, grid_y) -> None` | Mouse leaves cell |
|
|
||||||
| `hovered_cell` | `(x, y)` or `None` | Currently hovered cell (read-only) |
|
def on_frame_click(pos, button, action):
|
||||||
|
if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED:
|
||||||
See [[Grid-Interaction-Patterns]] for usage examples.
|
print(f"Left-clicked at ({pos.x}, {pos.y})")
|
||||||
|
elif button == mcrfpy.MouseButton.RIGHT and action == mcrfpy.InputState.PRESSED:
|
||||||
### Mouse Position
|
print("Right-clicked!")
|
||||||
|
|
||||||
```python
|
frame.on_click = on_frame_click
|
||||||
# Window coordinates (pixels)
|
frame.on_enter = lambda pos: print(f"Mouse entered at ({pos.x}, {pos.y})")
|
||||||
x, y = mcrfpy.getMousePos()
|
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.
|
||||||
## Event Priority
|
|
||||||
|
### MouseButton Enum (`mcrfpy.MouseButton`)
|
||||||
### Click Dispatch Order
|
|
||||||
|
| Value | Meaning |
|
||||||
Clicks are dispatched in reverse render order (front to back):
|
|-------|---------|
|
||||||
|
| `MouseButton.LEFT` | Left mouse button |
|
||||||
1. **UI elements with highest z_index** receive clicks first
|
| `MouseButton.RIGHT` | Right mouse button |
|
||||||
2. If handled (callback returns truthy), propagation stops
|
| `MouseButton.MIDDLE` | Middle mouse button (scroll wheel click) |
|
||||||
3. **Entities on grids** receive clicks next
|
| `MouseButton.SCROLL_UP` | Scroll wheel up |
|
||||||
4. **Grid cells** receive clicks last
|
| `MouseButton.SCROLL_DOWN` | Scroll wheel down |
|
||||||
|
|
||||||
### Keyboard Priority
|
### Grid Cell Events
|
||||||
|
|
||||||
Keyboard events go only to the current scene's registered callback. There is no concept of "focused" UI elements for keyboard input.
|
Grids provide cell-level mouse events with grid coordinates:
|
||||||
|
|
||||||
---
|
| Property | Signature | Description |
|
||||||
|
|----------|-----------|-------------|
|
||||||
## Window Events
|
| `on_cell_click` | `(cell_pos: Vector, button: MouseButton, action: InputState)` | Cell clicked |
|
||||||
|
| `on_cell_enter` | `(cell_pos: Vector)` | Mouse enters cell |
|
||||||
### Resize Events
|
| `on_cell_exit` | `(cell_pos: Vector)` | Mouse leaves cell |
|
||||||
|
| `hovered_cell` | `(x, y)` tuple or `None` | Currently hovered cell (read-only) |
|
||||||
Window resize events are not currently exposed directly. Workaround using timer polling:
|
|
||||||
|
```python
|
||||||
```python
|
grid = mcrfpy.Grid(grid_size=(20, 15), pos=(50, 50), size=(400, 300))
|
||||||
last_size = mcrfpy.getWindowSize()
|
|
||||||
|
def on_cell_click(cell_pos, button, action):
|
||||||
def check_resize(dt):
|
if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED:
|
||||||
global last_size
|
x, y = int(cell_pos.x), int(cell_pos.y)
|
||||||
current = mcrfpy.getWindowSize()
|
point = grid.at(x, y)
|
||||||
if current != last_size:
|
print(f"Cell ({x}, {y}): walkable={point.walkable}")
|
||||||
on_resize(current)
|
|
||||||
last_size = current
|
grid.on_cell_click = on_cell_click
|
||||||
|
grid.on_cell_enter = lambda cell_pos: print(f"Entered cell ({cell_pos.x}, {cell_pos.y})")
|
||||||
mcrfpy.setTimer("resize_check", check_resize, 100)
|
grid.on_cell_exit = lambda cell_pos: print(f"Left cell ({cell_pos.x}, {cell_pos.y})")
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
See [[Grid-Interaction-Patterns]] for usage examples.
|
||||||
|
|
||||||
## Testing Input
|
---
|
||||||
|
|
||||||
Use the automation API to simulate input in tests:
|
## Event Priority
|
||||||
|
|
||||||
```python
|
### Click Dispatch Order
|
||||||
from mcrfpy import automation
|
|
||||||
|
Clicks are dispatched in reverse render order (front to back):
|
||||||
automation.keypress("W", True) # Press W
|
|
||||||
automation.keypress("W", False) # Release W
|
1. **UI elements with highest z_index** receive clicks first
|
||||||
automation.click(100, 200, button=0) # Left-click at position
|
2. If handled (callback exists), propagation stops
|
||||||
```
|
3. **Entities on grids** receive clicks next
|
||||||
|
4. **Grid cells** receive clicks last
|
||||||
See [[Writing-Tests]] for complete testing patterns.
|
|
||||||
|
### 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.
|
||||||
## API Reference
|
|
||||||
|
---
|
||||||
**Module Functions:**
|
|
||||||
- `mcrfpy.keypressScene(callback)` - Register keyboard handler
|
## Common Patterns
|
||||||
- `mcrfpy.getMousePos() -> (int, int)` - Get mouse position
|
|
||||||
- `mcrfpy.getWindowSize() -> (int, int)` - Get window dimensions
|
### WASD Movement
|
||||||
|
|
||||||
**UIDrawable Properties:**
|
```python
|
||||||
- `on_click`, `on_enter`, `on_exit`, `on_move` - Event handlers
|
scene = mcrfpy.Scene("game")
|
||||||
- `hovered` - Mouse hover state (read-only)
|
|
||||||
|
def handle_key(key, action):
|
||||||
**Grid Properties:**
|
if action != mcrfpy.InputState.PRESSED:
|
||||||
- `on_cell_click`, `on_cell_enter`, `on_cell_exit` - Cell event handlers
|
return
|
||||||
- `hovered_cell` - Currently hovered cell (read-only)
|
moves = {
|
||||||
|
mcrfpy.Key.W: (0, -1),
|
||||||
---
|
mcrfpy.Key.A: (-1, 0),
|
||||||
|
mcrfpy.Key.S: (0, 1),
|
||||||
*Last updated: 2025-11-29*
|
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*
|
||||||
Loading…
Add table
Add a link
Reference in a new issue