Implement main menu, profile list, and profile stats screens; add screen manager and user profile integration

This commit is contained in:
Matteo Benedetto
2025-08-24 22:13:00 +02:00
parent c004edef8f
commit 47028c95ae
21 changed files with 2889 additions and 1266 deletions
+180
View File
@@ -0,0 +1,180 @@
# Profile Manager Screen Modules
This folder contains individual screen modules for the Profile Manager, providing a clean separation of concerns and modular architecture.
## Structure
```
screens/
├── __init__.py # Module exports
├── base_screen.py # Abstract base class for all screens
├── screen_manager.py # Manages screen modules and navigation
├── main_menu_screen.py # Main menu interface
├── profile_list_screen.py # Profile selection and management
├── create_profile_screen.py # Profile creation with virtual keyboard
├── edit_profile_screen.py # Profile settings editing
├── leaderboard_screen.py # Leaderboard display (device/global)
├── profile_stats_screen.py # Detailed profile statistics
└── README.md # This file
```
## Architecture
### BaseScreen
- Abstract base class providing common interface
- Standard navigation methods (up, down, left, right)
- Input handling framework
- State management utilities
### Screen Modules
Each screen is a self-contained module with:
- **Rendering**: Complete screen display logic
- **Input Handling**: Screen-specific input processing
- **State Management**: Internal state tracking
- **Navigation**: Transitions to other screens
### ModularScreenManager
- Manages all screen module instances
- Handles screen transitions
- Provides error dialog overlay
- Maintains current screen state
## Usage
### Adding New Screens
1. **Create Screen Module**:
```python
from .base_screen import BaseScreen
class NewScreen(BaseScreen):
def render(self) -> None:
# Implement rendering logic
pass
def handle_input(self, action: str) -> bool:
# Implement input handling
pass
```
2. **Register in ScreenManager**:
```python
# In screen_manager.py _initialize_screens()
"new_screen": NewScreen(self.data_manager, self.ui_renderer, self)
```
3. **Add to Exports**:
```python
# In __init__.py
from .new_screen import NewScreen
```
### Screen Integration
To integrate with the main Profile Manager:
```python
from screens import ModularScreenManager
# Replace existing screen manager
self.screen_manager = ModularScreenManager(self.data_manager, self.ui_renderer)
# Handle input
result = self.screen_manager.handle_input(action)
# Render
self.screen_manager.render()
```
## Screen Responsibilities
### MainMenuScreen
- Primary navigation hub
- Profile status display
- API connection status
- Menu option availability
### ProfileListScreen
- Display existing profiles
- Profile selection
- Profile deletion
- Active profile indication
### CreateProfileScreen
- Virtual keyboard interface
- Profile name input
- Input validation
- Profile creation
### EditProfileScreen
- Settings adjustment interface
- Real-time value display
- Setting validation
- Changes persistence
### LeaderboardScreen
- Leaderboard data display
- Device/Global toggle
- Data refresh functionality
- User highlighting
### ProfileStatsScreen
- Comprehensive statistics
- Server sync status
- Settings summary
- Achievements display
## Benefits
### Modularity
- Each screen is independent and testable
- Clear separation of concerns
- Easy to add/remove/modify screens
### Maintainability
- Isolated screen logic
- Consistent interface pattern
- Reduced coupling between screens
### Extensibility
- Simple to add new screens
- Common functionality in base class
- Flexible navigation system
### Testability
- Individual screen unit tests
- Mock dependencies easily
- Isolated functionality testing
## Navigation Flow
```
Main Menu ─┬─ Create Profile
├─ Profile List
├─ Edit Profile (if profile active)
├─ Leaderboard (if API enabled)
├─ Profile Stats (if profile active)
└─ Exit
All screens ─→ Back to Main Menu (ESC)
```
## Input Actions
Standard actions handled by screens:
- `up`: Navigate selection up
- `down`: Navigate selection down
- `left`: Navigate left / Adjust setting
- `right`: Navigate right / Adjust setting
- `confirm`: Confirm selection / Enter
- `back`: Return to previous screen / ESC
- `delete`: Delete item / Backspace
## Error Handling
Screens can show error messages via:
```python
self.show_error("Error message")
```
The ModularScreenManager handles error dialog display and auto-dismissal.
+24
View File
@@ -0,0 +1,24 @@
"""
Screen modules for Profile Manager
Individual screen implementations with separated rendering and logic
"""
from .base_screen import BaseScreen
from .main_menu_screen import MainMenuScreen
from .profile_list_screen import ProfileListScreen
from .create_profile_screen import CreateProfileScreen
from .edit_profile_screen import EditProfileScreen
from .leaderboard_screen import LeaderboardScreen
from .profile_stats_screen import ProfileStatsScreen
from .screen_manager import ModularScreenManager
__all__ = [
'BaseScreen',
'MainMenuScreen',
'ProfileListScreen',
'CreateProfileScreen',
'EditProfileScreen',
'LeaderboardScreen',
'ProfileStatsScreen',
'ModularScreenManager'
]
+91
View File
@@ -0,0 +1,91 @@
"""
Base Screen Class
Common interface and functionality for all screens in the Profile Manager
"""
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional, Tuple
class BaseScreen(ABC):
"""Base class for all profile manager screens"""
def __init__(self, data_manager, ui_renderer, screen_manager):
"""
Initialize base screen
Args:
data_manager: ProfileDataManager instance
ui_renderer: UIRenderer instance
screen_manager: ScreenManager instance
"""
self.data_manager = data_manager
self.ui_renderer = ui_renderer
self.screen_manager = screen_manager
# Screen-specific state
self.selected_index = 0
self.screen_state = {}
@abstractmethod
def render(self) -> None:
"""Render the screen content"""
pass
@abstractmethod
def handle_input(self, action: str) -> bool:
"""
Handle input for this screen
Args:
action: Input action string ('up', 'down', 'left', 'right', 'confirm', 'back', 'delete')
Returns:
bool: True if input was handled, False otherwise
"""
pass
def navigate_up(self) -> None:
"""Navigate selection up"""
if self.selected_index > 0:
self.selected_index -= 1
def navigate_down(self, max_index: int) -> None:
"""Navigate selection down"""
if self.selected_index < max_index:
self.selected_index += 1
def navigate_left(self) -> None:
"""Navigate left (screen-specific implementation)"""
pass
def navigate_right(self) -> None:
"""Navigate right (screen-specific implementation)"""
pass
def handle_confirm(self) -> None:
"""Handle confirm action (screen-specific implementation)"""
pass
def handle_back(self) -> None:
"""Handle back action (default: return to main menu)"""
self.screen_manager.set_screen("main_menu")
def handle_delete(self) -> None:
"""Handle delete action (screen-specific implementation)"""
pass
def show_error(self, message: str) -> None:
"""Show error message through the screen manager"""
# Delegate to screen manager
if hasattr(self.screen_manager, 'show_error_dialog'):
self.screen_manager.show_error_dialog(message)
def reset_state(self) -> None:
"""Reset screen state when entering"""
self.selected_index = 0
self.screen_state.clear()
def get_help_text(self) -> str:
"""Get help text for this screen"""
return "↑↓ Navigate • Enter Confirm • Escape Back"
@@ -0,0 +1,141 @@
"""
Create Profile Screen
Virtual keyboard interface for creating new user profiles
"""
from .base_screen import BaseScreen
class CreateProfileScreen(BaseScreen):
"""Profile creation screen with virtual keyboard"""
def __init__(self, data_manager, ui_renderer, screen_manager):
super().__init__(data_manager, ui_renderer, screen_manager)
# Virtual keyboard layout
self.virtual_keyboard = [
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
['K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T'],
['U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3'],
['4', '5', '6', '7', '8', '9', ' ', '.', '-', '_'],
['<DEL', 'SPACE', 'DONE', 'CANCEL']
]
# Screen state
self.input_text = ""
self.input_active = False
self.vk_cursor_x = 0
self.vk_cursor_y = 0
def render(self) -> None:
"""Render profile creation screen"""
self.ui_renderer.draw_header("Create Profile")
# Input field
placeholder = "Profile Name" if not self.input_text else ""
self.ui_renderer.draw_input_field(self.input_text, 120, 90, 400, 30,
self.input_active, placeholder)
if self.input_active:
# Virtual keyboard
self.ui_renderer.draw_virtual_keyboard(self.virtual_keyboard,
self.vk_cursor_x, self.vk_cursor_y)
help_text = "Arrows: Navigate • Enter: Select • Escape: Cancel"
else:
self.ui_renderer.draw_text("Press Enter to start typing", 320, 150,
'yellow', 'medium', center=True)
self.ui_renderer.draw_button("← Back", 270, 200, 100, 30, True)
help_text = "Enter: Start Input • Escape: Back"
self.ui_renderer.draw_footer_help(help_text)
def handle_input(self, action: str) -> bool:
"""Handle create profile input"""
if self.input_active:
# Virtual keyboard navigation
if action == 'up':
self.vk_cursor_y = max(0, self.vk_cursor_y - 1)
max_x = len(self.virtual_keyboard[self.vk_cursor_y]) - 1
self.vk_cursor_x = min(self.vk_cursor_x, max_x)
return True
elif action == 'down':
max_y = len(self.virtual_keyboard) - 1
self.vk_cursor_y = min(max_y, self.vk_cursor_y + 1)
max_x = len(self.virtual_keyboard[self.vk_cursor_y]) - 1
self.vk_cursor_x = min(self.vk_cursor_x, max_x)
return True
elif action == 'left':
self.vk_cursor_x = max(0, self.vk_cursor_x - 1)
return True
elif action == 'right':
max_x = len(self.virtual_keyboard[self.vk_cursor_y]) - 1
self.vk_cursor_x = min(max_x, self.vk_cursor_x + 1)
return True
elif action == 'confirm':
self.handle_virtual_keyboard_input()
return True
elif action == 'back':
self.input_active = False
return True
else:
# Initial screen navigation
if action == 'confirm':
self.input_active = True
self.vk_cursor_x = 0
self.vk_cursor_y = 0
return True
elif action == 'back':
self.handle_back()
return True
elif action == 'delete' and self.input_text:
self.input_text = self.input_text[:-1]
return True
return False
def handle_virtual_keyboard_input(self) -> None:
"""Handle virtual keyboard character selection"""
if (self.vk_cursor_y >= len(self.virtual_keyboard) or
self.vk_cursor_x >= len(self.virtual_keyboard[self.vk_cursor_y])):
return
selected_char = self.virtual_keyboard[self.vk_cursor_y][self.vk_cursor_x]
if selected_char == '<DEL':
if self.input_text:
self.input_text = self.input_text[:-1]
elif selected_char == 'SPACE':
if len(self.input_text) < 20:
self.input_text += ' '
elif selected_char == 'DONE':
if self.input_text.strip():
success, message = self.data_manager.create_profile(self.input_text)
if success:
self.screen_manager.set_screen("main_menu")
self.input_active = False
self.input_text = ""
else:
self.show_error(message)
else:
self.show_error("Profile name cannot be empty!")
elif selected_char == 'CANCEL':
self.input_text = ""
self.input_active = False
else:
if len(self.input_text) < 20:
self.input_text += selected_char
def reset_state(self) -> None:
"""Reset screen state when entering"""
super().reset_state()
self.input_text = ""
self.input_active = False
self.vk_cursor_x = 0
self.vk_cursor_y = 0
def get_help_text(self) -> str:
"""Get help text for create profile"""
if self.input_active:
return "Arrows: Navigate • Enter: Select • Escape: Cancel"
else:
return "Enter: Start Input • Escape: Back"
@@ -0,0 +1,136 @@
"""
Edit Profile Screen
Interface for editing profile settings and configurations
"""
from .base_screen import BaseScreen
from profile_data import SettingsManager
class EditProfileScreen(BaseScreen):
"""Profile editing screen implementation"""
def __init__(self, data_manager, ui_renderer, screen_manager):
super().__init__(data_manager, ui_renderer, screen_manager)
# Settings configuration
self.settings = [
("Difficulty", "difficulty"),
("Sound Vol", "sound_volume"),
("Music Vol", "music_volume"),
("Screen Shake", "screen_shake"),
]
self.max_index = len(self.settings) + 1 # +1 for save/back buttons
def render(self) -> None:
"""Render profile editing screen"""
active_profile = self.data_manager.get_active_profile()
if not active_profile:
self.screen_manager.set_screen("main_menu")
return
# Header with profile info
subtitle = f"Games: {active_profile.games_played} • Score: {active_profile.best_score}"
self.ui_renderer.draw_header(f"Edit: {active_profile.name}", subtitle)
# Settings panel
self.ui_renderer.draw_panel(60, 90, 520, 280, 'dark_gray', 'gray')
self.ui_renderer.draw_text("Settings", 320, 105, 'white', 'large', center=True)
# Settings items
for i, (label, setting_key) in enumerate(self.settings):
item_y = 130 + i * 35
selected = (i == self.selected_index)
if selected:
self.ui_renderer.draw_panel(80, item_y, 480, 25, 'blue', 'light_blue')
text_color = 'white'
value_color = 'light_green'
# Navigation arrows
self.ui_renderer.draw_text("◄", 70, item_y + 5, 'white', 'small')
self.ui_renderer.draw_text("►", 570, item_y + 5, 'white', 'small')
else:
text_color = 'light_gray'
value_color = 'yellow'
# Setting label and value
self.ui_renderer.draw_text(label, 90, item_y + 5, text_color, 'medium')
value = active_profile.settings[setting_key]
display_value = SettingsManager.get_setting_display_value(setting_key, value)
self.ui_renderer.draw_text(display_value, 500, item_y + 5, value_color, 'medium')
# Action buttons
save_selected = (self.selected_index == len(self.settings))
back_selected = (self.selected_index == len(self.settings) + 1)
self.ui_renderer.draw_button("Save", 200, 310, 80, 30, save_selected)
self.ui_renderer.draw_button("← Back", 320, 310, 80, 30, back_selected)
self.ui_renderer.draw_footer_help(self.get_help_text())
def handle_input(self, action: str) -> bool:
"""Handle edit profile input"""
max_index = len(self.settings) + 1 # settings + save + back
if action == 'up':
self.navigate_up()
return True
elif action == 'down':
self.navigate_down(max_index)
return True
elif action == 'left':
self.adjust_setting(-1)
return True
elif action == 'right':
self.adjust_setting(1)
return True
elif action == 'confirm':
self.handle_confirm()
return True
elif action == 'back':
self.handle_back()
return True
return False
def adjust_setting(self, direction: int) -> None:
"""Adjust setting value left/right"""
if not self.data_manager.active_profile or self.selected_index >= len(self.settings):
return
profile = self.data_manager.get_active_profile()
if not profile:
return
setting_label, setting_name = self.settings[self.selected_index]
current_value = profile.settings[setting_name]
# Adjust based on setting type
if setting_name == "difficulty":
new_value = SettingsManager.adjust_difficulty(current_value, direction)
elif setting_name in ["sound_volume", "music_volume"]:
new_value = SettingsManager.adjust_volume(current_value, direction)
elif setting_name == "screen_shake":
new_value = SettingsManager.toggle_boolean(current_value)
else:
return
# Update the setting
success, message = self.data_manager.update_profile_settings(
self.data_manager.active_profile, setting_name, new_value
)
if not success:
self.show_error(message)
def handle_confirm(self) -> None:
"""Handle profile editing confirmation"""
if self.selected_index == len(self.settings): # Save
self.screen_manager.set_screen("main_menu")
elif self.selected_index == len(self.settings) + 1: # Back
self.screen_manager.set_screen("main_menu")
def get_help_text(self) -> str:
"""Get help text for edit profile"""
return "Left/Right: Adjust • Enter: Save/Back • Escape: Cancel"
@@ -0,0 +1,135 @@
"""
Leaderboard Screen
Display global and device leaderboards with rankings and scores
"""
from .base_screen import BaseScreen
class LeaderboardScreen(BaseScreen):
"""Leaderboard display screen implementation"""
def __init__(self, data_manager, ui_renderer, screen_manager):
super().__init__(data_manager, ui_renderer, screen_manager)
# Leaderboard state
self.leaderboard_type = "device" # "device" or "global"
self.leaderboard_data = []
def render(self) -> None:
"""Render leaderboard screen"""
title = f"{self.leaderboard_type.title()} Leaderboard"
self.ui_renderer.draw_header(title)
if not self.data_manager.api_enabled:
self.ui_renderer.draw_text("Server offline - No leaderboard available",
320, 150, 'red', 'medium', center=True)
self.ui_renderer.draw_button("← Back", 270, 200, 100, 30, True)
return
# Controls
toggle_text = f"Type: {self.leaderboard_type.title()} (Left/Right to toggle)"
toggle_selected = (self.selected_index == 0)
self.ui_renderer.draw_button(toggle_text, 120, 90, 400, 25, toggle_selected)
refresh_selected = (self.selected_index == 1)
back_selected = (self.selected_index == 2)
self.ui_renderer.draw_button("Refresh", 150, 125, 80, 25, refresh_selected)
self.ui_renderer.draw_button("← Back", 250, 125, 80, 25, back_selected)
# Leaderboard data
if not self.leaderboard_data:
self.ui_renderer.draw_text("No leaderboard data available", 320, 200,
'yellow', 'medium', center=True)
else:
self._render_leaderboard_data()
self.ui_renderer.draw_footer_help(self.get_help_text())
def _render_leaderboard_data(self) -> None:
"""Render the leaderboard data table"""
# Draw leaderboard entries
panel_height = min(250, len(self.leaderboard_data) * 25 + 40)
self.ui_renderer.draw_panel(50, 160, 540, panel_height, 'black', 'gray')
# Headers
self.ui_renderer.draw_text("Rank", 60, 175, 'light_blue', 'small')
self.ui_renderer.draw_text("Player", 120, 175, 'light_blue', 'small')
self.ui_renderer.draw_text("Score", 350, 175, 'light_blue', 'small')
self.ui_renderer.draw_text("Games", 450, 175, 'light_blue', 'small')
if self.leaderboard_type == "global":
self.ui_renderer.draw_text("Device", 520, 175, 'light_blue', 'small')
# Entries
for i, entry in enumerate(self.leaderboard_data):
entry_y = 195 + i * 22
# Highlight current user
is_current = (self.data_manager.active_profile and
entry.get('user_id') == self.data_manager.active_profile)
text_color = 'light_green' if is_current else 'light_gray'
self.ui_renderer.draw_text(str(entry.get('rank', i+1)), 60, entry_y, text_color, 'tiny')
player_name = entry.get('user_id', 'Unknown')[:20]
self.ui_renderer.draw_text(player_name, 120, entry_y, text_color, 'tiny')
self.ui_renderer.draw_text(str(entry.get('best_score', 0)), 350, entry_y, text_color, 'tiny')
self.ui_renderer.draw_text(str(entry.get('total_games', 0)), 450, entry_y, text_color, 'tiny')
if self.leaderboard_type == "global":
device = entry.get('device_id', '')[:8]
self.ui_renderer.draw_text(device, 520, entry_y, text_color, 'tiny')
def handle_input(self, action: str) -> bool:
"""Handle leaderboard input"""
max_index = 2 # Toggle, Refresh, Back
if action == 'up':
self.navigate_up()
return True
elif action == 'down':
self.navigate_down(max_index)
return True
elif action == 'left':
if self.selected_index == 0: # Toggle leaderboard type
self.toggle_leaderboard_type()
return True
elif action == 'right':
if self.selected_index == 0: # Toggle leaderboard type
self.toggle_leaderboard_type()
return True
elif action == 'confirm':
self.handle_confirm()
return True
elif action == 'back':
self.handle_back()
return True
return False
def toggle_leaderboard_type(self) -> None:
"""Toggle between device and global leaderboard"""
self.leaderboard_type = "global" if self.leaderboard_type == "device" else "device"
self.load_leaderboard_data()
def handle_confirm(self) -> None:
"""Handle leaderboard actions"""
if self.selected_index == 1: # Refresh
self.load_leaderboard_data()
elif self.selected_index == 2: # Back
self.handle_back()
def load_leaderboard_data(self) -> None:
"""Load leaderboard data from data manager"""
self.leaderboard_data = self.data_manager.get_leaderboard_data(self.leaderboard_type)
def reset_state(self) -> None:
"""Reset screen state when entering"""
super().reset_state()
self.load_leaderboard_data()
def get_help_text(self) -> str:
"""Get help text for leaderboard"""
return "Left/Right: Toggle Type • Enter: Select • Escape: Back"
+103
View File
@@ -0,0 +1,103 @@
"""
Main Menu Screen
The primary menu interface for profile management
"""
from .base_screen import BaseScreen
class MainMenuScreen(BaseScreen):
"""Main menu screen implementation"""
def __init__(self, data_manager, ui_renderer, screen_manager):
super().__init__(data_manager, ui_renderer, screen_manager)
self.menu_items = [
"Create Profile",
"Select Profile",
"Edit Settings",
"Leaderboard",
"Profile Stats",
"Exit"
]
def render(self) -> None:
"""Render main menu screen"""
# Header
active_profile = self.data_manager.active_profile
subtitle = f"Active: {active_profile}" if active_profile else "No active profile"
self.ui_renderer.draw_header("Profile Manager", subtitle)
# API status
api_status = "API: Connected" if self.data_manager.api_enabled else "API: Offline"
api_color = 'light_green' if self.data_manager.api_enabled else 'red'
self.ui_renderer.draw_text(api_status, 320, 80, api_color, 'tiny', center=True)
# Device ID
device_text = f"Device: {self.data_manager.device_id}"
self.ui_renderer.draw_text(device_text, 320, 95, 'light_gray', 'tiny', center=True)
# Draw menu panel
self.ui_renderer.draw_panel(120, 120, 400, 240, 'dark_gray', 'gray')
# Draw menu buttons
for i, item in enumerate(self.menu_items):
button_y = 135 + i * 38
selected = (i == self.selected_index)
# Gray out unavailable options
disabled = False
display_text = item
if (item in ["Edit Settings", "Profile Stats"]) and not active_profile:
disabled = True
display_text = f"{item} (No Profile)"
elif item == "Leaderboard" and not self.data_manager.api_enabled:
disabled = True
display_text = f"{item} (Offline)"
self.ui_renderer.draw_button(display_text, 180, button_y, 280, 30,
selected, disabled)
# Help text
self.ui_renderer.draw_footer_help(self.get_help_text())
def handle_input(self, action: str) -> bool:
"""Handle main menu input"""
if action == 'up':
self.navigate_up()
return True
elif action == 'down':
self.navigate_down(len(self.menu_items) - 1)
return True
elif action == 'confirm':
self.handle_confirm()
return True
elif action == 'back':
# Exit application from main menu
return False
return False
def handle_confirm(self) -> None:
"""Handle main menu selections"""
index = self.selected_index
if index == 0: # Create Profile
self.screen_manager.set_screen("create_profile")
elif index == 1: # Select Profile
self.screen_manager.set_screen("profile_list")
elif index == 2: # Settings
if self.data_manager.active_profile:
self.screen_manager.set_screen("edit_profile")
elif index == 3: # Leaderboard
if self.data_manager.api_enabled:
self.screen_manager.set_screen("leaderboard")
elif index == 4: # Profile Stats
if self.data_manager.active_profile:
self.screen_manager.set_screen("profile_stats")
elif index == 5: # Exit
return False
def get_help_text(self) -> str:
"""Get help text for main menu"""
return "↑↓ Navigate • Enter Confirm • Escape Exit"
@@ -0,0 +1,103 @@
"""
Profile List Screen
Display and select from existing user profiles
"""
from .base_screen import BaseScreen
class ProfileListScreen(BaseScreen):
"""Profile list selection screen implementation"""
def render(self) -> None:
"""Render profile selection screen"""
self.ui_renderer.draw_header("Select Profile")
profile_names = self.data_manager.get_profile_list()
if not profile_names:
# No profiles message
self.ui_renderer.draw_panel(120, 100, 400, 150, 'dark_gray', 'gray')
self.ui_renderer.draw_text("No profiles found", 320, 150, 'yellow', 'large', center=True)
self.ui_renderer.draw_text("Create one first", 320, 180, 'light_gray', 'medium', center=True)
self.ui_renderer.draw_button("← Back", 270, 300, 100, 30, True)
else:
# Profile list
panel_height = min(280, len(profile_names) * 55 + 60)
self.ui_renderer.draw_panel(30, 80, 580, panel_height, 'black', 'gray')
for i, name in enumerate(profile_names):
profile = self.data_manager.get_profile(name)
entry_y = 95 + i * 50
selected = (i == self.selected_index)
# Profile stats
stats_text = f"Games: {profile.games_played} • Score: {profile.best_score}"
indicator = "★" if name == self.data_manager.active_profile else ""
self.ui_renderer.draw_list_item(name, 40, entry_y, 560, 40,
selected, stats_text, indicator)
# Back button
back_y = 95 + len(profile_names) * 50 + 10
back_selected = (self.selected_index == len(profile_names))
self.ui_renderer.draw_button("← Back", 270, back_y, 100, 30, back_selected)
self.ui_renderer.draw_footer_help(self.get_help_text())
def handle_input(self, action: str) -> bool:
"""Handle profile list input"""
profile_names = self.data_manager.get_profile_list()
max_index = len(profile_names) # Includes back button
if action == 'up':
self.navigate_up()
return True
elif action == 'down':
self.navigate_down(max_index)
return True
elif action == 'confirm':
self.handle_confirm()
return True
elif action == 'back':
self.handle_back()
return True
elif action == 'delete':
self.handle_delete()
return True
return False
def handle_confirm(self) -> None:
"""Handle profile list selections"""
profile_names = self.data_manager.get_profile_list()
if self.selected_index < len(profile_names):
# Select profile
profile_name = profile_names[self.selected_index]
success, message = self.data_manager.set_active_profile(profile_name)
if success:
self.screen_manager.set_screen("main_menu")
else:
self.show_error(message)
else:
# Back option
self.handle_back()
def handle_delete(self) -> None:
"""Handle profile deletion"""
profile_names = self.data_manager.get_profile_list()
if self.selected_index < len(profile_names):
profile_name = profile_names[self.selected_index]
success, message = self.data_manager.delete_profile(profile_name)
if not success:
self.show_error(message)
else:
# Adjust selection if needed
max_index = max(0, len(self.data_manager.get_profile_list()) - 1)
self.selected_index = min(self.selected_index, max_index)
def get_help_text(self) -> str:
"""Get help text for profile list"""
return "Enter: Select • Escape: Back • Delete: Remove"
@@ -0,0 +1,106 @@
"""
Profile Stats Screen
Display detailed statistics for the active user profile
"""
from .base_screen import BaseScreen
class ProfileStatsScreen(BaseScreen):
"""Profile statistics display screen implementation"""
def render(self) -> None:
"""Render detailed profile statistics"""
if not self.data_manager.active_profile:
self.screen_manager.set_screen("main_menu")
return
stats_data = self.data_manager.get_profile_stats(self.data_manager.active_profile)
if not stats_data:
self.screen_manager.set_screen("main_menu")
return
profile = stats_data['profile']
# Header
self.ui_renderer.draw_header(f"Stats: {profile.name}")
# Stats panel
self.ui_renderer.draw_panel(50, 90, 540, 260, 'dark_gray', 'gray')
# Render statistics content
self._render_local_statistics(stats_data, profile)
self._render_server_status(stats_data)
self._render_settings_summary(profile)
# Back button
back_selected = (self.selected_index == 0)
self.ui_renderer.draw_button("← Back", 270, 370, 100, 30, back_selected)
self.ui_renderer.draw_footer_help(self.get_help_text())
def _render_local_statistics(self, stats_data, profile) -> None:
"""Render local profile statistics"""
y = 110
self.ui_renderer.draw_text("Local Statistics:", 60, y, 'light_blue', 'medium')
y += 25
stats = [
f"Games Played: {profile.games_played}",
f"Best Score: {profile.best_score}",
f"Total Score: {profile.total_score}",
f"Achievements: {len(profile.achievements)}",
f"Created: {stats_data['created_formatted']}",
f"Last Played: {stats_data['last_played_formatted']}"
]
for stat in stats:
self.ui_renderer.draw_text(stat, 70, y, 'light_gray', 'small')
y += 20
def _render_server_status(self, stats_data) -> None:
"""Render server connection and sync status"""
y = 250
if stats_data['api_enabled']:
self.ui_renderer.draw_text("Server Status:", 60, y, 'light_green', 'medium')
y += 20
integration_info = stats_data['integration_info']
device_id = integration_info.get('device_id', 'Unknown')
self.ui_renderer.draw_text(f"Device ID: {device_id}", 70, y, 'light_gray', 'small')
y += 20
self.ui_renderer.draw_text("✓ Profile synced with server", 70, y, 'light_green', 'small')
else:
self.ui_renderer.draw_text("Server Status:", 60, y, 'red', 'medium')
y += 20
self.ui_renderer.draw_text("✗ Server offline", 70, y, 'red', 'small')
def _render_settings_summary(self, profile) -> None:
"""Render current profile settings summary"""
settings_x = 350
y = 110
self.ui_renderer.draw_text("Current Settings:", settings_x, y, 'light_blue', 'medium')
y += 25
settings_display = [
f"Difficulty: {profile.settings.get('difficulty', 'normal').title()}",
f"Sound Volume: {profile.settings.get('sound_volume', 50)}%",
f"Music Volume: {profile.settings.get('music_volume', 50)}%",
f"Screen Shake: {'On' if profile.settings.get('screen_shake', True) else 'Off'}"
]
for setting in settings_display:
self.ui_renderer.draw_text(setting, settings_x + 10, y, 'light_gray', 'small')
y += 20
def handle_input(self, action: str) -> bool:
"""Handle profile stats input"""
if action == 'confirm' or action == 'back':
self.handle_back()
return True
return False
def get_help_text(self) -> str:
"""Get help text for profile stats"""
return "Enter: Back • Escape: Back to Main Menu"
+142
View File
@@ -0,0 +1,142 @@
"""
Screen Manager Module
Manages individual screen modules and navigation between them
"""
from typing import Dict, Optional
from screens import (
BaseScreen,
MainMenuScreen,
ProfileListScreen,
CreateProfileScreen,
EditProfileScreen,
LeaderboardScreen,
ProfileStatsScreen
)
class ModularScreenManager:
"""Manages individual screen modules and navigation"""
def __init__(self, data_manager, ui_renderer):
"""
Initialize screen manager with all screen modules
Args:
data_manager: ProfileDataManager instance
ui_renderer: UIRenderer instance
"""
self.data_manager = data_manager
self.ui_renderer = ui_renderer
# Current screen state
self.current_screen_name = "main_menu"
self.screens: Dict[str, BaseScreen] = {}
# Initialize all screen modules
self._initialize_screens()
# Error state
self.show_error = False
self.error_message = ""
self.error_timer = 0
def _initialize_screens(self) -> None:
"""Initialize all screen module instances"""
self.screens = {
"main_menu": MainMenuScreen(self.data_manager, self.ui_renderer, self),
"profile_list": ProfileListScreen(self.data_manager, self.ui_renderer, self),
"create_profile": CreateProfileScreen(self.data_manager, self.ui_renderer, self),
"edit_profile": EditProfileScreen(self.data_manager, self.ui_renderer, self),
"leaderboard": LeaderboardScreen(self.data_manager, self.ui_renderer, self),
"profile_stats": ProfileStatsScreen(self.data_manager, self.ui_renderer, self)
}
def set_screen(self, screen_name: str) -> None:
"""
Switch to a different screen
Args:
screen_name: Name of the screen to switch to
"""
if screen_name in self.screens:
self.current_screen_name = screen_name
# Reset state when entering new screen
self.screens[screen_name].reset_state()
else:
print(f"Warning: Unknown screen '{screen_name}'")
def get_current_screen(self) -> Optional[BaseScreen]:
"""Get the current active screen module"""
return self.screens.get(self.current_screen_name)
def handle_input(self, action: str) -> bool:
"""
Handle input by delegating to current screen
Args:
action: Input action string
Returns:
bool: True if input was handled and app should continue, False to exit
"""
# Dismiss error dialog on any input
if self.show_error:
self.show_error = False
return True
current_screen = self.get_current_screen()
if current_screen:
result = current_screen.handle_input(action)
# Handle special case for main menu exit
if self.current_screen_name == "main_menu" and action == 'back':
return False # Exit application
return result if result is not None else True
return True
def render(self) -> None:
"""Render current screen and any overlays"""
current_screen = self.get_current_screen()
if current_screen:
current_screen.render()
# Render error dialog if active
if self.show_error:
import time
self.ui_renderer.draw_error_dialog(self.error_message)
# Auto-dismiss after timer
if time.time() > self.error_timer:
self.show_error = False
def show_error_dialog(self, message: str) -> None:
"""
Show an error dialog overlay
Args:
message: Error message to display
"""
import time
self.show_error = True
self.error_message = message
self.error_timer = time.time() + 3.0
@property
def current_screen(self) -> str:
"""Get current screen name (for compatibility)"""
return self.current_screen_name
@property
def selected_index(self) -> int:
"""Get selected index from current screen (for compatibility)"""
current_screen = self.get_current_screen()
return current_screen.selected_index if current_screen else 0
@selected_index.setter
def selected_index(self, value: int) -> None:
"""Set selected index on current screen (for compatibility)"""
current_screen = self.get_current_screen()
if current_screen:
current_screen.selected_index = value