Mouse State Tracking and Enter/Exit Events #140

Closed
opened 2025-11-27 18:34:42 +00:00 by john · 0 comments
Owner

Track mouse position and hovered element in C++, fire Python callbacks only on state transitions.

Context

Python currently has no awareness of mouse position or hover state. Games need hover highlighting, tooltips, and similar UX patterns. Following the "C++ every frame, Python every tick" philosophy, C++ tracks state and only calls Python when state changes.

Definition of Done

C++ Side (every frame)

  • Track current mouse position in screen coordinates
  • Compute currently hovered UIDrawable via AABB hit testing
  • Handle Z-order: topmost (last rendered) element wins
  • Compare current vs previous frame's hovered element
  • Fire on_exit on previous element (if changed and callback set)
  • Fire on_enter on new element (if changed and callback set)
  • Store state for Python queries

Python Side (queryable)

  • mcrfpy.mouse_pos property → (x, y) tuple, read-only
  • drawable.hovered property → bool, read-only
  • Works in both windowed and headless modes

Callbacks

  • drawable.on_enter - called when mouse enters bounds, no arguments
  • drawable.on_exit - called when mouse leaves bounds, no arguments

Technical Notes

State Machine

C++ GameEngine::processEvents():
    mouse_pos = get_current_mouse_position()
    new_hovered = find_topmost_drawable_at(mouse_pos)
    
    if new_hovered != current_hovered:
        if current_hovered and current_hovered.on_exit:
            call_python(current_hovered.on_exit)
        if new_hovered and new_hovered.on_enter:
            call_python(new_hovered.on_enter)
        current_hovered = new_hovered

Hit Testing Order

Iterate UI elements in reverse render order (last rendered = topmost = first to test).
Stop at first hit. Only the topmost element receives enter/exit events.

Nested Elements

A Frame containing a Caption: if mouse is over the Caption, only the Caption gets on_enter. The Frame does NOT get on_enter unless the mouse is over the Frame but not over any child.

This matches typical GUI behavior. If bubble-up is needed later, it's a separate enhancement.

Performance

When on_enter and on_exit are both None (common case), the Python call overhead is zero. C++ still tracks state for the hovered property query.

Dependencies

  • Blocked by: AABB / Hit Testing System (NEW)
  • Blocked by: #122 (Parent-Child UI System) - for correct Z-order traversal
  • Blocked by: #102 (Global Position) - for nested element hit testing
  • Enables: Grid Cell Events (NEW)
  • Enables: #45 (Accessibility) - affordances need hover state
Track mouse position and hovered element in C++, fire Python callbacks only on state transitions. ## Context Python currently has no awareness of mouse position or hover state. Games need hover highlighting, tooltips, and similar UX patterns. Following the "C++ every frame, Python every tick" philosophy, C++ tracks state and only calls Python when state changes. ## Definition of Done ### C++ Side (every frame) - [ ] Track current mouse position in screen coordinates - [ ] Compute currently hovered UIDrawable via AABB hit testing - [ ] Handle Z-order: topmost (last rendered) element wins - [ ] Compare current vs previous frame's hovered element - [ ] Fire `on_exit` on previous element (if changed and callback set) - [ ] Fire `on_enter` on new element (if changed and callback set) - [ ] Store state for Python queries ### Python Side (queryable) - [ ] `mcrfpy.mouse_pos` property → `(x, y)` tuple, read-only - [ ] `drawable.hovered` property → `bool`, read-only - [ ] Works in both windowed and headless modes ### Callbacks - [ ] `drawable.on_enter` - called when mouse enters bounds, no arguments - [ ] `drawable.on_exit` - called when mouse leaves bounds, no arguments ## Technical Notes ### State Machine ``` C++ GameEngine::processEvents(): mouse_pos = get_current_mouse_position() new_hovered = find_topmost_drawable_at(mouse_pos) if new_hovered != current_hovered: if current_hovered and current_hovered.on_exit: call_python(current_hovered.on_exit) if new_hovered and new_hovered.on_enter: call_python(new_hovered.on_enter) current_hovered = new_hovered ``` ### Hit Testing Order Iterate UI elements in reverse render order (last rendered = topmost = first to test). Stop at first hit. Only the topmost element receives enter/exit events. ### Nested Elements A Frame containing a Caption: if mouse is over the Caption, only the Caption gets `on_enter`. The Frame does NOT get `on_enter` unless the mouse is over the Frame but not over any child. This matches typical GUI behavior. If bubble-up is needed later, it's a separate enhancement. ## Performance When `on_enter` and `on_exit` are both None (common case), the Python call overhead is zero. C++ still tracks state for the `hovered` property query. ## Dependencies - **Blocked by**: AABB / Hit Testing System (NEW) - **Blocked by**: #122 (Parent-Child UI System) - for correct Z-order traversal - **Blocked by**: #102 (Global Position) - for nested element hit testing ## Related Issues - Enables: Grid Cell Events (NEW) - Enables: #45 (Accessibility) - affordances need hover state
john added this to the Cursor, Grid, and Callback project 2025-11-27 19:29:44 +00:00
john closed this issue 2025-11-28 19:47:38 +00:00
Sign in to join this conversation.
No Milestone
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: john/McRogueFace#140
No description provided.