3 Input and Events
John McCardle edited this page 2026-02-07 22:18:14 +00:00

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:

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:

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.NUM_0 through Key.NUM_9
  • Numpad: Key.NUMPAD_0 through Key.NUMPAD_9
  • Arrows: Key.UP, Key.DOWN, Key.LEFT, Key.RIGHT
  • Special: Key.SPACE, Key.ENTER, Key.ESCAPE, Key.TAB, Key.BACKSPACE, Key.DELETE
  • Modifiers: Key.LEFT_SHIFT, Key.RIGHT_SHIFT, Key.LEFT_CONTROL, Key.RIGHT_CONTROL, Key.LEFT_ALT, Key.RIGHT_ALT
  • Function: Key.F1 through Key.F15
  • Navigation: Key.HOME, Key.END, Key.PAGE_UP, Key.PAGE_DOWN, Key.INSERT
  • Punctuation: Key.COMMA, Key.PERIOD, Key.SEMICOLON, Key.APOSTROPHE, Key.SLASH, Key.BACKSLASH, Key.GRAVE, Key.HYPHEN, Key.EQUAL
  • Brackets: Key.LEFT_BRACKET, Key.RIGHT_BRACKET
  • Numpad ops: Key.ADD, Key.SUBTRACT, Key.MULTIPLY, Key.DIVIDE

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
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)
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

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

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

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:

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, NUM_0-NUM_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