Input and Events
Overview
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-Component-Hierarchy - UI element interaction
- Entity-Management - Entity-based input (click handlers)
- Writing-Tests - Testing input with automation API
Key Files:
src/GameEngine.cpp::processEvent()- Event dispatchsrc/Scene.cpp::input()- Scene input handlingsrc/McRFPy_API.cpp- Python callback registration
Related Issues:
Keyboard Input
Scene-Level Keyboard Callbacks
Register a callback that fires on every key press/release:
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:
print("Escape pressed!")
mcrfpy.setScene("menu")
if key == "W":
if pressed:
player.start_moving_north()
else:
player.stop_moving_north()
# Register for current scene
mcrfpy.keypressScene(handle_key)
Key Names
Common key names from SFML:
- Letters:
"A","B","C", ..."Z" - Numbers:
"Num0","Num1", ..."Num9" - Arrows:
"Up","Down","Left","Right" - Special:
"Space","Enter","Escape","Tab","LShift","RShift","LControl","RControl" - Function:
"F1","F2", ..."F12"
See src/McRFPy_API.cpp::McRFPy_API_keypressScene() for full key mapping.
Mouse Input
Click Events
Mouse clicks are dispatched to UI elements based on screen position:
import mcrfpy
# Create a frame with click handler
frame = mcrfpy.Frame(pos=(100, 100), size=(200, 50))
def on_frame_click(button: int, x: int, y: int):
"""
button: 0=left, 1=right, 2=middle
x, y: Mouse position in window coordinates
"""
print(f"Frame clicked with button {button} at ({x}, {y})")
frame.click = on_frame_click
Entity Click Handlers
Entities can register click callbacks for grid-based interaction:
player = mcrfpy.Entity(grid_pos=(10, 10), sprite_index=1)
def on_entity_click(button: int):
if button == 0: # Left-click
print("Selected player!")
elif button == 1: # Right-click
print("Context menu for player")
elif button == 2: # Middle-click
print("Middle-clicked entity")
player.click = on_entity_click
Issue #128 adds proper middle-click support for entity selection.
Mouse Position
Get current mouse position in game coordinates:
# Window coordinates (pixels)
window_x, window_y = mcrfpy.getMousePos()
# Convert to game coordinates (respects viewport)
game_x, game_y = mcrfpy.windowToGameCoords(window_x, window_y)
Window Events
Resize Events
Issue #1 tracks window resize event support. Current workaround:
def update_on_resize(ms):
"""Timer-based resize detection"""
current_size = mcrfpy.getWindowSize()
if current_size != last_size:
on_window_resized(current_size)
mcrfpy.setTimer("resize_check", update_on_resize, 100)
Focus Events
Window focus/unfocus events are not currently exposed to Python. The engine does not pause automatically when focus is lost.
Event Priority and Propagation
Click Dispatch Order
Clicks are dispatched in reverse render order (top-to-bottom):
- UI elements with highest z_index receive clicks first
- If a UI element handles the click, propagation stops
- Entities on grids receive clicks if no UI element handled it
- Grid cells receive clicks last
# High z_index UI blocks clicks to entities below
overlay = mcrfpy.Frame(pos=(0, 0), size=(1024, 768), z_index=100)
overlay.click = lambda btn, x, y: True # Blocks all clicks
# Remove overlay to allow clicks through
ui.remove(overlay)
Keyboard Priority
Keyboard events are dispatched only to the current scene's registered callback. There is no concept of "focused" UI elements for keyboard input.
Common Patterns
Modal Dialog
Capture all input while a dialog is open:
class ModalDialog:
def __init__(self, message: str):
self.frame = mcrfpy.Frame(pos=(300, 200), size=(400, 200), z_index=1000)
self.caption = mcrfpy.Caption(text=message, pos=(310, 220))
self.frame.click = self._on_click
# Save previous input handlers
self.prev_keypress = mcrfpy.keypressScene(self._on_key)
ui = mcrfpy.sceneUI("game")
ui.append(self.frame)
ui.append(self.caption)
def _on_click(self, btn, x, y):
return True # Block all clicks
def _on_key(self, key, pressed):
if key == "Enter" and pressed:
self.close()
return True # Block all keys
def close(self):
ui = mcrfpy.sceneUI("game")
ui.remove(self.frame)
ui.remove(self.caption)
mcrfpy.keypressScene(self.prev_keypress) # Restore
WASD Movement
Handle movement keys with press/release state:
class PlayerController:
def __init__(self, entity):
self.entity = entity
self.moving = {"W": False, "A": False, "S": False, "D": False}
def handle_key(self, key, pressed):
if key in self.moving:
self.moving[key] = pressed
self._update_velocity()
def _update_velocity(self):
vx = 0
vy = 0
if self.moving["W"]: vy -= 1
if self.moving["S"]: vy += 1
if self.moving["A"]: vx -= 1
if self.moving["D"]: vx += 1
# Normalize diagonal movement
import math
magnitude = math.sqrt(vx*vx + vy*vy)
if magnitude > 0:
vx /= magnitude
vy /= magnitude
self.entity.velocity_x = vx * 5.0
self.entity.velocity_y = vy * 5.0
controller = PlayerController(player)
mcrfpy.keypressScene(controller.handle_key)
Context Menu
Right-click to show context-sensitive options:
def show_context_menu(entity, x, y):
"""Show menu at mouse position"""
menu_frame = mcrfpy.Frame(pos=(x, y), size=(150, 100), z_index=500)
# Menu options
option1 = mcrfpy.Caption(text="Examine", pos=(x+10, y+10))
option2 = mcrfpy.Caption(text="Attack", pos=(x+10, y+30))
option3 = mcrfpy.Caption(text="Talk", pos=(x+10, y+50))
def on_menu_click(btn, mx, my):
# Determine which option was clicked
if y+10 <= my < y+30:
entity.examine()
elif y+30 <= my < y+50:
entity.attack()
elif y+50 <= my < y+70:
entity.talk()
# Close menu
ui = mcrfpy.sceneUI("game")
ui.remove(menu_frame)
ui.remove(option1)
ui.remove(option2)
ui.remove(option3)
menu_frame.click = on_menu_click
ui = mcrfpy.sceneUI("game")
ui.append(menu_frame)
ui.append(option1)
ui.append(option2)
ui.append(option3)
# Entity right-click handler
def entity_right_click(button):
if button == 1: # Right-click
mx, my = mcrfpy.getMousePos()
show_context_menu(entity, mx, my)
entity.click = entity_right_click
Hotbar / Quick Slots
Number keys for quick item access:
hotbar = [None] * 10 # 10 slots
def handle_hotbar(key, pressed):
if pressed and key.startswith("Num"):
slot = int(key[3:]) # "Num1" -> 1
if 0 <= slot < 10 and hotbar[slot]:
hotbar[slot].use()
mcrfpy.keypressScene(handle_hotbar)
Testing Input
Automation API
Use the automation API to simulate input in tests:
import mcrfpy
from mcrfpy import automation
# Simulate keyboard input
automation.keypress("W", True) # Press W
automation.keypress("W", False) # Release W
# Simulate mouse clicks
automation.click(100, 200, button=0) # Left-click at (100, 200)
automation.click(150, 250, button=1) # Right-click
# Wait for effects
import time
time.sleep(0.1)
# Verify results with screenshot
automation.screenshot("test_result.png")
See Writing-Tests for complete testing patterns.
Performance Considerations
Event Handler Complexity
Event handlers run on the main thread and block rendering:
# BAD: Expensive computation in event handler
def handle_key(key, pressed):
if key == "Space" and pressed:
# This will freeze the game!
for i in range(1000000):
expensive_calculation()
# GOOD: Defer expensive work to timer
def handle_key(key, pressed):
if key == "Space" and pressed:
mcrfpy.setTimer("deferred_work", do_expensive_work, 10)
Click Handler Optimization
Avoid creating handlers that search large collections:
# BAD: O(n) search on every click
def on_click(btn, x, y):
for entity in all_entities: # Expensive!
if entity.contains_point(x, y):
entity.select()
# GOOD: Use spatial data structures (see [[Grid-System]])
def on_click(btn, x, y):
grid_x, grid_y = screen_to_grid(x, y)
entity = grid.get_entity_at(grid_x, grid_y) # O(1)
if entity:
entity.select()
API Reference
See docs/api_reference_dynamic.html for complete input API documentation.
Key Functions:
mcrfpy.keypressScene(callback)- Register keyboard handlermcrfpy.getMousePos() -> (int, int)- Get mouse positionmcrfpy.windowToGameCoords(x, y) -> (float, float)- Convert coordinates
UI Element Properties:
element.click = callback- Click handler for any UIDrawableentity.click = callback- Click handler for entities
Navigation:
- Home - Documentation hub
- Entity-Management - Entity interaction patterns
- UI-Component-Hierarchy - UI element click regions
- Writing-Tests - Testing input with automation API