diff --git a/Input-and-Events.-.md b/Input-and-Events.-.md deleted file mode 100644 index 30cec4f..0000000 --- a/Input-and-Events.-.md +++ /dev/null @@ -1,395 +0,0 @@ -# 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 dispatch -- `src/Scene.cpp::input()` - Scene input handling -- `src/McRFPy_API.cpp` - Python callback registration - -**Related Issues:** -- [#128](../../issues/128) - Middle-click entity selection -- [#1](../../issues/1) - Window resize events - ---- - -## Keyboard Input - -### Scene-Level Keyboard Callbacks - -Register a 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: - 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: - -```python -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: - -```python -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: - -```python -# 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: - -```python -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): - -1. **UI elements with highest z_index** receive clicks first -2. If a UI element handles the click, propagation stops -3. **Entities on grids** receive clicks if no UI element handled it -4. **Grid cells** receive clicks last - -```python -# 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: - -```python -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: - -```python -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: - -```python -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: - -```python -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: - -```python -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: - -```python -# 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: - -```python -# 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`](../../src/branch/master/docs/api_reference_dynamic.html) for complete input API documentation. - -**Key Functions:** -- `mcrfpy.keypressScene(callback)` - Register keyboard handler -- `mcrfpy.getMousePos() -> (int, int)` - Get mouse position -- `mcrfpy.windowToGameCoords(x, y) -> (float, float)` - Convert coordinates - -**UI Element Properties:** -- `element.click = callback` - Click handler for any UIDrawable -- `entity.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