2025-12-28 15:30:17 -05:00
#!/usr/bin/env python3
""" Focus System Demo for McRogueFace
Demonstrates a Python - level focus management system using engine primitives .
This shows how game developers can implement keyboard navigation without
requiring C + + engine changes .
Features demonstrated :
- Click - to - focus
- Tab / Shift + Tab cycling
- Visual focus indicators
- Keyboard routing to focused widget
- Modal focus stack
- Three widget types : Grid ( WASD ) , TextInput , MenuIcon
Issue : #143
"""
import mcrfpy
import sys
# =============================================================================
# Modifier Key Tracker (workaround until #160 is implemented)
# =============================================================================
class ModifierTracker :
""" Tracks modifier key state since engine doesn ' t expose this yet. """
def __init__ ( self ) :
self . shift = False
self . ctrl = False
self . alt = False
def update ( self , key : str , action : str ) :
""" Call this from your key handler to update modifier state. """
if key in ( " LShift " , " RShift " ) :
self . shift = ( action == " start " )
elif key in ( " LControl " , " RControl " ) :
self . ctrl = ( action == " start " )
elif key in ( " LAlt " , " RAlt " ) :
self . alt = ( action == " start " )
# =============================================================================
# Focus Manager
# =============================================================================
class FocusManager :
""" Central focus coordinator for a scene.
Manages which widget receives keyboard input , handles tab cycling ,
and maintains a modal stack for popup dialogs .
"""
# Focus indicator colors
FOCUS_COLOR = mcrfpy . Color ( 0 , 150 , 255 ) # Blue
UNFOCUS_COLOR = mcrfpy . Color ( 80 , 80 , 80 ) # Dark gray
FOCUS_OUTLINE = 3.0
UNFOCUS_OUTLINE = 1.0
def __init__ ( self ) :
self . widgets = [ ] # List of (widget, focusable: bool)
self . focus_index = - 1 # Currently focused widget index
self . modal_stack = [ ] # Stack of (modal_frame, previous_focus_index)
self . modifiers = ModifierTracker ( )
def register ( self , widget , focusable : bool = True ) :
""" Add a widget to the focus order.
Args :
widget : Object implementing on_focus ( ) , on_blur ( ) , handle_key ( )
focusable : Whether this widget can receive focus via Tab
"""
self . widgets . append ( ( widget , focusable ) )
# Give widget a reference back to us for click-to-focus
widget . _focus_manager = self
widget . _focus_index = len ( self . widgets ) - 1
def focus ( self , widget_or_index ) :
""" Set focus to a specific widget. """
# Resolve to index
if isinstance ( widget_or_index , int ) :
new_index = widget_or_index
else :
new_index = next (
( i for i , ( w , _ ) in enumerate ( self . widgets ) if w is widget_or_index ) ,
- 1
)
if new_index < 0 or new_index > = len ( self . widgets ) :
return
# Blur old widget
if 0 < = self . focus_index < len ( self . widgets ) :
old_widget , _ = self . widgets [ self . focus_index ]
if hasattr ( old_widget , ' on_blur ' ) :
old_widget . on_blur ( )
# Focus new widget
self . focus_index = new_index
new_widget , _ = self . widgets [ new_index ]
if hasattr ( new_widget , ' on_focus ' ) :
new_widget . on_focus ( )
def cycle ( self , direction : int = 1 ) :
""" Cycle focus to next/previous focusable widget.
Args :
direction : 1 for next ( Tab ) , - 1 for previous ( Shift + Tab )
"""
if not self . widgets :
return
start = self . focus_index if self . focus_index > = 0 else 0
current = start
for _ in range ( len ( self . widgets ) ) :
current = ( current + direction ) % len ( self . widgets )
widget , focusable = self . widgets [ current ]
if focusable :
self . focus ( current )
return
# No focusable widget found, stay where we are
def push_modal ( self , modal_frame , first_focus_widget = None ) :
""" Push a modal onto the focus stack.
Args :
modal_frame : The Frame to show as modal
first_focus_widget : Widget to focus inside modal ( optional )
"""
# Save current focus
self . modal_stack . append ( ( modal_frame , self . focus_index ) )
# Show modal
modal_frame . visible = True
# Focus first widget in modal if specified
if first_focus_widget is not None :
self . focus ( first_focus_widget )
def pop_modal ( self ) :
""" Pop the top modal and restore previous focus. """
if not self . modal_stack :
return False
modal_frame , previous_focus = self . modal_stack . pop ( )
modal_frame . visible = False
# Restore focus
if previous_focus > = 0 :
self . focus ( previous_focus )
return True
def handle_key ( self , key : str , action : str ) - > bool :
""" Main key handler - route to focused widget or handle global keys.
Returns True if key was consumed .
"""
# Always update modifier state
self . modifiers . update ( key , action )
# Only process on key press, not release (key repeat sends multiple "start")
if action != " start " :
return False
# Global: Escape closes modals
if key == " Escape " :
if self . pop_modal ( ) :
return True
# Global: Tab cycles focus
if key == " Tab " :
direction = - 1 if self . modifiers . shift else 1
self . cycle ( direction )
return True
# Route to focused widget
if 0 < = self . focus_index < len ( self . widgets ) :
widget , _ = self . widgets [ self . focus_index ]
if hasattr ( widget , ' handle_key ' ) :
if widget . handle_key ( key , action ) :
return True
return False
# =============================================================================
# Focusable Widgets
# =============================================================================
class FocusableGrid :
""" A grid where WASD keys move a player entity.
Demonstrates focus on a game - world element .
"""
def __init__ ( self , x : float , y : float , grid_w : int , grid_h : int ,
tile_size : int = 16 , zoom : float = 2.0 ) :
self . grid_w = grid_w
self . grid_h = grid_h
self . tile_size = tile_size
self . zoom = zoom
self . base_x = x
self . base_y = y
# Calculate pixel dimensions
self . cell_px = tile_size * zoom # Pixels per cell
grid_pixel_w = grid_w * self . cell_px
grid_pixel_h = grid_h * self . cell_px
# Create the grid background
self . grid = mcrfpy . Grid (
pos = ( x , y ) ,
grid_size = ( grid_w , grid_h ) ,
size = ( grid_pixel_w , grid_pixel_h )
)
self . grid . zoom = zoom
self . grid . fill_color = mcrfpy . Color ( 40 , 40 , 55 )
# Add outline frame for focus indication
self . outline_frame = mcrfpy . Frame (
pos = ( x - 2 , y - 2 ) ,
size = ( grid_pixel_w + 4 , grid_pixel_h + 4 ) ,
fill_color = mcrfpy . Color ( 0 , 0 , 0 , 0 ) ,
outline_color = FocusManager . UNFOCUS_COLOR ,
outline = FocusManager . UNFOCUS_OUTLINE
)
# Player marker (a bright square overlay)
self . player_x = grid_w / / 2
self . player_y = grid_h / / 2
marker_size = self . cell_px - 4 # Slightly smaller than cell
self . player_marker = mcrfpy . Frame (
pos = ( 0 , 0 ) , # Will be positioned by _update_player_display
size = ( marker_size , marker_size ) ,
fill_color = mcrfpy . Color ( 255 , 200 , 50 ) ,
outline_color = mcrfpy . Color ( 255 , 150 , 0 ) ,
outline = 2
)
self . _update_player_display ( )
# Click handler
self . grid . on_click = self . _on_click
# Focus manager reference (set by FocusManager.register)
self . _focus_manager = None
self . _focus_index = - 1
def _on_click ( self , x , y , button , action ) :
""" Handle click to focus this grid. """
if self . _focus_manager and action == " start " :
self . _focus_manager . focus ( self . _focus_index )
def _update_player_display ( self ) :
""" Update the visual representation of player position. """
# Position the player marker
px = self . base_x + ( self . player_x * self . cell_px ) + 2
py = self . base_y + ( self . player_y * self . cell_px ) + 2
self . player_marker . x = px
self . player_marker . y = py
def on_focus ( self ) :
""" Called when this widget gains focus. """
self . outline_frame . outline_color = FocusManager . FOCUS_COLOR
self . outline_frame . outline = FocusManager . FOCUS_OUTLINE
def on_blur ( self ) :
""" Called when this widget loses focus. """
self . outline_frame . outline_color = FocusManager . UNFOCUS_COLOR
self . outline_frame . outline = FocusManager . UNFOCUS_OUTLINE
def handle_key ( self , key : str , action : str ) - > bool :
""" Handle WASD movement. """
moves = {
" W " : ( 0 , - 1 ) , " Up " : ( 0 , - 1 ) ,
" A " : ( - 1 , 0 ) , " Left " : ( - 1 , 0 ) ,
" S " : ( 0 , 1 ) , " Down " : ( 0 , 1 ) ,
" D " : ( 1 , 0 ) , " Right " : ( 1 , 0 ) ,
}
if key in moves :
dx , dy = moves [ key ]
new_x = self . player_x + dx
new_y = self . player_y + dy
# Bounds check
if 0 < = new_x < self . grid_w and 0 < = new_y < self . grid_h :
self . player_x = new_x
self . player_y = new_y
self . _update_player_display ( )
return True
return False
def add_to_scene ( self , ui ) :
""" Add all components to a scene ' s UI collection. """
ui . append ( self . outline_frame )
ui . append ( self . grid )
ui . append ( self . player_marker )
class TextInputWidget :
""" A text input field with cursor and editing.
Demonstrates text entry with focus indication .
"""
def __init__ ( self , x : float , y : float , width : float , label : str = " " ,
placeholder : str = " " ) :
self . x = x
self . y = y
self . width = width
self . height = 28
self . label_text = label
self . placeholder_text = placeholder
# State
self . text = " "
self . cursor_pos = 0
self . focused = False
# Create UI elements
self . _create_ui ( )
# Focus manager reference
self . _focus_manager = None
self . _focus_index = - 1
def _create_ui ( self ) :
""" Create the visual components. """
# Label above input
if self . label_text :
self . label = mcrfpy . Caption (
text = self . label_text ,
pos = ( self . x , self . y - 20 )
)
self . label . fill_color = mcrfpy . Color ( 200 , 200 , 200 )
# Input background
self . frame = mcrfpy . Frame (
pos = ( self . x , self . y ) ,
size = ( self . width , self . height ) ,
fill_color = mcrfpy . Color ( 40 , 40 , 50 ) ,
outline_color = FocusManager . UNFOCUS_COLOR ,
outline = FocusManager . UNFOCUS_OUTLINE
)
self . frame . on_click = self . _on_click
# Placeholder text
self . placeholder = mcrfpy . Caption (
text = self . placeholder_text ,
pos = ( self . x + 6 , self . y + 5 )
)
self . placeholder . fill_color = mcrfpy . Color ( 100 , 100 , 100 )
# Actual text display
self . display = mcrfpy . Caption (
text = " " ,
pos = ( self . x + 6 , self . y + 5 )
)
self . display . fill_color = mcrfpy . Color ( 255 , 255 , 255 )
# Cursor (thin frame)
self . cursor = mcrfpy . Frame (
pos = ( self . x + 6 , self . y + 4 ) ,
size = ( 2 , self . height - 8 ) ,
fill_color = mcrfpy . Color ( 255 , 255 , 255 )
)
self . cursor . visible = False
def _on_click ( self , x , y , button , action ) :
""" Handle click to focus. """
if self . _focus_manager and action == " start " :
self . _focus_manager . focus ( self . _focus_index )
def _update_display ( self ) :
""" Update visual state. """
self . display . text = self . text
self . placeholder . visible = ( not self . text and not self . focused )
self . _update_cursor ( )
def _update_cursor ( self ) :
""" Update cursor position. """
# Approximate character width (monospace assumption)
char_width = 10
self . cursor . x = self . x + 6 + ( self . cursor_pos * char_width )
def on_focus ( self ) :
""" Called when gaining focus. """
self . focused = True
self . frame . outline_color = FocusManager . FOCUS_COLOR
self . frame . outline = FocusManager . FOCUS_OUTLINE
self . cursor . visible = True
self . _update_display ( )
def on_blur ( self ) :
""" Called when losing focus. """
self . focused = False
self . frame . outline_color = FocusManager . UNFOCUS_COLOR
self . frame . outline = FocusManager . UNFOCUS_OUTLINE
self . cursor . visible = False
self . _update_display ( )
def handle_key ( self , key : str , action : str ) - > bool :
""" Handle text input and editing keys. """
if not self . focused :
return False
old_text = self . text
handled = True
if key == " BackSpace " :
if self . cursor_pos > 0 :
self . text = self . text [ : self . cursor_pos - 1 ] + self . text [ self . cursor_pos : ]
self . cursor_pos - = 1
elif key == " Delete " :
if self . cursor_pos < len ( self . text ) :
self . text = self . text [ : self . cursor_pos ] + self . text [ self . cursor_pos + 1 : ]
elif key == " Left " :
self . cursor_pos = max ( 0 , self . cursor_pos - 1 )
elif key == " Right " :
self . cursor_pos = min ( len ( self . text ) , self . cursor_pos + 1 )
elif key == " Home " :
self . cursor_pos = 0
elif key == " End " :
self . cursor_pos = len ( self . text )
elif key in ( " Return " , " Tab " ) :
# Don't consume - let focus manager handle
handled = False
elif len ( key ) == 1 and key . isprintable ( ) :
# Insert character
self . text = self . text [ : self . cursor_pos ] + key + self . text [ self . cursor_pos : ]
self . cursor_pos + = 1
else :
handled = False
self . _update_display ( )
return handled
def get_text ( self ) - > str :
""" Get the current text value. """
return self . text
def set_text ( self , text : str ) :
""" Set the text value. """
self . text = text
self . cursor_pos = len ( text )
self . _update_display ( )
def add_to_scene ( self , ui ) :
""" Add all components to the scene. """
if hasattr ( self , ' label ' ) :
ui . append ( self . label )
ui . append ( self . frame )
ui . append ( self . placeholder )
ui . append ( self . display )
ui . append ( self . cursor )
class MenuIcon :
""" An icon that opens a modal dialog when activated.
Demonstrates activation via Space / Enter and modal focus .
"""
def __init__ ( self , x : float , y : float , size : float , icon_char : str ,
tooltip : str , modal_content_builder = None ) :
self . x = x
self . y = y
self . size = size
self . tooltip = tooltip
self . modal_content_builder = modal_content_builder
self . modal = None
# Create icon frame
self . frame = mcrfpy . Frame (
pos = ( x , y ) ,
size = ( size , size ) ,
fill_color = mcrfpy . Color ( 60 , 60 , 80 ) ,
outline_color = FocusManager . UNFOCUS_COLOR ,
outline = FocusManager . UNFOCUS_OUTLINE
)
self . frame . on_click = self . _on_click
# Icon character (centered)
self . icon = mcrfpy . Caption (
text = icon_char ,
pos = ( x + size / / 3 , y + size / / 6 )
)
self . icon . fill_color = mcrfpy . Color ( 200 , 200 , 220 )
# Tooltip (shown on hover/focus)
self . tooltip_caption = mcrfpy . Caption (
text = tooltip ,
pos = ( x , y + size + 4 )
)
self . tooltip_caption . fill_color = mcrfpy . Color ( 150 , 150 , 150 )
self . tooltip_caption . visible = False
# Focus manager reference
self . _focus_manager = None
self . _focus_index = - 1
def _on_click ( self , x , y , button , action ) :
""" Handle click to focus or activate. """
if not self . _focus_manager :
return
if action == " start " :
# If already focused, activate; otherwise just focus
if self . _focus_manager . focus_index == self . _focus_index :
self . _activate ( )
else :
self . _focus_manager . focus ( self . _focus_index )
def _activate ( self ) :
""" Open the modal dialog. """
if self . modal and self . _focus_manager :
self . _focus_manager . push_modal ( self . modal )
def on_focus ( self ) :
""" Called when gaining focus. """
self . frame . outline_color = FocusManager . FOCUS_COLOR
self . frame . outline = FocusManager . FOCUS_OUTLINE
self . frame . fill_color = mcrfpy . Color ( 80 , 80 , 110 )
self . tooltip_caption . visible = True
def on_blur ( self ) :
""" Called when losing focus. """
self . frame . outline_color = FocusManager . UNFOCUS_COLOR
self . frame . outline = FocusManager . UNFOCUS_OUTLINE
self . frame . fill_color = mcrfpy . Color ( 60 , 60 , 80 )
self . tooltip_caption . visible = False
def handle_key ( self , key : str , action : str ) - > bool :
""" Handle activation keys. """
if key in ( " Space " , " Return " ) :
self . _activate ( )
return True
return False
def set_modal ( self , modal_frame ) :
""" Set the modal frame this icon opens. """
self . modal = modal_frame
def add_to_scene ( self , ui ) :
""" Add all components to the scene. """
ui . append ( self . frame )
ui . append ( self . icon )
ui . append ( self . tooltip_caption )
# =============================================================================
# Modal Dialog Builder
# =============================================================================
def create_modal ( x : float , y : float , width : float , height : float ,
title : str ) - > mcrfpy . Frame :
""" Create a modal dialog frame. """
# Semi-transparent backdrop
# Note: This is simplified - real implementation might want fullscreen backdrop
# Modal frame
modal = mcrfpy . Frame (
pos = ( x , y ) ,
size = ( width , height ) ,
fill_color = mcrfpy . Color ( 40 , 40 , 50 ) ,
outline_color = mcrfpy . Color ( 100 , 100 , 120 ) ,
outline = 2
)
modal . visible = False
# Title
title_caption = mcrfpy . Caption (
text = title ,
pos = ( x + 10 , y + 8 )
)
title_caption . fill_color = mcrfpy . Color ( 220 , 220 , 240 )
modal . children . append ( title_caption )
# Close hint
close_hint = mcrfpy . Caption (
text = " [Esc to close] " ,
pos = ( x + width - 100 , y + 8 )
)
close_hint . fill_color = mcrfpy . Color ( 120 , 120 , 140 )
modal . children . append ( close_hint )
return modal
# =============================================================================
# Demo Scene Setup
# =============================================================================
def create_demo_scene ( ) :
""" Create and populate the focus system demo scene. """
# Create scene
2026-01-03 10:59:52 -05:00
focus_demo = mcrfpy . Scene ( " focus_demo " )
ui = focus_demo . children
2025-12-28 15:30:17 -05:00
# Background
bg = mcrfpy . Frame (
pos = ( 0 , 0 ) ,
size = ( 1024 , 768 ) ,
fill_color = mcrfpy . Color ( 25 , 25 , 35 )
)
ui . append ( bg )
# Title
title = mcrfpy . Caption (
text = " Focus System Demo " ,
pos = ( 20 , 15 )
)
title . fill_color = mcrfpy . Color ( 255 , 255 , 255 )
ui . append ( title )
# Instructions
instructions = mcrfpy . Caption (
text = " Tab: cycle focus | Shift+Tab: reverse | WASD: move in grid | Space/Enter: activate | Esc: close modal " ,
pos = ( 20 , 45 )
)
instructions . fill_color = mcrfpy . Color ( 150 , 150 , 170 )
ui . append ( instructions )
# Create focus manager
focus_mgr = FocusManager ( )
# --- Grid Section ---
grid_label = mcrfpy . Caption ( text = " Game Grid (WASD to move) " , pos = ( 50 , 90 ) )
grid_label . fill_color = mcrfpy . Color ( 180 , 180 , 200 )
ui . append ( grid_label )
grid_widget = FocusableGrid ( 50 , 115 , 10 , 8 , tile_size = 16 , zoom = 2.0 )
grid_widget . add_to_scene ( ui )
focus_mgr . register ( grid_widget )
# --- Text Inputs Section ---
input_label = mcrfpy . Caption ( text = " Text Inputs " , pos = ( 400 , 90 ) )
input_label . fill_color = mcrfpy . Color ( 180 , 180 , 200 )
ui . append ( input_label )
name_input = TextInputWidget ( 400 , 130 , 250 , label = " Name: " , placeholder = " Enter your name " )
name_input . add_to_scene ( ui )
focus_mgr . register ( name_input )
class_input = TextInputWidget ( 400 , 200 , 250 , label = " Class: " , placeholder = " e.g. Warrior, Mage " )
class_input . add_to_scene ( ui )
focus_mgr . register ( class_input )
notes_input = TextInputWidget ( 400 , 270 , 350 , label = " Notes: " , placeholder = " Additional notes... " )
notes_input . add_to_scene ( ui )
focus_mgr . register ( notes_input )
# --- Menu Icons Section ---
icons_label = mcrfpy . Caption ( text = " Menu Icons " , pos = ( 50 , 390 ) )
icons_label . fill_color = mcrfpy . Color ( 180 , 180 , 200 )
ui . append ( icons_label )
# Help icon
help_icon = MenuIcon ( 50 , 420 , 48 , " ? " , " Help " )
help_icon . add_to_scene ( ui )
focus_mgr . register ( help_icon )
help_modal = create_modal ( 200 , 150 , 400 , 300 , " Help " )
ui . append ( help_modal )
help_text = mcrfpy . Caption (
text = " This demo shows focus management. \n \n Use Tab to move between widgets. \n WASD moves the player in the grid. \n Type in text fields. \n Press Space on icons to open dialogs. " ,
pos = ( 210 , 190 )
)
help_text . fill_color = mcrfpy . Color ( 200 , 200 , 200 )
help_modal . children . append ( help_text )
help_icon . set_modal ( help_modal )
# Settings icon
settings_icon = MenuIcon ( 110 , 420 , 48 , " S " , " Settings " )
settings_icon . add_to_scene ( ui )
focus_mgr . register ( settings_icon )
settings_modal = create_modal ( 200 , 150 , 400 , 250 , " Settings " )
ui . append ( settings_modal )
settings_text = mcrfpy . Caption (
text = " Settings would go here. \n \n (This is a placeholder modal) " ,
pos = ( 210 , 190 )
)
settings_text . fill_color = mcrfpy . Color ( 200 , 200 , 200 )
settings_modal . children . append ( settings_text )
settings_icon . set_modal ( settings_modal )
# Inventory icon
inv_icon = MenuIcon ( 170 , 420 , 48 , " I " , " Inventory " )
inv_icon . add_to_scene ( ui )
focus_mgr . register ( inv_icon )
inv_modal = create_modal ( 200 , 150 , 400 , 300 , " Inventory " )
ui . append ( inv_modal )
inv_text = mcrfpy . Caption (
text = " Your inventory: \n \n - Sword \n - Shield \n - 3x Potions " ,
pos = ( 210 , 190 )
)
inv_text . fill_color = mcrfpy . Color ( 200 , 200 , 200 )
inv_modal . children . append ( inv_text )
inv_icon . set_modal ( inv_modal )
# --- Status Display ---
status_frame = mcrfpy . Frame (
pos = ( 50 , 520 ) ,
size = ( 700 , 80 ) ,
fill_color = mcrfpy . Color ( 35 , 35 , 45 ) ,
outline_color = mcrfpy . Color ( 60 , 60 , 70 ) ,
outline = 1
)
ui . append ( status_frame )
status_label = mcrfpy . Caption ( text = " Status " , pos = ( 60 , 530 ) )
status_label . fill_color = mcrfpy . Color ( 150 , 150 , 170 )
ui . append ( status_label )
status_text = mcrfpy . Caption ( text = " Click or Tab to focus a widget " , pos = ( 60 , 555 ) )
status_text . fill_color = mcrfpy . Color ( 200 , 200 , 200 )
ui . append ( status_text )
# Store references for status updates
demo_state = {
' focus_mgr ' : focus_mgr ,
' status_text ' : status_text ,
' grid ' : grid_widget ,
' inputs ' : [ name_input , class_input , notes_input ] ,
' icons ' : [ help_icon , settings_icon , inv_icon ] ,
}
# Key handler that routes to focus manager
def on_key ( key : str , action : str ) :
focus_mgr . handle_key ( key , action )
# Update status display
if focus_mgr . focus_index > = 0 :
widget , _ = focus_mgr . widgets [ focus_mgr . focus_index ]
if widget is grid_widget :
status_text . text = f " Grid focused - Player at ( { grid_widget . player_x } , { grid_widget . player_y } ) "
elif widget in demo_state [ ' inputs ' ] :
idx = demo_state [ ' inputs ' ] . index ( widget )
labels = [ " Name " , " Class " , " Notes " ]
status_text . text = f " { labels [ idx ] } input focused - Text: ' { widget . get_text ( ) } ' "
elif widget in demo_state [ ' icons ' ] :
status_text . text = f " Icon focused: { widget . tooltip } "
else :
status_text . text = " No widget focused "
2025-12-28 15:35:48 -05:00
# Activate scene first (keypressScene sets handler for CURRENT scene)
2026-01-03 10:59:52 -05:00
focus_demo . activate ( )
2025-12-28 15:35:48 -05:00
# Register key handler for the now-current scene
2026-01-03 10:59:52 -05:00
focus_demo . on_key = on_key
2025-12-28 15:30:17 -05:00
# Set initial focus
focus_mgr . focus ( 0 )
return demo_state
# =============================================================================
# Entry Point
# =============================================================================
def run_demo ( ) :
""" Run the focus system demo. """
print ( " === Focus System Demo === " )
print ( " Demonstrating Python-level focus management " )
print ( )
print ( " Controls: " )
print ( " Tab / Shift+Tab - Cycle between widgets " )
print ( " WASD / Arrows - Move player in grid (when focused) " )
print ( " Type - Enter text in inputs (when focused) " )
print ( " Space / Enter - Activate icons (when focused) " )
print ( " Escape - Close modal dialogs " )
print ( " Click - Focus clicked widget " )
print ( )
demo_state = create_demo_scene ( )
# Set up exit timer for headless testing
2026-01-03 22:44:53 -05:00
def check_exit ( timer , dt ) :
2025-12-28 15:30:17 -05:00
# In headless mode, exit after a short delay
# In interactive mode, this won't trigger
pass
2026-01-03 22:44:53 -05:00
# check_exit_timer = mcrfpy.Timer("demo_check", check_exit, 100)
2025-12-28 15:30:17 -05:00
# Run if executed directly
if __name__ == " __main__ " :
import sys
from mcrfpy import automation
run_demo ( )
# If --screenshot flag, take a screenshot and exit
if " --screenshot " in sys . argv or len ( sys . argv ) > 1 :
2026-01-03 22:44:53 -05:00
def take_screenshot ( timer , dt ) :
2025-12-28 15:30:17 -05:00
automation . screenshot ( " focus_demo_screenshot.png " )
print ( " Screenshot saved: focus_demo_screenshot.png " )
sys . exit ( 0 )
2026-01-03 22:44:53 -05:00
screenshot_timer = mcrfpy . Timer ( " screenshot " , take_screenshot , 200 , once = True )