diff --git a/__main__.py b/__main__.py index 34a8d94..d58a14c 100644 --- a/__main__.py +++ b/__main__.py @@ -4,16 +4,15 @@ import pygame # type: ignore import asyncio import platform import subprocess -import math import logging import requests import sys import json -from display import init_display, draw_loading_screen, draw_error_screen, draw_platform_grid, draw_progress_screen, draw_controls, draw_gradient, draw_virtual_keyboard, draw_popup_message, draw_extension_warning, draw_pause_menu, draw_controls_help, draw_game_list, draw_history_list, draw_clear_history_dialog, draw_confirm_dialog, draw_redownload_game_cache_dialog, draw_popup +from display import init_display, draw_loading_screen, draw_error_screen, draw_platform_grid, draw_progress_screen, draw_controls, draw_gradient, draw_virtual_keyboard, draw_popup_result_download, draw_extension_warning, draw_pause_menu, draw_controls_help, draw_game_list, draw_history_list, draw_clear_history_dialog, draw_confirm_dialog, draw_redownload_game_cache_dialog, draw_popup, THEME_COLORS from network import test_internet, download_rom, check_extension_before_download, extract_zip from controls import handle_controls, validate_menu_state from controls_mapper import load_controls_config, map_controls, draw_controls_mapping, ACTIONS -from utils import truncate_text_end, load_system_image, load_games +from utils import load_games from history import load_history import config @@ -77,17 +76,19 @@ clock = pygame.time.Clock() # Initialisation des polices try: - config.font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 48) - config.title_font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 60) - config.search_font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 60) + config.font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 36) + config.title_font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 48) + config.search_font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 48) + config.progress_font = pygame.font.SysFont("arial", 36) # Police pour l'affichage de la progression + config.small_font = pygame.font.Font("/userdata/roms/ports/RGSX/assets/Pixel-UniCode.ttf", 28) # Police pour les petits textes logger.debug("Police Pixel-UniCode chargée") except: - config.font = pygame.font.SysFont("arial", 48) - config.title_font = pygame.font.SysFont("arial", 60) - config.search_font = pygame.font.SysFont("arial", 60) + config.font = pygame.font.SysFont("arial", 48) # Police fallback + config.title_font = pygame.font.SysFont("arial", 60) # Police fallback pour les titres + config.search_font = pygame.font.SysFont("arial", 60) # Police fallback pour la recherche + config.progress_font = pygame.font.SysFont("arial", 36) # Police fallback pour l'affichage de la progression + config.small_font = pygame.font.SysFont("arial", 28) # Police fallback pour les petits textes logger.debug("Police Arial chargée") -config.progress_font = pygame.font.SysFont("arial", 36) -config.small_font = pygame.font.SysFont("arial", 24) # Mise à jour de la résolution dans config config.screen_width, config.screen_height = pygame.display.get_surface().get_size() @@ -436,7 +437,7 @@ async def main(): # Affichage if config.needs_redraw: - draw_gradient(screen, (28, 37, 38), (47, 59, 61)) + draw_gradient(screen, THEME_COLORS["background_top"], THEME_COLORS["background_bottom"]) if config.menu_state == "controls_mapping": draw_controls_mapping(screen, ACTIONS[0], None, False, 0.0) # logger.debug("Rendu initial de draw_controls_mapping") @@ -460,7 +461,7 @@ async def main(): draw_progress_screen(screen) # logger.debug("Rendu de draw_progress_screen") elif config.menu_state == "download_result": - draw_popup_message(screen, config.download_result_message, config.download_result_error) + draw_popup_result_download(screen, config.download_result_message, config.download_result_error) # logger.debug("Rendu de draw_popup_message") elif config.menu_state == "confirm_exit": draw_confirm_dialog(screen) @@ -660,26 +661,6 @@ async def main(): pygame.quit() logger.debug("Application terminée") -# Fonction pour vérifier si un événement correspond à une action -def is_input_matched(event, action_name): - if not config.controls_config.get(action_name): - return False - mapping = config.controls_config[action_name] - input_type = mapping["type"] - input_value = mapping["value"] - - if input_type == "key" and event.type == pygame.KEYDOWN: - return event.key == input_value - elif input_type == "button" and event.type == pygame.JOYBUTTONDOWN: - return event.button == input_value - elif input_type == "axis" and event.type == pygame.JOYAXISMOTION: - axis, direction = input_value - return event.axis == axis and abs(event.value) > 0.5 and (1 if event.value > 0 else -1) == direction - elif input_type == "hat" and event.type == pygame.JOYHATMOTION: - return event.value == input_value - elif input_type == "mouse" and event.type == pygame.MOUSEBUTTONDOWN: - return event.button == input_value - return False if platform.system() == "Emscripten": asyncio.ensure_future(main()) diff --git a/config.py b/config.py index 0039b22..fb58033 100644 --- a/config.py +++ b/config.py @@ -5,7 +5,7 @@ import logging logger = logging.getLogger(__name__) # Version actuelle de l'application -app_version = "1.8.0" +app_version = "1.9.0" # Variables d'état platforms = [] @@ -60,6 +60,9 @@ popup_message = "" # Message à afficher dans les popups popup_timer = 0 # Temps restant pour le popup en millisecondes (0 = inactif) last_frame_time = pygame.time.get_ticks() +GRID_COLS = 3 # Number of columns in the platform grid +GRID_ROWS = 4 # Number of rows in the platform grid + # Résolution de l'écran fallback # Utilisée si la résolution définie dépasse les capacités de l'écran SCREEN_WIDTH = 800 diff --git a/controls.py b/controls.py index 42d8c27..ed5729e 100644 --- a/controls.py +++ b/controls.py @@ -1,14 +1,13 @@ import shutil import pygame # type: ignore import config -from config import CONTROLS_CONFIG_PATH +from config import CONTROLS_CONFIG_PATH , GRID_COLS, GRID_ROWS import asyncio import math import json import os from display import draw_validation_transition from network import download_rom, check_extension_before_download, download_from_1fichier, is_1fichier_url, is_extension_supported,load_extensions_json,sanitize_filename -from controls_mapper import get_readable_input_name from utils import load_games from history import load_history, clear_history import logging @@ -22,7 +21,7 @@ JOYHAT_DEBOUNCE = 200 # Délai anti-rebond pour JOYHATMOTION (ms) JOYAXIS_DEBOUNCE = 50 # Délai anti-rebond pour JOYAXISMOTION (ms) REPEAT_ACTION_DEBOUNCE = 50 # Délai anti-rebond pour répétitions up/down/left/right (ms) -# Liste des états valides (mise à jour) +# Liste des états valides VALID_STATES = [ "platform", "game", "download_progress", "download_result", "confirm_exit", "extension_warning", "pause_menu", "controls_help", "history", "controls_mapping", @@ -46,17 +45,27 @@ def load_controls_config(path=CONTROLS_CONFIG_PATH): with open(path, "r") as f: config_data = json.load(f) # Vérifier les actions nécessaires - required_actions = ["confirm", "cancel", "left", "right"] + required_actions = ["confirm", "cancel", "up", "down"] for action in required_actions: if action not in config_data: logger.warning(f"Action {action} manquante dans {path}, utilisation de la valeur par défaut") config_data[action] = { "type": "key", "value": { - "confirm": pygame.K_RETURN, - "cancel": pygame.K_ESCAPE, - "left": pygame.K_LEFT, - "right": pygame.K_RIGHT + "confirm": {"type": "key", "value": pygame.K_RETURN}, + "cancel": {"type": "key", "value": pygame.K_ESCAPE}, + "left": {"type": "key", "value": pygame.K_LEFT}, + "right": {"type": "key", "value": pygame.K_RIGHT}, + "up": {"type": "key", "value": pygame.K_UP}, + "down": {"type": "key", "value": pygame.K_DOWN}, + "start": {"type": "key", "value": pygame.K_p}, + "progress": {"type": "key", "value": pygame.K_x}, + "history": {"type": "key", "value": pygame.K_h}, + "page_up": {"type": "key", "value": pygame.K_PAGEUP}, + "page_down": {"type": "key", "value": pygame.K_PAGEDOWN}, + "filter": {"type": "key", "value": pygame.K_f}, + "delete": {"type": "key", "value": pygame.K_BACKSPACE}, + "space": {"type": "key", "value": pygame.K_SPACE} }[action] } return config_data @@ -79,38 +88,25 @@ def load_controls_config(path=CONTROLS_CONFIG_PATH): "space": {"type": "key", "value": pygame.K_SPACE} } +# Fonction pour vérifier si un événement correspond à une action def is_input_matched(event, action_name): - """Vérifie si l'événement correspond à l'action configurée.""" if not config.controls_config.get(action_name): return False mapping = config.controls_config[action_name] input_type = mapping["type"] input_value = mapping["value"] - event_type = event["type"] if isinstance(event, dict) else event.type - event_key = event.get("key") if isinstance(event, dict) else getattr(event, "key", None) - event_button = event.get("button") if isinstance(event, dict) else getattr(event, "button", None) - event_axis = event.get("axis") if isinstance(event, dict) else getattr(event, "axis", None) - event_value = event.get("value") if isinstance(event, dict) else getattr(event, "value", None) - - if input_type == "key" and event_type in (pygame.KEYDOWN, pygame.KEYUP): - #logger.debug(f"Vérification key: event_key={event_key}, input_value={input_value}") - return event_key == input_value - elif input_type == "button" and event_type in (pygame.JOYBUTTONDOWN, pygame.JOYBUTTONUP): - #logger.debug(f"Vérification button: event_button={event_button}, input_value={input_value}") - return event_button == input_value - elif input_type == "axis" and event_type == pygame.JOYAXISMOTION: + if input_type == "key" and event.type == pygame.KEYDOWN: + return event.key == input_value + elif input_type == "button" and event.type == pygame.JOYBUTTONDOWN: + return event.button == input_value + elif input_type == "axis" and event.type == pygame.JOYAXISMOTION: axis, direction = input_value - result = event_axis == axis and abs(event_value) > 0.5 and (1 if event_value > 0 else -1) == direction - #logger.debug(f"Vérification axis: event_axis={event_axis}, event_value={event_value}, input_value={input_value}, result={result}") - return result - elif input_type == "hat" and event_type == pygame.JOYHATMOTION: - input_value_tuple = tuple(input_value) if isinstance(input_value, list) else input_value - #logger.debug(f"Vérification hat: event_value={event_value}, input_value={input_value_tuple}") - return event_value == input_value_tuple - elif input_type == "mouse" and event_type == pygame.MOUSEBUTTONDOWN: - #logger.debug(f"Vérification mouse: event_button={event_button}, input_value={input_value}") - return event_button == input_value + return event.axis == axis and abs(event.value) > 0.5 and (1 if event.value > 0 else -1) == direction + elif input_type == "hat" and event.type == pygame.JOYHATMOTION: + return event.value == input_value + elif input_type == "mouse" and event.type == pygame.MOUSEBUTTONDOWN: + return event.button == input_value return False def handle_controls(event, sources, joystick, screen): @@ -122,15 +118,11 @@ def handle_controls(event, sources, joystick, screen): # Valider previous_menu_state avant tout traitement config.previous_menu_state = validate_menu_state(config.previous_menu_state) - #logger.debug(f"Validation initiale: previous_menu_state={config.previous_menu_state}") # Debounce général if current_time - config.last_state_change_time < config.debounce_delay: return action - # Log des événements reçus - #logger.debug(f"Événement reçu: type={event.type}, value={getattr(event, 'value', None)}") - # --- CLAVIER, MANETTE, SOURIS --- if event.type in (pygame.KEYDOWN, pygame.JOYBUTTONDOWN, pygame.JOYAXISMOTION, pygame.JOYHATMOTION, pygame.MOUSEBUTTONDOWN): # Débouncer les événements JOYHATMOTION @@ -149,12 +141,7 @@ def handle_controls(event, sources, joystick, screen): if event.type == pygame.QUIT: logger.debug("Événement pygame.QUIT détecté") return "quit" - - # Vérification des actions mappées - #for action_name in ["up", "down", "left", "right"]: - #if is_input_matched(event, action_name): - #logger.debug(f"Action mappée détectée: {action_name}, input={get_readable_input_name(event)}") - + # Menu pause if is_input_matched(event, "start") and config.menu_state not in ("pause_menu", "controls_help", "controls_mapping", "redownload_game_cache"): config.previous_menu_state = config.menu_state @@ -170,30 +157,33 @@ def handle_controls(event, sources, joystick, screen): config.menu_state = validate_menu_state(config.previous_menu_state) config.needs_redraw = True logger.debug("Sortie du menu erreur avec Confirm") - - # Plateformes + + #Plateformes elif config.menu_state == "platform": - max_index = min(9, len(config.platforms) - config.current_page * 9) - 1 - current_grid_index = config.selected_platform - config.current_page * 9 - row = current_grid_index // 3 + systems_per_page = GRID_COLS * GRID_ROWS + max_index = min(systems_per_page, len(config.platforms) - config.current_page * systems_per_page) - 1 + current_grid_index = config.selected_platform - config.current_page * systems_per_page + row = current_grid_index // GRID_COLS + col = current_grid_index % GRID_COLS + if is_input_matched(event, "down"): - if current_grid_index + 3 <= max_index: - config.selected_platform += 3 + if current_grid_index + GRID_COLS <= max_index: + config.selected_platform += GRID_COLS config.repeat_action = "down" config.repeat_start_time = current_time + REPEAT_DELAY config.repeat_last_action = current_time config.repeat_key = event.key if event.type == pygame.KEYDOWN else event.button if event.type == pygame.JOYBUTTONDOWN else (event.axis, 1 if event.value > 0 else -1) if event.type == pygame.JOYAXISMOTION else event.value config.needs_redraw = True elif is_input_matched(event, "up"): - if current_grid_index - 3 >= 0: - config.selected_platform -= 3 + if current_grid_index - GRID_COLS >= 0: + config.selected_platform -= GRID_COLS config.repeat_action = "up" config.repeat_start_time = current_time + REPEAT_DELAY config.repeat_last_action = current_time config.repeat_key = event.key if event.type == pygame.KEYDOWN else event.button if event.type == pygame.JOYBUTTONDOWN else (event.axis, 1 if event.value > 0 else -1) if event.type == pygame.JOYAXISMOTION else event.value config.needs_redraw = True elif is_input_matched(event, "left"): - if current_grid_index % 3 != 0: + if col > 0: config.selected_platform -= 1 config.repeat_action = "left" config.repeat_start_time = current_time + REPEAT_DELAY @@ -202,7 +192,7 @@ def handle_controls(event, sources, joystick, screen): config.needs_redraw = True elif config.current_page > 0: config.current_page -= 1 - config.selected_platform = config.current_page * 9 + row * 3 + 2 + config.selected_platform = config.current_page * systems_per_page + row * GRID_COLS + (GRID_COLS - 1) if config.selected_platform >= len(config.platforms): config.selected_platform = len(config.platforms) - 1 config.repeat_action = "left" @@ -211,16 +201,16 @@ def handle_controls(event, sources, joystick, screen): config.repeat_key = event.key if event.type == pygame.KEYDOWN else event.button if event.type == pygame.JOYBUTTONDOWN else (event.axis, 1 if event.value > 0 else -1) if event.type == pygame.JOYAXISMOTION else event.value config.needs_redraw = True elif is_input_matched(event, "right"): - if current_grid_index % 3 != 2 and current_grid_index < max_index: + if col < GRID_COLS - 1 and current_grid_index < max_index: config.selected_platform += 1 config.repeat_action = "right" config.repeat_start_time = current_time + REPEAT_DELAY config.repeat_last_action = current_time config.repeat_key = event.key if event.type == pygame.KEYDOWN else event.button if event.type == pygame.JOYBUTTONDOWN else (event.axis, 1 if event.value > 0 else -1) if event.type == pygame.JOYAXISMOTION else event.value config.needs_redraw = True - elif (config.current_page + 1) * 9 < len(config.platforms): + elif (config.current_page + 1) * systems_per_page < len(config.platforms): config.current_page += 1 - config.selected_platform = config.current_page * 9 + row * 3 + config.selected_platform = config.current_page * systems_per_page + row * GRID_COLS if config.selected_platform >= len(config.platforms): config.selected_platform = len(config.platforms) - 1 config.repeat_action = "right" @@ -229,9 +219,9 @@ def handle_controls(event, sources, joystick, screen): config.repeat_key = event.key if event.type == pygame.KEYDOWN else event.button if event.type == pygame.JOYBUTTONDOWN else (event.axis, 1 if event.value > 0 else -1) if event.type == pygame.JOYAXISMOTION else event.value config.needs_redraw = True elif is_input_matched(event, "page_down"): - if (config.current_page + 1) * 9 < len(config.platforms): + if (config.current_page + 1) * systems_per_page < len(config.platforms): config.current_page += 1 - config.selected_platform = config.current_page * 9 + row * 3 + config.selected_platform = config.current_page * systems_per_page + row * GRID_COLS + col if config.selected_platform >= len(config.platforms): config.selected_platform = len(config.platforms) - 1 config.repeat_action = None @@ -243,7 +233,19 @@ def handle_controls(event, sources, joystick, screen): elif is_input_matched(event, "page_up"): if config.current_page > 0: config.current_page -= 1 - config.selected_platform = config.current_page * 9 + row * 3 + config.selected_platform = config.current_page * systems_per_page + row * GRID_COLS - col + if config.selected_platform >= len(config.platforms): + config.selected_platform = len(config.platforms) - 1 + config.repeat_action = None + config.repeat_key = None + config.repeat_start_time = 0 + config.repeat_last_action = current_time + config.needs_redraw = True + #logger.debug("Page précédente, répétition réinitialisée") + elif is_input_matched(event, "page_up"): + if config.current_page > 0: + config.current_page -= 1 + config.selected_platform = config.current_page * systems_per_page + row * GRID_COLS + col if config.selected_platform >= len(config.platforms): config.selected_platform = len(config.platforms) - 1 config.repeat_action = None diff --git a/display - Copie.py b/display - Copie.py new file mode 100644 index 0000000..a4f8a8d --- /dev/null +++ b/display - Copie.py @@ -0,0 +1,975 @@ +import pygame # type: ignore +import config +import math +from utils import truncate_text_middle, wrap_text, load_system_image, load_games +import logging +from history import load_history # Ajout de l'import + +logger = logging.getLogger(__name__) + +OVERLAY = None # Initialisé dans init_display() + +#Général, résolution, overlay +def init_display(): + """Initialise l'écran et les ressources globales.""" + global OVERLAY + logger.debug("Initialisation de l'écran") + display_info = pygame.display.Info() + screen_width = display_info.current_w + screen_height = display_info.current_h + screen = pygame.display.set_mode((screen_width, screen_height)) + config.screen_width = screen_width + config.screen_height = screen_height + # Initialisation de OVERLAY + OVERLAY = pygame.Surface((screen_width, screen_height), pygame.SRCALPHA) + OVERLAY.fill((0, 0, 0, 128)) # Semi-transparent (noir avec alpha 128) + logger.debug(f"Écran initialisé avec résolution : {screen_width}x{screen_height}") + return screen + +#Fond d'ecran dégradé +def draw_gradient(screen, top_color, bottom_color): + """Dessine un fond dégradé vertical.""" + height = screen.get_height() + top_color = pygame.Color(*top_color) + bottom_color = pygame.Color(*bottom_color) + for y in range(height): + ratio = y / height + color = top_color.lerp(bottom_color, ratio) + pygame.draw.line(screen, color, (0, y), (screen.get_width(), y)) + +#Transistion d'image lors de la selection d'un systeme +def draw_validation_transition(screen, platform_index): + """Affiche une animation de transition pour la sélection d’une plateforme.""" + platform_dict = config.platform_dicts[platform_index] + image = load_system_image(platform_dict) + if not image: + return + orig_width, orig_height = image.get_width(), image.get_height() + base_size = int(config.screen_width * 0.0781) # ~150px pour 1920p + start_time = pygame.time.get_ticks() + duration = 500 + while pygame.time.get_ticks() - start_time < duration: + draw_gradient(screen, (28, 37, 38), (47, 59, 61)) + elapsed = pygame.time.get_ticks() - start_time + scale = 2.0 + (2.0 * elapsed / duration) if elapsed < duration / 2 else 3.0 - (2.0 * elapsed / duration) + new_width = int(base_size * scale) + new_height = int(base_size * scale) + scaled_image = pygame.transform.smoothscale(image, (new_width, new_height)) + image_rect = scaled_image.get_rect(center=(config.screen_width // 2, config.screen_height // 2)) + screen.blit(scaled_image, image_rect) + pygame.display.flip() + pygame.time.wait(10) + +#Ecran de chargement +def draw_loading_screen(screen): + """Affiche l’écran de chargement avec le disclaimer en haut, le texte de chargement et la barre de progression.""" + disclaimer_lines = [ + "Bienvenue dans RGSX", + "It's dangerous to go alone, take all you need!", + "Mais ne téléchargez que des jeux", + "dont vous possédez les originaux !" + ] + + margin_horizontal = int(config.screen_width * 0.025) # 2.5% de la largeur + padding_vertical = int(config.screen_height * 0.0185) # ~20px pour 1080p + padding_between = int(config.screen_height * 0.0074) # ~8px pour 1080p + border_radius = 16 + border_width = 3 + shadow_offset = 6 + + line_height = config.small_font.get_height() + padding_between + total_height = line_height * len(disclaimer_lines) - padding_between + rect_width = config.screen_width - 2 * margin_horizontal + rect_height = total_height + 2 * padding_vertical + rect_x = margin_horizontal + rect_y = int(config.screen_height * 0.0185) # ~20px pour 1080p + + shadow_rect = pygame.Rect(rect_x + shadow_offset, rect_y + shadow_offset, rect_width, rect_height) + shadow_surface = pygame.Surface((rect_width, rect_height), pygame.SRCALPHA) + pygame.draw.rect(shadow_surface, (0, 0, 0, 100), shadow_surface.get_rect(), border_radius=border_radius) + screen.blit(shadow_surface, shadow_rect.topleft) + + disclaimer_rect = pygame.Rect(rect_x, rect_y, rect_width, rect_height) + disclaimer_surface = pygame.Surface((rect_width, rect_height), pygame.SRCALPHA) + pygame.draw.rect(disclaimer_surface, (30, 30, 30, 220), disclaimer_surface.get_rect(), border_radius=border_radius) + screen.blit(disclaimer_surface, disclaimer_rect.topleft) + + pygame.draw.rect(screen, (255, 255, 255), disclaimer_rect, border_width, border_radius=border_radius) + + max_text_width = rect_width - 2 * padding_vertical + for i, line in enumerate(disclaimer_lines): + wrapped_lines = wrap_text(line, config.small_font, max_text_width) + for j, wrapped_line in enumerate(wrapped_lines): + text_surface = config.small_font.render(wrapped_line, True, (255, 255, 255)) + text_rect = text_surface.get_rect(center=( + config.screen_width // 2, + rect_y + padding_vertical + (i * len(wrapped_lines) + j + 0.5) * line_height - padding_between // 2 + )) + screen.blit(text_surface, text_rect) + + loading_y = rect_y + rect_height + int(config.screen_height * 0.0926) # ~100px pour 1080p + text = config.small_font.render(truncate_text_middle(f"{config.current_loading_system}", config.small_font, config.screen_width - 2 * margin_horizontal), True, (255, 255, 255)) + text_rect = text.get_rect(center=(config.screen_width // 2, loading_y)) + screen.blit(text, text_rect) + + progress_text = config.small_font.render(f"Progression : {int(config.loading_progress)}%", True, (255, 255, 255)) + progress_rect = progress_text.get_rect(center=(config.screen_width // 2, loading_y + int(config.screen_height * 0.0463))) # ~50px pour 1080p + screen.blit(progress_text, progress_rect) + + bar_width = int(config.screen_width * 0.2083) # ~400px pour 1920p + bar_height = int(config.screen_height * 0.037) # ~40px pour 1080p + progress_width = (bar_width * config.loading_progress) / 100 + pygame.draw.rect(screen, (100, 100, 100), (config.screen_width // 2 - bar_width // 2, loading_y + int(config.screen_height * 0.0926), bar_width, bar_height)) + pygame.draw.rect(screen, (0, 255, 0), (config.screen_width // 2 - bar_width // 2, loading_y + int(config.screen_height * 0.0926), progress_width, bar_height)) + +#Ecran d'erreur +def draw_error_screen(screen): + """Affiche l’écran d’erreur.""" + error_font = pygame.font.SysFont("arial", 28) + wrapped_message = wrap_text(config.error_message, error_font, config.screen_width - 80) + line_height = error_font.get_height() + 5 + for i, line in enumerate(wrapped_message): + text = error_font.render(line, True, (255, 0, 0)) + text_rect = text.get_rect(center=(config.screen_width // 2, config.screen_height // 2 - (len(wrapped_message) // 2 - i) * line_height)) + screen.blit(text, text_rect) + # Afficher uniquement "Valider" + confirm_text = config.small_font.render("Valider", True, (0, 150, 255)) + confirm_rect = confirm_text.get_rect(center=(config.screen_width // 2, config.screen_height // 2 + int(config.screen_height * 0.0926))) + screen.blit(confirm_text, confirm_rect) + +#Recuperer les noms d'affichage des controles +def get_control_display(action, default): + """Récupère le nom d'affichage d'une action depuis controls_config.""" + if not config.controls_config: + logger.warning(f"controls_config vide pour l'action {action}, utilisation de la valeur par défaut") + return default + return config.controls_config.get(action, {}).get('display', default) + +#Grille des systemes 3x3 +def draw_platform_grid(screen): + """Affiche la grille des plateformes avec un titre en haut.""" + # Configuration du titre + if not config.platforms or config.selected_platform >= len(config.platforms): + platform_name = "Aucune plateforme" + logger.warning("Aucune plateforme ou selected_platform hors limites") + else: + platform = config.platforms[config.selected_platform] + platform_name = config.platform_names.get(platform, platform) + title_text = f"{platform_name}" + title_surface = config.title_font.render(title_text, True, (255, 255, 255)) + title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 10)) + title_rect_inflated = title_rect.inflate(40, 20) + title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 0) + + # Dessiner le rectangle de fond du titre + pygame.draw.rect(screen, (50, 50, 50, 200), title_rect_inflated, border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), title_rect_inflated, 2, border_radius=10) + screen.blit(title_surface, title_rect) + + # Configuration de la grille + margin_left = int(config.screen_width * 0.026) # ~50px pour 1920p + margin_right = int(config.screen_width * 0.026) + margin_top = int(config.screen_height * 0.140) # ~120px pour 1080p + margin_bottom = int(config.screen_height * 0.0648) # ~70px pour 1080p + num_cols = 3 + num_rows = 3 + systems_per_page = num_cols * num_rows + + available_width = config.screen_width - margin_left - margin_right + available_height = config.screen_height - margin_top - margin_bottom + + col_width = available_width // num_cols + row_height = available_height // num_rows + + x_positions = [margin_left + col_width * i + col_width // 2 for i in range(num_cols)] + y_positions = [margin_top + row_height * i + row_height // 2 for i in range(num_rows)] + + start_idx = config.current_page * systems_per_page + #logger.debug(f"Page {config.current_page}, start_idx: {start_idx}, total_platforms: {len(config.platforms)}") + + for idx in range(start_idx, start_idx + systems_per_page): + if idx >= len(config.platforms): + break + grid_idx = idx - start_idx + row = grid_idx // num_cols + col = grid_idx % num_cols + x = x_positions[col] + y = y_positions[row] + scale = 1.5 if idx == config.selected_platform else 1.0 + platform_dict = config.platform_dicts[idx] + image = load_system_image(platform_dict) + if image: + orig_width, orig_height = image.get_width(), image.get_height() + max_size = int(min(col_width, row_height) * scale * 0.9) + ratio = min(max_size / orig_width, max_size / orig_height) + new_width = int(orig_width * ratio) + new_height = int(orig_height * ratio) + image = pygame.transform.smoothscale(image, (new_width, new_height)) + image_rect = image.get_rect(center=(x, y)) + + if idx == config.selected_platform: + neon_color = (0, 255, 255) + border_radius = 24 + padding = 24 + rect_width = image_rect.width + 2 * padding + rect_height = image_rect.height + 2 * padding + neon_surface = pygame.Surface((rect_width, rect_height), pygame.SRCALPHA) + pygame.draw.rect( + neon_surface, + neon_color + (60,), + neon_surface.get_rect(), + width=1, + border_radius=border_radius + 8 + ) + pygame.draw.rect( + neon_surface, + neon_color + (180,), + neon_surface.get_rect().inflate(-8, -8), + width=2, + border_radius=border_radius + ) + screen.blit(neon_surface, (image_rect.left - padding, image_rect.top - padding), special_flags=pygame.BLEND_RGBA_ADD) + + screen.blit(image, image_rect) + +#Liste des jeux +def draw_game_list(screen): + """Affiche la liste des jeux avec défilement et rectangle de fond.""" + #logger.debug("Début de draw_game_list") + + platform = config.platforms[config.current_platform] + platform_name = config.platform_names.get(platform, platform) + games = config.filtered_games if config.filter_active or config.search_mode else config.games + game_count = len(games) + + if not games: + logger.debug("Aucune liste de jeux disponible") + message = "Aucun jeu disponible" + lines = wrap_text(message, config.font, config.screen_width - 80) + line_height = config.font.get_height() + 5 + text_height = len(lines) * line_height + margin_top_bottom = 20 + rect_height = text_height + 2 * margin_top_bottom + max_text_width = max([config.font.size(line)[0] for line in lines], default=300) + rect_width = max_text_width + 40 + rect_x = (config.screen_width - rect_width) // 2 + rect_y = (config.screen_height - rect_height) // 2 + + screen.blit(OVERLAY, (0, 0)) + pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + + for i, line in enumerate(lines): + text_surface = config.font.render(line, True, (255, 255, 255)) + text_rect = text_surface.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) + screen.blit(text_surface, text_rect) + return + + line_height = config.small_font.get_height() + 10 + #header_height = line_height + margin_top_bottom = 20 + extra_margin_top = 20 + extra_margin_bottom = 60 # Aligné sur draw_history_list + title_height = config.title_font.get_height() + 20 + + available_height = config.screen_height - title_height - extra_margin_top - extra_margin_bottom - 2 * margin_top_bottom + items_per_page = available_height // line_height + + rect_height = items_per_page * line_height + 2 * margin_top_bottom + rect_width = int(0.95 * config.screen_width) + rect_x = (config.screen_width - rect_width) // 2 + rect_y = title_height + extra_margin_top + (config.screen_height - title_height - extra_margin_top - extra_margin_bottom - rect_height) // 2 + + config.scroll_offset = max(0, min(config.scroll_offset, max(0, len(games) - items_per_page))) + if config.current_game < config.scroll_offset: + config.scroll_offset = config.current_game + elif config.current_game >= config.scroll_offset + items_per_page: + config.scroll_offset = config.current_game - items_per_page + 1 + + screen.blit(OVERLAY, (0, 0)) + + if config.search_mode: + search_text = f"Filtrer : {config.search_query}_" + title_surface = config.search_font.render(search_text, True, (255, 255, 255)) + title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 10)) + title_rect_inflated = title_rect.inflate(40, 20) + title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 0) + pygame.draw.rect(screen, (50, 50, 50, 200), title_rect_inflated, border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), title_rect_inflated, 2, border_radius=10) + screen.blit(title_surface, title_rect) + elif config.filter_active: + filter_text = f"Filtre actif : {config.search_query}" + title_surface = config.small_font.render(filter_text, True, (255, 255, 255)) + title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 10)) + title_rect_inflated = title_rect.inflate(40, 20) + title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 0) + pygame.draw.rect(screen, (50, 50, 50, 200), title_rect_inflated, border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), title_rect_inflated, 2, border_radius=10) + screen.blit(title_surface, title_rect) + else: + title_text = f"{platform_name} ({game_count} jeux)" + title_surface = config.title_font.render(title_text, True, (255, 255, 255)) + title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 10)) + title_rect_inflated = title_rect.inflate(40, 20) + title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 0) + pygame.draw.rect(screen, (50, 50, 50, 200), title_rect_inflated, border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), title_rect_inflated, 2, border_radius=10) + screen.blit(title_surface, title_rect) + + pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + + for i in range(config.scroll_offset, min(config.scroll_offset + items_per_page, len(games))): + game_name = games[i][0] if isinstance(games[i], (list, tuple)) else games[i] + color = (0, 150, 255) if i == config.current_game else (255, 255, 255) + game_text = truncate_text_middle(game_name, config.small_font, rect_width - 40) + text_surface = config.small_font.render(game_text, True, color) + text_rect = text_surface.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + (i - config.scroll_offset) * line_height + line_height // 2)) + screen.blit(text_surface, text_rect) + #logger.debug(f"Jeu affiché : texte={game_text}, position={text_rect}, selected={i == config.current_game}") + + # Afficher la barre de scroll si besoin + if len(games) > items_per_page: + try: + draw_game_scrollbar( + screen, + config.scroll_offset, + len(games), + items_per_page, + rect_x + rect_width - 10, + rect_y, + rect_height + ) + except NameError as e: + logger.error(f"Erreur : draw_game_scrollbar non défini: {str(e)}") + +#Barre de défilement des jeux +def draw_game_scrollbar(screen, scroll_offset, total_items, visible_items, x, y, height): + """Affiche la barre de défilement pour la liste des jeux.""" + if total_items <= visible_items: + return + game_area_height = height + scrollbar_height = game_area_height * (visible_items / total_items) + scrollbar_y = y + (game_area_height - scrollbar_height) * (scroll_offset / max(1, total_items - visible_items)) + pygame.draw.rect(screen, (255, 255, 255), (x, scrollbar_y, 15, scrollbar_height)) + +#Liste historique téléchargement + +def draw_history_list(screen): + """Affiche l'historique des téléchargements sous forme de tableau avec système, nom du jeu et état.""" + history = config.history if hasattr(config, 'history') else load_history() + history_count = len(history) + + # Définir les largeurs des colonnes (valeurs fixes pour simplifier, ajustez si nécessaire) + col_platform_width = int((0.95 * config.screen_width - 60) * 0.33) + col_game_width = int((0.95 * config.screen_width - 60) * 0.50) + col_status_width = int((0.95 * config.screen_width - 60) * 0.17) + rect_width = int(0.95 * config.screen_width) # 95% de la largeur de l'écran + + # Hauteur des lignes et en-tête + line_height = config.small_font.get_height() + 10 + header_height = line_height + margin_top_bottom = 20 + extra_margin_top = 20 + extra_margin_bottom = 60 + title_height = config.title_font.get_height() + 20 + + # Cas où l'historique est vide + if not history: + logger.debug("Aucun historique disponible") + message = "Aucun téléchargement dans l'historique" + lines = wrap_text(message, config.font, config.screen_width - 80) + line_height = config.font.get_height() + 5 + text_height = len(lines) * line_height + rect_height = text_height + 2 * margin_top_bottom + max_text_width = max([config.font.size(line)[0] for line in lines], default=300) + rect_width = max_text_width + 40 + rect_x = (config.screen_width - rect_width) // 2 + rect_y = (config.screen_height - rect_height) // 2 + + screen.blit(OVERLAY, (0, 0)) + pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + + for i, line in enumerate(lines): + text_surface = config.font.render(line, True, (255, 255, 255)) + text_rect = text_surface.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) + screen.blit(text_surface, text_rect) + return + + # Calculer la hauteur disponible et les éléments par page + available_height = config.screen_height - title_height - extra_margin_top - extra_margin_bottom - 2 * margin_top_bottom + items_per_page = available_height // line_height + + # Calculer les dimensions du rectangle + rect_height = header_height + items_per_page * line_height + 2 * margin_top_bottom + rect_x = (config.screen_width - rect_width) // 2 + rect_y = title_height + extra_margin_top + (config.screen_height - title_height - extra_margin_top - extra_margin_bottom - rect_height) // 2 + + # Gestion du défilement + config.history_scroll_offset = max(0, min(config.history_scroll_offset, max(0, len(history) - items_per_page))) + if config.current_history_item < config.history_scroll_offset: + config.history_scroll_offset = config.current_history_item + elif config.current_history_item >= config.history_scroll_offset + items_per_page: + config.history_scroll_offset = config.current_history_item - items_per_page + 1 + + # Fond et cadre + screen.blit(OVERLAY, (0, 0)) + + # Titre + title_text = f"Historique des téléchargements ({history_count})" + title_surface = config.title_font.render(title_text, True, (255, 255, 255)) + title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 10)) + title_rect_inflated = title_rect.inflate(40, 20) + title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 0) + pygame.draw.rect(screen, (50, 50, 50, 200), title_rect_inflated, border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), title_rect_inflated, 2, border_radius=10) + screen.blit(title_surface, title_rect) + + # Cadre du tableau + pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + + # En-têtes du tableau + headers = ["Système", "Nom du jeu", "État"] + header_y = rect_y + margin_top_bottom + header_height // 2 + header_x_positions = [ + rect_x + 20 + col_platform_width // 2, + rect_x + 20 + col_platform_width + col_game_width // 2, + rect_x + 20 + col_platform_width + col_game_width + col_status_width // 2 + ] + for header, x_pos in zip(headers, header_x_positions): + text_surface = config.small_font.render(header, True, (255, 255, 255)) + text_rect = text_surface.get_rect(center=(x_pos, header_y)) + screen.blit(text_surface, text_rect) + + # Afficher les entrées de l'historique + for idx, i in enumerate(range(config.history_scroll_offset, min(config.history_scroll_offset + items_per_page, len(history)))): + entry = history[i] + platform = entry.get("platform", "Inconnu") + game_name = entry.get("game_name", "Inconnu") + status = entry.get("status", "Inconnu") + color = (0, 150, 255) if i == config.current_history_item else (255, 255, 255) + platform_text = truncate_text_middle(platform, config.small_font, col_platform_width - 10) + game_text = truncate_text_middle(game_name, config.small_font, col_game_width - 10) + status_text = truncate_text_middle(status, config.small_font, col_status_width - 10) + + y_pos = rect_y + margin_top_bottom + header_height + idx * line_height + line_height // 2 + platform_surface = config.small_font.render(platform_text, True, color) + game_surface = config.small_font.render(game_text, True, color) + status_surface = config.small_font.render(status_text, True, color) + + platform_rect = platform_surface.get_rect(center=(header_x_positions[0], y_pos)) + game_rect = game_surface.get_rect(center=(header_x_positions[1], y_pos)) + status_rect = status_surface.get_rect(center=(header_x_positions[2], y_pos)) + + screen.blit(platform_surface, platform_rect) + screen.blit(game_surface, game_rect) + screen.blit(status_surface, status_rect) + #logger.debug(f"Entrée historique affichée : index={i}, platform={platform_text}, game={game_text}, status={status_text}, selected={i == config.current_history_item}") + + # Scrollbar + if len(history) > items_per_page: + try: + draw_history_scrollbar( + screen, + config.history_scroll_offset, + len(history), + items_per_page, + rect_x + rect_width - 10, + rect_y, + rect_height + ) + except NameError as e: + logger.error(f"Erreur : draw_history_scrollbar non défini: {str(e)}") + +#Barre de défilement de l'historique +def draw_history_scrollbar(screen, scroll_offset, total_items, visible_items, x, y, height): + """Affiche la barre de défilement à droite de l’écran.""" + if len(config.filtered_games) <= config.visible_games: + return + + game_area_height = config.screen_height - 150 + scrollbar_height = game_area_height * (config.visible_games / len(config.filtered_games)) + scrollbar_y = 120 + (game_area_height - scrollbar_height) * (scroll_offset / max(1, len(config.filtered_games) - config.visible_games)) + pygame.draw.rect(screen, (255, 255, 255), (config.screen_width - 25, scrollbar_y, 15, scrollbar_height)) + +#Ecran confirmation vider historique +def draw_clear_history_dialog(screen): + """Affiche la boîte de dialogue de confirmation pour vider l'historique.""" + screen.blit(OVERLAY, (0, 0)) + + message = "Vider l'historique ?" + wrapped_message = wrap_text(message, config.font, config.screen_width - 80) + line_height = config.font.get_height() + 5 + text_height = len(wrapped_message) * line_height + button_height = line_height + 20 + margin_top_bottom = 20 + rect_height = text_height + button_height + 2 * margin_top_bottom + max_text_width = max([config.font.size(line)[0] for line in wrapped_message], default=300) + rect_width = max_text_width + 40 + rect_x = (config.screen_width - rect_width) // 2 + rect_y = (config.screen_height - rect_height) // 2 + + pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + + for i, line in enumerate(wrapped_message): + text = config.font.render(line, True, (255, 255, 255)) + text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) + screen.blit(text, text_rect) + + yes_text = config.font.render("Oui", True, (0, 150, 255) if config.confirm_clear_selection == 1 else (255, 255, 255)) + no_text = config.font.render("Non", True, (0, 150, 255) if config.confirm_clear_selection == 0 else (255, 255, 255)) + yes_rect = yes_text.get_rect(center=(config.screen_width // 2 - 100, rect_y + text_height + margin_top_bottom + line_height // 2)) + no_rect = no_text.get_rect(center=(config.screen_width // 2 + 100, rect_y + text_height + margin_top_bottom + line_height // 2)) + + screen.blit(yes_text, yes_rect) + screen.blit(no_text, no_rect) + +#Affichage du clavier virtuel sur non pc +def draw_virtual_keyboard(screen): + """Affiche un clavier virtuel pour la saisie dans search_mode, centré verticalement.""" + keyboard_layout = [ + ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + ['A', 'Z', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'], + ['Q', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M'], + ['W', 'X', 'C', 'V', 'B', 'N'] + ] + key_width = int(config.screen_width * 0.03125) # ~60px pour 1920p + key_height = int(config.screen_height * 0.0556) # ~60px pour 1080p + key_spacing = int(config.screen_width * 0.0052) # ~10px pour 1920p + keyboard_width = len(keyboard_layout[0]) * (key_width + key_spacing) - key_spacing + keyboard_height = len(keyboard_layout) * (key_height + key_spacing) - key_spacing + start_x = (config.screen_width - keyboard_width) // 2 + search_bottom_y = int(config.screen_height * 0.111) + (config.search_font.get_height() + 40) // 2 # ~120px pour 1080p + controls_y = config.screen_height - int(config.screen_height * 0.037) # ~40px pour 1080p + available_height = controls_y - search_bottom_y + start_y = search_bottom_y + (available_height - keyboard_height - 40) // 2 + + keyboard_rect = pygame.Rect(start_x - 20, start_y - 20, keyboard_width + 40, keyboard_height + 40) + pygame.draw.rect(screen, (50, 50, 50, 200), keyboard_rect, border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), keyboard_rect, 2, border_radius=10) + + for row_idx, row in enumerate(keyboard_layout): + for col_idx, key in enumerate(row): + x = start_x + col_idx * (key_width + key_spacing) + y = start_y + row_idx * (key_height + key_spacing) + key_rect = pygame.Rect(x, y, key_width, key_height) + if (row_idx, col_idx) == config.selected_key: + pygame.draw.rect(screen, (0, 150, 255, 150), key_rect, border_radius=5) + else: + pygame.draw.rect(screen, (80, 80, 80, 255), key_rect, border_radius=5) + pygame.draw.rect(screen, (255, 255, 255), key_rect, 1, border_radius=5) + text = config.font.render(key, True, (255, 255, 255)) + text_rect = text.get_rect(center=key_rect.center) + screen.blit(text, text_rect) + +#Ecran de progression de téléchargement/extraction +def draw_progress_screen(screen): + """Affiche l'écran de progression des téléchargements avec taille en Mo.""" + #logger.debug("Début de draw_progress_screen") + + if not config.download_tasks: + logger.debug("Aucune tâche de téléchargement active") + return + + task = list(config.download_tasks.keys())[0] + game_name = config.download_tasks[task][2] + url = config.download_tasks[task][1] + progress = config.download_progress.get(url, {"downloaded_size": 0, "total_size": 0, "status": "Téléchargement", "progress_percent": 0}) + status = progress.get("status", "Téléchargement") + downloaded_size = progress["downloaded_size"] + total_size = progress["total_size"] + progress_percent = progress["progress_percent"] + #logger.debug(f"Progression : game_name={game_name}, url={url}, status={status}, progress_percent={progress_percent}, downloaded_size={downloaded_size}, total_size={total_size}") + + screen.blit(OVERLAY, (0, 0)) + + title_text = f"{status} : {truncate_text_middle(game_name, config.font, config.screen_width - 200)}" + title_lines = wrap_text(title_text, config.font, config.screen_width - 80) + line_height = config.font.get_height() + 5 + text_height = len(title_lines) * line_height + margin_top_bottom = 20 + bar_height = int(config.screen_height * 0.0278) # ~30px pour 1080p + percent_height = line_height + rect_height = text_height + bar_height + percent_height + 3 * margin_top_bottom + max_text_width = max([config.font.size(line)[0] for line in title_lines], default=300) + bar_width = max_text_width + rect_width = max_text_width + 40 + rect_x = (config.screen_width - rect_width) // 2 + rect_y = (config.screen_height - rect_height) // 2 + + pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + + for i, line in enumerate(title_lines): + title_render = config.font.render(line, True, (255, 255, 255)) + title_rect = title_render.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) + screen.blit(title_render, title_rect) + #logger.debug(f"Titre affiché : texte={line}, position={title_rect}, taille={title_render.get_size()}") + + bar_y = rect_y + text_height + margin_top_bottom + progress_width = 0 + pygame.draw.rect(screen, (100, 100, 100), (rect_x + 20, bar_y, bar_width, bar_height)) + if total_size > 0: + progress_width = int(bar_width * (progress_percent / 100)) + pygame.draw.rect(screen, (0, 150, 255), (rect_x + 20, bar_y, progress_width, bar_height)) + pygame.draw.rect(screen, (255, 255, 255), (rect_x + 20, bar_y, bar_width, bar_height), 2) + #logger.debug(f"Barre de progression affichée : position=({rect_x + 20}, {bar_y}), taille=({bar_width}, {bar_height}), progress_width={progress_width}") + + downloaded_mb = downloaded_size / (1024 * 1024) + total_mb = total_size / (1024 * 1024) + size_text = f"{downloaded_mb:.1f} Mo / {total_mb:.1f} Mo" + percent_text = f"{int(progress_percent)}% {size_text}" + percent_lines = wrap_text(percent_text, config.font, config.screen_width - 80) + text_y = bar_y + bar_height + margin_top_bottom + for i, line in enumerate(percent_lines): + percent_render = config.font.render(line, True, (255, 255, 255)) + percent_rect = percent_render.get_rect(center=(config.screen_width // 2, text_y + i * line_height + line_height // 2)) + screen.blit(percent_render, percent_rect) + +#Ecran popup resultat téléchargement +def draw_popup_message(screen, message, is_error): + """Affiche une popup avec un message de résultat.""" + screen.blit(OVERLAY, (0, 0)) + if message is None: + message = "Téléchargement annulé" + logger.debug(f"Message popup : {message}, is_error={is_error}") + wrapped_message = wrap_text(message, config.small_font, config.screen_width - 80) + line_height = config.small_font.get_height() + 5 + for i, line in enumerate(wrapped_message): + text = config.small_font.render(line, True, (255, 0, 0) if is_error else (0, 255, 0)) + text_rect = text.get_rect(center=(config.screen_width // 2, config.screen_height // 2 - (len(wrapped_message) // 2 - i) * line_height)) + screen.blit(text, text_rect) + +#Ecran avertissement extension non supportée téléchargement +def draw_extension_warning(screen): + """Affiche un avertissement pour une extension non reconnue ou un fichier ZIP.""" + #logger.debug("Début de draw_extension_warning") + + if not config.pending_download: + logger.error("config.pending_download est None ou vide dans extension_warning") + message = "Erreur : Aucun téléchargement en attente." + is_zip = False + game_name = "Inconnu" + else: + url, platform, game_name, is_zip_non_supported = config.pending_download + logger.debug(f"config.pending_download: url={url}, platform={platform}, game_name={game_name}, is_zip_non_supported={is_zip_non_supported}") + is_zip = is_zip_non_supported + if not game_name: + game_name = "Inconnu" + logger.warning("game_name vide, utilisation de 'Inconnu'") + + if is_zip: + message = f"Le fichier '{game_name}' est une archive et Batocera ne prend pas en charge les archives pour ce système. L'extraction automatique du fichier aura lieu après le téléchargement, continuer ?" + else: + message = f"L'extension du fichier '{game_name}' n'est pas supportée par Batocera d'après le fichier info.txt. Voulez-vous continuer ?" + + max_width = config.screen_width - 80 + lines = wrap_text(message, config.font, max_width) + logger.debug(f"Lignes générées : {lines}") + + try: + line_height = config.font.get_height() + 5 + text_height = len(lines) * line_height + button_height = line_height + 20 + margin_top_bottom = 20 + rect_height = text_height + button_height + 2 * margin_top_bottom + max_text_width = max([config.font.size(line)[0] for line in lines], default=300) + rect_width = max_text_width + 40 + rect_x = (config.screen_width - rect_width) // 2 + rect_y = (config.screen_height - rect_height) // 2 + + screen.blit(OVERLAY, (0, 0)) + pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + + for i, line in enumerate(lines): + text_surface = config.font.render(line, True, (255, 255, 255)) + text_rect = text_surface.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) + screen.blit(text_surface, text_rect) + #logger.debug(f"Lignes affichées : {[(rect.center, text_surface.get_size()) for rect, text_surface in zip([text_surface.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) for i in range(len(lines))], [config.font.render(line, True, (255, 255, 255)) for line in lines])]}") + + yes_text = "[Oui]" if config.extension_confirm_selection == 1 else "Oui" + no_text = "[Non]" if config.extension_confirm_selection == 0 else "Non" + yes_surface = config.font.render(yes_text, True, (0, 150, 255) if config.extension_confirm_selection == 1 else (255, 255, 255)) + no_surface = config.font.render(no_text, True, (0, 150, 255) if config.extension_confirm_selection == 0 else (255, 255, 255)) + + button_y = rect_y + text_height + margin_top_bottom + line_height // 2 + yes_rect = yes_surface.get_rect(center=(config.screen_width // 2 - 100, button_y)) + no_rect = no_surface.get_rect(center=(config.screen_width // 2 + 100, button_y)) + + screen.blit(yes_surface, yes_rect) + screen.blit(no_surface, no_rect) + #logger.debug(f"Boutons affichés : Oui={yes_rect}, Non={no_rect}, selection={config.extension_confirm_selection}") + + except Exception as e: + logger.error(f"Erreur lors du rendu de extension_warning : {str(e)}") + error_message = "Erreur d'affichage de l'avertissement." + wrapped_error = wrap_text(error_message, config.font, config.screen_width - 80) + line_height = config.font.get_height() + 5 + rect_height = len(wrapped_error) * line_height + 2 * 20 + max_text_width = max([config.font.size(line)[0] for line in wrapped_error], default=300) + rect_width = max_text_width + 40 + rect_x = (config.screen_width - rect_width) // 2 + rect_y = (config.screen_height - rect_height) // 2 + + screen.blit(OVERLAY, (0, 0)) + pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + + for i, line in enumerate(wrapped_error): + error_surface = config.font.render(line, True, (255, 0, 0)) + error_rect = error_surface.get_rect(center=(config.screen_width // 2, rect_y + 20 + i * line_height + line_height // 2)) + screen.blit(error_surface, error_rect) + +#Affichage des controles en bas de page +def draw_controls(screen, menu_state): + """Affiche les contrôles sur une seule ligne en bas de l’écran pour tous les états du menu.""" + start_button = get_control_display('start', 'START') + control_text = f"{start_button} : Options - History - Controls" + max_width = config.screen_width - 40 + wrapped_controls = wrap_text(control_text, config.small_font, max_width) + line_height = config.small_font.get_height() + 5 + rect_height = len(wrapped_controls) * line_height + 20 + rect_y = config.screen_height - rect_height - 5 + + for i, line in enumerate(wrapped_controls): + text_surface = config.small_font.render(line, True, (255, 255, 255)) + text_rect = text_surface.get_rect(center=(config.screen_width // 2, rect_y + 10 + i * line_height + line_height // 2)) + screen.blit(text_surface, text_rect) + +#Menu pause +def draw_pause_menu(screen, selected_option): + """Dessine le menu pause avec les options Aide, Configurer contrôles, Historique, Redownload game cache, Quitter.""" + screen.blit(OVERLAY, (0, 0)) + + options = [ + "Controls", + "Remap controls", + "History", + "Redownload Games cache", + "Quit" + ] + + menu_width = int(config.screen_width * 0.8) # ~400px pour 1920p + line_height = config.font.get_height() + 10 + text_height = len(options) * line_height + margin_top_bottom = 20 + menu_height = text_height + 2 * margin_top_bottom + menu_x = (config.screen_width - menu_width) // 2 + menu_y = (config.screen_height - menu_height) // 2 + + pygame.draw.rect(screen, (50, 50, 50, 200), (menu_x, menu_y, menu_width, menu_height), border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), (menu_x, menu_y, menu_width, menu_height), 2, border_radius=10) + + for i, option in enumerate(options): + color = (0, 150, 255) if i == selected_option else (255, 255, 255) + text_surface = config.font.render(option, True, color) + text_rect = text_surface.get_rect(center=(config.screen_width // 2, menu_y + margin_top_bottom + i * line_height + line_height // 2)) + screen.blit(text_surface, text_rect) + +#Menu aide controles +def draw_controls_help(screen, previous_state): + """Affiche la liste des contrôles pour l'état précédent du menu.""" + common_controls = { + "confirm": lambda action: f"{get_control_display('confirm', 'Entrée/A')} : {action}", + "cancel": lambda action: f"{get_control_display('cancel', 'Échap/B')} : {action}", + "start": lambda: f"{get_control_display('start', 'Start')} : Menu", + "progress": lambda action: f"{get_control_display('progress', 'X')} : {action}", + "up": lambda action: f"{get_control_display('up', 'Flèche Haut')} : {action}", + "down": lambda action: f"{get_control_display('down', 'Flèche Bas')} : {action}", + "page_up": lambda action: f"{get_control_display('page_up', 'Q/LB')} : {action}", + "page_down": lambda action: f"{get_control_display('page_down', 'E/RB')} : {action}", + "filter": lambda action: f"{get_control_display('filter', 'Select')} : {action}", + "history": lambda action: f"{get_control_display('history', 'H')} : {action}", + "delete": lambda: f"{get_control_display('delete', 'Retour Arrière')} : Supprimer", + "space": lambda: f"{get_control_display('space', 'Espace')} : Espace" + } + + state_controls = { + "error": [ + common_controls["confirm"]("Retenter"), + common_controls["cancel"]("Quitter") + ], + "platform": [ + common_controls["confirm"]("Sélectionner"), + common_controls["cancel"]("Quitter"), + common_controls["start"](), + common_controls["history"]("Historique"), + *( [common_controls["progress"]("Progression")] if config.download_tasks else []) + ], + "game": [ + common_controls["confirm"](f"{'Selectionner' if config.search_mode else 'Télécharger'}"), + common_controls["filter"]("Filtrer"), + common_controls["cancel"](f"{'Annuler' if config.search_mode else 'Retour'}"), + common_controls["history"]("Historique"), + *( [ + common_controls["delete"](), + common_controls["space"]() + ] if config.search_mode and config.is_non_pc else []), + *( [ + f"{common_controls['up']('Naviguer')} / {common_controls['down']('Naviguer')}", + f"{common_controls['page_up']('Page')} / {common_controls['page_down']('Page')}", + common_controls["filter"]("Filtrer") + ] if not config.is_non_pc or not config.search_mode else []), + common_controls["start"](), + *( [common_controls["progress"]("Progression")] if config.download_tasks and not config.search_mode else []) + ], + "download_progress": [ + common_controls["cancel"]("Annuler le téléchargement"), + common_controls["progress"]("Arrière plan"), + common_controls["start"]() + ], + "download_result": [ + common_controls["confirm"]("Retour") + ], + "confirm_exit": [ + common_controls["confirm"]("Confirmer") + ], + "extension_warning": [ + common_controls["confirm"]("Confirmer") + ], + "history": [ + common_controls["confirm"]("Retélécharger"), + common_controls["cancel"]("Retour"), + common_controls["progress"]("Vider l'historique"), + f"{common_controls['up']('Naviguer')} / {common_controls['down']('Naviguer')}", + f"{common_controls['page_up']('Page')} / {common_controls['page_down']('Page')}", + common_controls["start"]() + ] + } + + controls = state_controls.get(previous_state, []) + if not controls: + return + + screen.blit(OVERLAY, (0, 0)) + + max_width = config.screen_width - 80 + wrapped_controls = [] + current_line = "" + for control in controls: + test_line = f"{current_line} | {control}" if current_line else control + if config.font.size(test_line)[0] <= max_width: + current_line = test_line + else: + wrapped_controls.append(current_line) + current_line = control + if current_line: + wrapped_controls.append(current_line) + + line_height = config.font.get_height() + 10 + popup_width = max_width + 40 + popup_height = len(wrapped_controls) * line_height + 60 + popup_x = (config.screen_width - popup_width) // 2 + popup_y = (config.screen_height - popup_height) // 2 + + for i, line in enumerate(wrapped_controls): + text = config.font.render(line, True, (255, 255, 255)) + text_rect = text.get_rect(center=(config.screen_width // 2, popup_y + 40 + i * line_height)) + screen.blit(text, text_rect) + + +#Menu Quitter Appli +def draw_confirm_dialog(screen): + """Affiche la boîte de dialogue de confirmation pour quitter.""" + global OVERLAY + #logger.debug("Rendu de draw_confirm_dialog") + # Vérifier si OVERLAY est valide, sinon le recréer + if OVERLAY is None or OVERLAY.get_size() != (config.screen_width, config.screen_height): + OVERLAY = pygame.Surface((config.screen_width, config.screen_height), pygame.SRCALPHA) + OVERLAY.fill((0, 0, 0, 128)) + logger.debug("OVERLAY recréé dans draw_confirm_dialog") + + screen.blit(OVERLAY, (0, 0)) + message = "Quitter l'application ?" + wrapped_message = wrap_text(message, config.font, config.screen_width - 80) + line_height = config.font.get_height() + 5 + text_height = len(wrapped_message) * line_height + button_height = line_height + 20 + margin_top_bottom = 20 + rect_height = text_height + button_height + 2 * margin_top_bottom + max_text_width = max([config.font.size(line)[0] for line in wrapped_message], default=300) + rect_width = max_text_width + 40 + rect_x = (config.screen_width - rect_width) // 2 + rect_y = (config.screen_height - rect_height) // 2 + pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + for i, line in enumerate(wrapped_message): + text = config.font.render(line, True, (255, 255, 255)) + text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) + screen.blit(text, text_rect) + yes_text = config.font.render("Oui", True, (0, 150, 255) if config.confirm_selection == 1 else (255, 255, 255)) + no_text = config.font.render("Non", True, (0, 150, 255) if config.confirm_selection == 0 else (255, 255, 255)) + yes_rect = yes_text.get_rect(center=(config.screen_width // 2 - 100, rect_y + text_height + margin_top_bottom + line_height // 2)) + no_rect = no_text.get_rect(center=(config.screen_width // 2 + 100, rect_y + text_height + margin_top_bottom + line_height // 2)) + screen.blit(yes_text, yes_rect) + screen.blit(no_text, no_rect) + +#draw_redownload_game_cache_dialog +def draw_redownload_game_cache_dialog(screen): + """Affiche la boîte de dialogue de confirmation pour retélécharger le cache des jeux.""" + global OVERLAY + #logger.debug("Rendu de draw_redownload_game_cache_dialog") + # Vérifier si OVERLAY est valide, sinon le recréer + if OVERLAY is None or OVERLAY.get_size() != (config.screen_width, config.screen_height): + OVERLAY = pygame.Surface((config.screen_width, config.screen_height), pygame.SRCALPHA) + OVERLAY.fill((0, 0, 0, 128)) + logger.debug("OVERLAY recréé dans draw_redownload_game_cache_dialog") + + screen.blit(OVERLAY, (0, 0)) + message = "Retélécharger le cache des jeux ?" + wrapped_message = wrap_text(message, config.small_font, config.screen_width - 80) + line_height = config.small_font.get_height() + 5 + text_height = len(wrapped_message) * line_height + button_height = line_height + 20 + margin_top_bottom = 20 + rect_height = text_height + button_height + 2 * margin_top_bottom + max_text_width = max([config.small_font.size(line)[0] for line in wrapped_message], default=300) + rect_width = max_text_width + 40 + rect_x = (config.screen_width - rect_width) // 2 + rect_y = (config.screen_height - rect_height) // 2 + pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + + for i, line in enumerate(wrapped_message): + text = config.small_font.render(line, True, (255, 255, 255)) + text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) + screen.blit(text, text_rect) + + yes_text = config.small_font.render("Oui", True, (0, 150, 255) if config.redownload_confirm_selection == 1 else (255, 255, 255)) + no_text = config.small_font.render("Non", True, (0, 150, 255) if config.redownload_confirm_selection == 0 else (255, 255, 255)) + yes_rect = yes_text.get_rect(center=(config.screen_width // 2 - 100, rect_y + text_height + margin_top_bottom + line_height // 2)) + no_rect = no_text.get_rect(center=(config.screen_width // 2 + 100, rect_y + text_height + margin_top_bottom + line_height // 2)) + screen.blit(yes_text, yes_rect) + screen.blit(no_text, no_rect) + +def draw_popup(screen): + """Dessine un popup avec un message et un compte à rebours.""" + screen.blit(OVERLAY, (0, 0)) + + popup_width = int(config.screen_width * 0.8) # ~400px pour 1920p + line_height = config.small_font.get_height() + 10 + text_lines = config.popup_message.split('\n') + text_height = len(text_lines) * line_height + margin_top_bottom = 20 + popup_height = text_height + 2 * margin_top_bottom + line_height # Espace pour le compte à rebours + popup_x = (config.screen_width - popup_width) // 2 + popup_y = (config.screen_height - popup_height) // 2 + + pygame.draw.rect(screen, (50, 50, 50, 200), (popup_x, popup_y, popup_width, popup_height), border_radius=10) + pygame.draw.rect(screen, (255, 255, 255), (popup_x, popup_y, popup_width, popup_height), 2, border_radius=10) + + for i, line in enumerate(text_lines): + text_surface = config.small_font.render(line, True, (255, 255, 255)) + text_rect = text_surface.get_rect(center=(config.screen_width // 2, popup_y + margin_top_bottom + i * line_height + line_height // 2)) + screen.blit(text_surface, text_rect) + + # Afficher le compte à rebours + remaining_time = max(0, config.popup_timer // 1000) # Convertir ms en secondes + countdown_text = f"Ce message se fermera dans {remaining_time} seconde{'s' if remaining_time != 1 else ''}" + countdown_surface = config.small_font.render(countdown_text, True, (255, 255, 255)) + countdown_rect = countdown_surface.get_rect(center=(config.screen_width // 2, popup_y + margin_top_bottom + len(text_lines) * line_height + line_height // 2)) + screen.blit(countdown_surface, countdown_rect) \ No newline at end of file diff --git a/display.py b/display.py index a4f8a8d..318839d 100644 --- a/display.py +++ b/display.py @@ -1,15 +1,42 @@ -import pygame # type: ignore +import pygame # type: ignore import config -import math -from utils import truncate_text_middle, wrap_text, load_system_image, load_games +from utils import truncate_text_middle, wrap_text, load_system_image import logging +import math from history import load_history # Ajout de l'import logger = logging.getLogger(__name__) OVERLAY = None # Initialisé dans init_display() -#Général, résolution, overlay +# Couleurs modernes pour le thème +THEME_COLORS = { + # Fond des lignes sélectionnées + "fond_lignes": (0, 255, 0), # vert + # Fond par défaut des images de grille des systèmes + "fond_image": (50, 50, 70), # Bleu sombre métal + # Néon image grille des systèmes + "neon": (0, 134, 179), # bleu + # Dégradé sombre pour le fond + "background_top": (30, 40, 50), + "background_bottom": (60, 80, 100), # noir vers bleu foncé + # Fond des cadres + "button_idle": (50, 50, 70, 150), # Bleu sombre métal + # Fond des boutons sélectionnés dans les popups ou menu + "button_hover": (255, 0, 255, 220), # Rose + # Générique + "text": (255, 255, 255), # blanc + # Erreur + "error_text": (255, 0, 0), # rouge + # Avertissement + "warning_text": (255, 100, 0), # orange + # Titres + "title_text": (200, 200, 200), # gris clair + # Bordures + "border": (150, 150, 150), # Bordures grises subtiles +} + +# Général, résolution, overlay def init_display(): """Initialise l'écran et les ressources globales.""" global OVERLAY @@ -22,13 +49,13 @@ def init_display(): config.screen_height = screen_height # Initialisation de OVERLAY OVERLAY = pygame.Surface((screen_width, screen_height), pygame.SRCALPHA) - OVERLAY.fill((0, 0, 0, 128)) # Semi-transparent (noir avec alpha 128) + OVERLAY.fill((0, 0, 0, 150)) # Transparence augmentée logger.debug(f"Écran initialisé avec résolution : {screen_width}x{screen_height}") return screen -#Fond d'ecran dégradé +# Fond d'écran dégradé def draw_gradient(screen, top_color, bottom_color): - """Dessine un fond dégradé vertical.""" + """Dessine un fond dégradé vertical avec des couleurs vibrantes.""" height = screen.get_height() top_color = pygame.Color(*top_color) bottom_color = pygame.Color(*bottom_color) @@ -37,32 +64,89 @@ def draw_gradient(screen, top_color, bottom_color): color = top_color.lerp(bottom_color, ratio) pygame.draw.line(screen, color, (0, y), (screen.get_width(), y)) -#Transistion d'image lors de la selection d'un systeme +# Nouvelle fonction pour dessiner un bouton stylisé +def draw_stylized_button(screen, text, x, y, width, height, selected=False): + """Dessine un bouton moderne avec effet de survol et bordure arrondie.""" + button_surface = pygame.Surface((width, height), pygame.SRCALPHA) + button_color = THEME_COLORS["button_hover"] if selected else THEME_COLORS["button_idle"] + pygame.draw.rect(button_surface, button_color, (0, 0, width, height), border_radius=12) + pygame.draw.rect(button_surface, THEME_COLORS["border"], (0, 0, width, height), 2, border_radius=12) + if selected: + glow_surface = pygame.Surface((width + 10, height + 10), pygame.SRCALPHA) + pygame.draw.rect(glow_surface, THEME_COLORS["fond_lignes"] + (50,), (5, 5, width, height), border_radius=12) + screen.blit(glow_surface, (x - 5, y - 5)) + screen.blit(button_surface, (x, y)) + text_surface = config.font.render(text, True, THEME_COLORS["text"]) + text_rect = text_surface.get_rect(center=(x + width // 2, y + height // 2)) + screen.blit(text_surface, text_rect) + +# Transition d'image lors de la sélection d'un système def draw_validation_transition(screen, platform_index): - """Affiche une animation de transition pour la sélection d’une plateforme.""" + """Affiche une animation de transition fluide pour la sélection d’une plateforme.""" platform_dict = config.platform_dicts[platform_index] image = load_system_image(platform_dict) if not image: return + + # Dimensions originales et calcul du ratio pour préserver les proportions orig_width, orig_height = image.get_width(), image.get_height() base_size = int(config.screen_width * 0.0781) # ~150px pour 1920p + ratio = min(base_size / orig_width, base_size / orig_height) # Maintenir les proportions + base_width = int(orig_width * ratio) + base_height = int(orig_height * ratio) + + # Paramètres de l'animation start_time = pygame.time.get_ticks() - duration = 500 + duration = 1000 # Durée augmentée à 1 seconde + fps = 60 + frame_time = 1000 / fps # Temps par frame en ms + while pygame.time.get_ticks() - start_time < duration: - draw_gradient(screen, (28, 37, 38), (47, 59, 61)) + # Fond dégradé + draw_gradient(screen, THEME_COLORS["background_top"], THEME_COLORS["background_bottom"]) + + # Calcul de l'échelle avec une courbe sinusoïdale pour une transition fluide elapsed = pygame.time.get_ticks() - start_time - scale = 2.0 + (2.0 * elapsed / duration) if elapsed < duration / 2 else 3.0 - (2.0 * elapsed / duration) - new_width = int(base_size * scale) - new_height = int(base_size * scale) + progress = elapsed / duration + # Courbe sinusoïdale pour une montée/descente douce + scale = 1.5 + 1.0 * math.sin(math.pi * progress) # Échelle de 1.5 à 2.5 + new_width = int(base_width * scale) + new_height = int(base_height * scale) + + # Redimensionner l'image en préservant les proportions scaled_image = pygame.transform.smoothscale(image, (new_width, new_height)) image_rect = scaled_image.get_rect(center=(config.screen_width // 2, config.screen_height // 2)) + + # Effet de fondu (opacité de 50% à 100% puis retour à 50%) + alpha = int(128 + 127 * math.cos(math.pi * progress)) # Opacité entre 128 et 255 + scaled_image.set_alpha(alpha) + + # Effet de glow néon pour l'image sélectionnée + neon_color = THEME_COLORS["neon"] # Cyan vif + padding = 24 + neon_surface = pygame.Surface((new_width + 2 * padding, new_height + 2 * padding), pygame.SRCALPHA) + pygame.draw.rect(neon_surface, neon_color + (40,), neon_surface.get_rect(), border_radius=24) + pygame.draw.rect(neon_surface, neon_color + (100,), neon_surface.get_rect().inflate(-10, -10), border_radius=18) + screen.blit(neon_surface, (image_rect.left - padding, image_rect.top - padding), special_flags=pygame.BLEND_RGBA_ADD) + + # Afficher l'image screen.blit(scaled_image, image_rect) pygame.display.flip() - pygame.time.wait(10) -#Ecran de chargement + # Contrôler la fréquence de rendu + pygame.time.wait(int(frame_time)) + + # Afficher l'image finale sans effet pour une transition propre + draw_gradient(screen, THEME_COLORS["background_top"], THEME_COLORS["background_bottom"]) + final_image = pygame.transform.smoothscale(image, (base_width, base_height)) + final_image.set_alpha(255) # Opacité complète + final_rect = final_image.get_rect(center=(config.screen_width // 2, config.screen_height // 2)) + screen.blit(final_image, final_rect) + pygame.display.flip() + +# Écran de chargement def draw_loading_screen(screen): - """Affiche l’écran de chargement avec le disclaimer en haut, le texte de chargement et la barre de progression.""" + """Affiche l’écran de chargement avec un style moderne.""" disclaimer_lines = [ "Bienvenue dans RGSX", "It's dangerous to go alone, take all you need!", @@ -70,9 +154,9 @@ def draw_loading_screen(screen): "dont vous possédez les originaux !" ] - margin_horizontal = int(config.screen_width * 0.025) # 2.5% de la largeur - padding_vertical = int(config.screen_height * 0.0185) # ~20px pour 1080p - padding_between = int(config.screen_height * 0.0074) # ~8px pour 1080p + margin_horizontal = int(config.screen_width * 0.025) + padding_vertical = int(config.screen_height * 0.0185) + padding_between = int(config.screen_height * 0.0074) border_radius = 16 border_width = 3 shadow_offset = 6 @@ -82,7 +166,7 @@ def draw_loading_screen(screen): rect_width = config.screen_width - 2 * margin_horizontal rect_height = total_height + 2 * padding_vertical rect_x = margin_horizontal - rect_y = int(config.screen_height * 0.0185) # ~20px pour 1080p + rect_y = int(config.screen_height * 0.0185) shadow_rect = pygame.Rect(rect_x + shadow_offset, rect_y + shadow_offset, rect_width, rect_height) shadow_surface = pygame.Surface((rect_width, rect_height), pygame.SRCALPHA) @@ -91,53 +175,63 @@ def draw_loading_screen(screen): disclaimer_rect = pygame.Rect(rect_x, rect_y, rect_width, rect_height) disclaimer_surface = pygame.Surface((rect_width, rect_height), pygame.SRCALPHA) - pygame.draw.rect(disclaimer_surface, (30, 30, 30, 220), disclaimer_surface.get_rect(), border_radius=border_radius) + pygame.draw.rect(disclaimer_surface, THEME_COLORS["button_idle"], disclaimer_surface.get_rect(), border_radius=border_radius) screen.blit(disclaimer_surface, disclaimer_rect.topleft) - pygame.draw.rect(screen, (255, 255, 255), disclaimer_rect, border_width, border_radius=border_radius) + pygame.draw.rect(screen, THEME_COLORS["border"], disclaimer_rect, border_width, border_radius=border_radius) max_text_width = rect_width - 2 * padding_vertical for i, line in enumerate(disclaimer_lines): wrapped_lines = wrap_text(line, config.small_font, max_text_width) for j, wrapped_line in enumerate(wrapped_lines): - text_surface = config.small_font.render(wrapped_line, True, (255, 255, 255)) + text_surface = config.small_font.render(wrapped_line, True, THEME_COLORS["title_text"]) text_rect = text_surface.get_rect(center=( config.screen_width // 2, rect_y + padding_vertical + (i * len(wrapped_lines) + j + 0.5) * line_height - padding_between // 2 )) screen.blit(text_surface, text_rect) - loading_y = rect_y + rect_height + int(config.screen_height * 0.0926) # ~100px pour 1080p - text = config.small_font.render(truncate_text_middle(f"{config.current_loading_system}", config.small_font, config.screen_width - 2 * margin_horizontal), True, (255, 255, 255)) + loading_y = rect_y + rect_height + int(config.screen_height * 0.0926) + text = config.small_font.render(truncate_text_middle(f"{config.current_loading_system}", config.small_font, config.screen_width - 2 * margin_horizontal), True, THEME_COLORS["text"]) text_rect = text.get_rect(center=(config.screen_width // 2, loading_y)) screen.blit(text, text_rect) - progress_text = config.small_font.render(f"Progression : {int(config.loading_progress)}%", True, (255, 255, 255)) - progress_rect = progress_text.get_rect(center=(config.screen_width // 2, loading_y + int(config.screen_height * 0.0463))) # ~50px pour 1080p + progress_text = config.small_font.render(f"Progression : {int(config.loading_progress)}%", True, THEME_COLORS["text"]) + progress_rect = progress_text.get_rect(center=(config.screen_width // 2, loading_y + int(config.screen_height * 0.0463))) screen.blit(progress_text, progress_rect) - bar_width = int(config.screen_width * 0.2083) # ~400px pour 1920p - bar_height = int(config.screen_height * 0.037) # ~40px pour 1080p + bar_width = int(config.screen_width * 0.2083) + bar_height = int(config.screen_height * 0.037) progress_width = (bar_width * config.loading_progress) / 100 - pygame.draw.rect(screen, (100, 100, 100), (config.screen_width // 2 - bar_width // 2, loading_y + int(config.screen_height * 0.0926), bar_width, bar_height)) - pygame.draw.rect(screen, (0, 255, 0), (config.screen_width // 2 - bar_width // 2, loading_y + int(config.screen_height * 0.0926), progress_width, bar_height)) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (config.screen_width // 2 - bar_width // 2, loading_y + int(config.screen_height * 0.0926), bar_width, bar_height), border_radius=8) + pygame.draw.rect(screen, THEME_COLORS["fond_lignes"], (config.screen_width // 2 - bar_width // 2, loading_y + int(config.screen_height * 0.0926), progress_width, bar_height), border_radius=8) -#Ecran d'erreur +# Écran d'erreur def draw_error_screen(screen): - """Affiche l’écran d’erreur.""" - error_font = pygame.font.SysFont("arial", 28) - wrapped_message = wrap_text(config.error_message, error_font, config.screen_width - 80) - line_height = error_font.get_height() + 5 - for i, line in enumerate(wrapped_message): - text = error_font.render(line, True, (255, 0, 0)) - text_rect = text.get_rect(center=(config.screen_width // 2, config.screen_height // 2 - (len(wrapped_message) // 2 - i) * line_height)) - screen.blit(text, text_rect) - # Afficher uniquement "Valider" - confirm_text = config.small_font.render("Valider", True, (0, 150, 255)) - confirm_rect = confirm_text.get_rect(center=(config.screen_width // 2, config.screen_height // 2 + int(config.screen_height * 0.0926))) - screen.blit(confirm_text, confirm_rect) + """Affiche l’écran d’erreur avec un style moderne.""" + wrapped_message = wrap_text(config.error_message, config.small_font, config.screen_width - 80) + line_height = config.small_font.get_height() + 5 + text_height = len(wrapped_message) * line_height + button_height = int(config.screen_height * 0.0463) + margin_top_bottom = 20 + rect_height = text_height + button_height + 2 * margin_top_bottom + max_text_width = max([config.small_font.size(line)[0] for line in wrapped_message], default=300) + rect_width = max_text_width + 80 + rect_x = (config.screen_width - rect_width) // 2 + rect_y = (config.screen_height - rect_height) // 2 -#Recuperer les noms d'affichage des controles + screen.blit(OVERLAY, (0, 0)) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12) + + for i, line in enumerate(wrapped_message): + text = config.small_font.render(line, True, THEME_COLORS["error_text"]) + text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) + screen.blit(text, text_rect) + + draw_stylized_button(screen, "Valider", rect_x + rect_width // 2 - 80, rect_y + text_height + margin_top_bottom, 160, button_height, selected=True) + +# Récupérer les noms d'affichage des contrôles def get_control_display(action, default): """Récupère le nom d'affichage d'une action depuis controls_config.""" if not config.controls_config: @@ -145,10 +239,9 @@ def get_control_display(action, default): return default return config.controls_config.get(action, {}).get('display', default) -#Grille des systemes 3x3 +# Grille des systèmes 3x3 def draw_platform_grid(screen): - """Affiche la grille des plateformes avec un titre en haut.""" - # Configuration du titre + """Affiche la grille des plateformes avec un style moderne.""" if not config.platforms or config.selected_platform >= len(config.platforms): platform_name = "Aucune plateforme" logger.warning("Aucune plateforme ou selected_platform hors limites") @@ -156,23 +249,21 @@ def draw_platform_grid(screen): platform = config.platforms[config.selected_platform] platform_name = config.platform_names.get(platform, platform) title_text = f"{platform_name}" - title_surface = config.title_font.render(title_text, True, (255, 255, 255)) - title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 10)) - title_rect_inflated = title_rect.inflate(40, 20) - title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 0) - - # Dessiner le rectangle de fond du titre - pygame.draw.rect(screen, (50, 50, 50, 200), title_rect_inflated, border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), title_rect_inflated, 2, border_radius=10) + title_surface = config.title_font.render(title_text, True, THEME_COLORS["text"]) + title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 20)) + title_rect_inflated = title_rect.inflate(60, 30) + title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 10) + + pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12) screen.blit(title_surface, title_rect) - # Configuration de la grille - margin_left = int(config.screen_width * 0.026) # ~50px pour 1920p + margin_left = int(config.screen_width * 0.026) margin_right = int(config.screen_width * 0.026) - margin_top = int(config.screen_height * 0.140) # ~120px pour 1080p - margin_bottom = int(config.screen_height * 0.0648) # ~70px pour 1080p + margin_top = int(config.screen_height * 0.140) + margin_bottom = int(config.screen_height * 0.0648) num_cols = 3 - num_rows = 3 + num_rows = 4 systems_per_page = num_cols * num_rows available_width = config.screen_width - margin_left - margin_right @@ -185,8 +276,6 @@ def draw_platform_grid(screen): y_positions = [margin_top + row_height * i + row_height // 2 for i in range(num_rows)] start_idx = config.current_page * systems_per_page - #logger.debug(f"Page {config.current_page}, start_idx: {start_idx}, total_platforms: {len(config.platforms)}") - for idx in range(start_idx, start_idx + systems_per_page): if idx >= len(config.platforms): break @@ -200,7 +289,7 @@ def draw_platform_grid(screen): image = load_system_image(platform_dict) if image: orig_width, orig_height = image.get_width(), image.get_height() - max_size = int(min(col_width, row_height) * scale * 0.9) + max_size = int(min(col_width, row_height) * scale * 1.1) # Légèrement plus grand que la cellule ratio = min(max_size / orig_width, max_size / orig_height) new_width = int(orig_width * ratio) new_height = int(orig_height * ratio) @@ -208,35 +297,26 @@ def draw_platform_grid(screen): image_rect = image.get_rect(center=(x, y)) if idx == config.selected_platform: - neon_color = (0, 255, 255) - border_radius = 24 - padding = 24 + neon_color = THEME_COLORS["neon"] + border_radius = 12 + padding = 12 rect_width = image_rect.width + 2 * padding rect_height = image_rect.height + 2 * padding neon_surface = pygame.Surface((rect_width, rect_height), pygame.SRCALPHA) - pygame.draw.rect( - neon_surface, - neon_color + (60,), - neon_surface.get_rect(), - width=1, - border_radius=border_radius + 8 - ) - pygame.draw.rect( - neon_surface, - neon_color + (180,), - neon_surface.get_rect().inflate(-8, -8), - width=2, - border_radius=border_radius - ) + pygame.draw.rect(neon_surface, neon_color + (40,), neon_surface.get_rect(), border_radius=border_radius) + pygame.draw.rect(neon_surface, neon_color + (100,), neon_surface.get_rect().inflate(-10, -10), border_radius=border_radius) + pygame.draw.rect(neon_surface, neon_color + (200,), neon_surface.get_rect().inflate(-20, -20), width=1, border_radius=border_radius) screen.blit(neon_surface, (image_rect.left - padding, image_rect.top - padding), special_flags=pygame.BLEND_RGBA_ADD) + background_surface = pygame.Surface((image_rect.width + 10, image_rect.height + 10), pygame.SRCALPHA) + pygame.draw.rect(background_surface, THEME_COLORS["fond_image"] + (180,), background_surface.get_rect(), border_radius=12) + screen.blit(background_surface, (image_rect.left - 5, image_rect.top - 5)) + screen.blit(image, image_rect) -#Liste des jeux +# Liste des jeux def draw_game_list(screen): - """Affiche la liste des jeux avec défilement et rectangle de fond.""" - #logger.debug("Début de draw_game_list") - + """Affiche la liste des jeux avec un style moderne.""" platform = config.platforms[config.current_platform] platform_name = config.platform_names.get(platform, platform) games = config.filtered_games if config.filter_active or config.search_mode else config.games @@ -251,25 +331,24 @@ def draw_game_list(screen): margin_top_bottom = 20 rect_height = text_height + 2 * margin_top_bottom max_text_width = max([config.font.size(line)[0] for line in lines], default=300) - rect_width = max_text_width + 40 + rect_width = max_text_width + 80 rect_x = (config.screen_width - rect_width) // 2 rect_y = (config.screen_height - rect_height) // 2 screen.blit(OVERLAY, (0, 0)) - pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12) for i, line in enumerate(lines): - text_surface = config.font.render(line, True, (255, 255, 255)) + text_surface = config.font.render(line, True, THEME_COLORS["text"]) text_rect = text_surface.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) screen.blit(text_surface, text_rect) return line_height = config.small_font.get_height() + 10 - #header_height = line_height margin_top_bottom = 20 extra_margin_top = 20 - extra_margin_bottom = 60 # Aligné sur draw_history_list + extra_margin_bottom = 60 title_height = config.title_font.get_height() + 20 available_height = config.screen_height - title_height - extra_margin_top - extra_margin_bottom - 2 * margin_top_bottom @@ -290,45 +369,47 @@ def draw_game_list(screen): if config.search_mode: search_text = f"Filtrer : {config.search_query}_" - title_surface = config.search_font.render(search_text, True, (255, 255, 255)) - title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 10)) - title_rect_inflated = title_rect.inflate(40, 20) - title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 0) - pygame.draw.rect(screen, (50, 50, 50, 200), title_rect_inflated, border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), title_rect_inflated, 2, border_radius=10) + title_surface = config.search_font.render(search_text, True, THEME_COLORS["text"]) + title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 20)) + title_rect_inflated = title_rect.inflate(60, 30) + title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 10) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12) screen.blit(title_surface, title_rect) elif config.filter_active: filter_text = f"Filtre actif : {config.search_query}" - title_surface = config.small_font.render(filter_text, True, (255, 255, 255)) - title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 10)) - title_rect_inflated = title_rect.inflate(40, 20) - title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 0) - pygame.draw.rect(screen, (50, 50, 50, 200), title_rect_inflated, border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), title_rect_inflated, 2, border_radius=10) + title_surface = config.small_font.render(filter_text, True, THEME_COLORS["text"]) + title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 20)) + title_rect_inflated = title_rect.inflate(60, 30) + title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 10) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12) screen.blit(title_surface, title_rect) else: title_text = f"{platform_name} ({game_count} jeux)" - title_surface = config.title_font.render(title_text, True, (255, 255, 255)) - title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 10)) - title_rect_inflated = title_rect.inflate(40, 20) - title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 0) - pygame.draw.rect(screen, (50, 50, 50, 200), title_rect_inflated, border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), title_rect_inflated, 2, border_radius=10) + title_surface = config.title_font.render(title_text, True, THEME_COLORS["text"]) + title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 20)) + title_rect_inflated = title_rect.inflate(60, 30) + title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 10) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12) screen.blit(title_surface, title_rect) - pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12) for i in range(config.scroll_offset, min(config.scroll_offset + items_per_page, len(games))): game_name = games[i][0] if isinstance(games[i], (list, tuple)) else games[i] - color = (0, 150, 255) if i == config.current_game else (255, 255, 255) + color = THEME_COLORS["fond_lignes"] if i == config.current_game else THEME_COLORS["text"] game_text = truncate_text_middle(game_name, config.small_font, rect_width - 40) text_surface = config.small_font.render(game_text, True, color) text_rect = text_surface.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + (i - config.scroll_offset) * line_height + line_height // 2)) + if i == config.current_game: + glow_surface = pygame.Surface((text_rect.width + 20, text_rect.height + 10), pygame.SRCALPHA) + pygame.draw.rect(glow_surface, THEME_COLORS["fond_lignes"] + (50,), (10, 5, text_rect.width, text_rect.height), border_radius=8) + screen.blit(glow_surface, (text_rect.left - 10, text_rect.top - 5)) screen.blit(text_surface, text_rect) - #logger.debug(f"Jeu affiché : texte={game_text}, position={text_rect}, selected={i == config.current_game}") - # Afficher la barre de scroll si besoin if len(games) > items_per_page: try: draw_game_scrollbar( @@ -343,7 +424,7 @@ def draw_game_list(screen): except NameError as e: logger.error(f"Erreur : draw_game_scrollbar non défini: {str(e)}") -#Barre de défilement des jeux +# Barre de défilement des jeux def draw_game_scrollbar(screen, scroll_offset, total_items, visible_items, x, y, height): """Affiche la barre de défilement pour la liste des jeux.""" if total_items <= visible_items: @@ -351,30 +432,25 @@ def draw_game_scrollbar(screen, scroll_offset, total_items, visible_items, x, y, game_area_height = height scrollbar_height = game_area_height * (visible_items / total_items) scrollbar_y = y + (game_area_height - scrollbar_height) * (scroll_offset / max(1, total_items - visible_items)) - pygame.draw.rect(screen, (255, 255, 255), (x, scrollbar_y, 15, scrollbar_height)) - -#Liste historique téléchargement + pygame.draw.rect(screen, THEME_COLORS["fond_lignes"], (x, scrollbar_y, 15, scrollbar_height), border_radius=4) def draw_history_list(screen): - """Affiche l'historique des téléchargements sous forme de tableau avec système, nom du jeu et état.""" + """Affiche l'historique des téléchargements avec un style moderne.""" history = config.history if hasattr(config, 'history') else load_history() history_count = len(history) - # Définir les largeurs des colonnes (valeurs fixes pour simplifier, ajustez si nécessaire) col_platform_width = int((0.95 * config.screen_width - 60) * 0.33) col_game_width = int((0.95 * config.screen_width - 60) * 0.50) col_status_width = int((0.95 * config.screen_width - 60) * 0.17) - rect_width = int(0.95 * config.screen_width) # 95% de la largeur de l'écran + rect_width = int(0.95 * config.screen_width) - # Hauteur des lignes et en-tête line_height = config.small_font.get_height() + 10 header_height = line_height margin_top_bottom = 20 - extra_margin_top = 20 - extra_margin_bottom = 60 + extra_margin_top = 40 + extra_margin_bottom = 80 title_height = config.title_font.get_height() + 20 - # Cas où l'historique est vide if not history: logger.debug("Aucun historique disponible") message = "Aucun téléchargement dans l'historique" @@ -383,54 +459,47 @@ def draw_history_list(screen): text_height = len(lines) * line_height rect_height = text_height + 2 * margin_top_bottom max_text_width = max([config.font.size(line)[0] for line in lines], default=300) - rect_width = max_text_width + 40 + rect_width = max_text_width + 80 rect_x = (config.screen_width - rect_width) // 2 rect_y = (config.screen_height - rect_height) // 2 screen.blit(OVERLAY, (0, 0)) - pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12) for i, line in enumerate(lines): - text_surface = config.font.render(line, True, (255, 255, 255)) + text_surface = config.font.render(line, True, THEME_COLORS["text"]) text_rect = text_surface.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) screen.blit(text_surface, text_rect) return - # Calculer la hauteur disponible et les éléments par page available_height = config.screen_height - title_height - extra_margin_top - extra_margin_bottom - 2 * margin_top_bottom items_per_page = available_height // line_height - # Calculer les dimensions du rectangle rect_height = header_height + items_per_page * line_height + 2 * margin_top_bottom rect_x = (config.screen_width - rect_width) // 2 rect_y = title_height + extra_margin_top + (config.screen_height - title_height - extra_margin_top - extra_margin_bottom - rect_height) // 2 - # Gestion du défilement config.history_scroll_offset = max(0, min(config.history_scroll_offset, max(0, len(history) - items_per_page))) if config.current_history_item < config.history_scroll_offset: config.history_scroll_offset = config.current_history_item elif config.current_history_item >= config.history_scroll_offset + items_per_page: config.history_scroll_offset = config.current_history_item - items_per_page + 1 - # Fond et cadre screen.blit(OVERLAY, (0, 0)) - # Titre title_text = f"Historique des téléchargements ({history_count})" - title_surface = config.title_font.render(title_text, True, (255, 255, 255)) - title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 10)) - title_rect_inflated = title_rect.inflate(40, 20) - title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 0) - pygame.draw.rect(screen, (50, 50, 50, 200), title_rect_inflated, border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), title_rect_inflated, 2, border_radius=10) + title_surface = config.title_font.render(title_text, True, THEME_COLORS["text"]) + title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 20)) + title_rect_inflated = title_rect.inflate(60, 30) + title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 10) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12) screen.blit(title_surface, title_rect) - # Cadre du tableau - pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12) - # En-têtes du tableau headers = ["Système", "Nom du jeu", "État"] header_y = rect_y + margin_top_bottom + header_height // 2 header_x_positions = [ @@ -439,36 +508,42 @@ def draw_history_list(screen): rect_x + 20 + col_platform_width + col_game_width + col_status_width // 2 ] for header, x_pos in zip(headers, header_x_positions): - text_surface = config.small_font.render(header, True, (255, 255, 255)) + text_surface = config.small_font.render(header, True, THEME_COLORS["text"]) text_rect = text_surface.get_rect(center=(x_pos, header_y)) screen.blit(text_surface, text_rect) - # Afficher les entrées de l'historique + # Ajouter un séparateur sous les en-têtes + separator_y = rect_y + margin_top_bottom + header_height + pygame.draw.line(screen, THEME_COLORS["border"], (rect_x + 20, separator_y), (rect_x + rect_width - 20, separator_y), 2) + for idx, i in enumerate(range(config.history_scroll_offset, min(config.history_scroll_offset + items_per_page, len(history)))): entry = history[i] platform = entry.get("platform", "Inconnu") game_name = entry.get("game_name", "Inconnu") status = entry.get("status", "Inconnu") - color = (0, 150, 255) if i == config.current_history_item else (255, 255, 255) + color = THEME_COLORS["fond_lignes"] if i == config.current_history_item else THEME_COLORS["text"] platform_text = truncate_text_middle(platform, config.small_font, col_platform_width - 10) game_text = truncate_text_middle(game_name, config.small_font, col_game_width - 10) status_text = truncate_text_middle(status, config.small_font, col_status_width - 10) - + y_pos = rect_y + margin_top_bottom + header_height + idx * line_height + line_height // 2 platform_surface = config.small_font.render(platform_text, True, color) game_surface = config.small_font.render(game_text, True, color) status_surface = config.small_font.render(status_text, True, color) - + platform_rect = platform_surface.get_rect(center=(header_x_positions[0], y_pos)) game_rect = game_surface.get_rect(center=(header_x_positions[1], y_pos)) status_rect = status_surface.get_rect(center=(header_x_positions[2], y_pos)) - + + if i == config.current_history_item: + glow_surface = pygame.Surface((rect_width - 40, line_height), pygame.SRCALPHA) + pygame.draw.rect(glow_surface, THEME_COLORS["fond_lignes"] + (50,), (0, 0, rect_width - 40, line_height), border_radius=8) + screen.blit(glow_surface, (rect_x + 20, y_pos - line_height // 2)) + screen.blit(platform_surface, platform_rect) screen.blit(game_surface, game_rect) screen.blit(status_surface, status_rect) - #logger.debug(f"Entrée historique affichée : index={i}, platform={platform_text}, game={game_text}, status={status_text}, selected={i == config.current_history_item}") - # Scrollbar if len(history) > items_per_page: try: draw_history_scrollbar( @@ -483,18 +558,17 @@ def draw_history_list(screen): except NameError as e: logger.error(f"Erreur : draw_history_scrollbar non défini: {str(e)}") -#Barre de défilement de l'historique +# Barre de défilement de l'historique def draw_history_scrollbar(screen, scroll_offset, total_items, visible_items, x, y, height): - """Affiche la barre de défilement à droite de l’écran.""" - if len(config.filtered_games) <= config.visible_games: + """Affiche la barre de défilement avec un style moderne.""" + if total_items <= visible_items: return + game_area_height = height + scrollbar_height = game_area_height * (visible_items / total_items) - 10 + scrollbar_y = y + (game_area_height - scrollbar_height) * (scroll_offset / max(1, total_items - visible_items)) + 10 + pygame.draw.rect(screen, THEME_COLORS["fond_lignes"], (x, scrollbar_y, 5, scrollbar_height), border_radius=4) - game_area_height = config.screen_height - 150 - scrollbar_height = game_area_height * (config.visible_games / len(config.filtered_games)) - scrollbar_y = 120 + (game_area_height - scrollbar_height) * (scroll_offset / max(1, len(config.filtered_games) - config.visible_games)) - pygame.draw.rect(screen, (255, 255, 255), (config.screen_width - 25, scrollbar_y, 15, scrollbar_height)) - -#Ecran confirmation vider historique +# Écran confirmation vider historique def draw_clear_history_dialog(screen): """Affiche la boîte de dialogue de confirmation pour vider l'historique.""" screen.blit(OVERLAY, (0, 0)) @@ -503,53 +577,48 @@ def draw_clear_history_dialog(screen): wrapped_message = wrap_text(message, config.font, config.screen_width - 80) line_height = config.font.get_height() + 5 text_height = len(wrapped_message) * line_height - button_height = line_height + 20 + button_height = int(config.screen_height * 0.0463) margin_top_bottom = 20 rect_height = text_height + button_height + 2 * margin_top_bottom max_text_width = max([config.font.size(line)[0] for line in wrapped_message], default=300) - rect_width = max_text_width + 40 + rect_width = max_text_width + 150 rect_x = (config.screen_width - rect_width) // 2 rect_y = (config.screen_height - rect_height) // 2 - pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12) for i, line in enumerate(wrapped_message): - text = config.font.render(line, True, (255, 255, 255)) + text = config.font.render(line, True, THEME_COLORS["text"]) text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) screen.blit(text, text_rect) - yes_text = config.font.render("Oui", True, (0, 150, 255) if config.confirm_clear_selection == 1 else (255, 255, 255)) - no_text = config.font.render("Non", True, (0, 150, 255) if config.confirm_clear_selection == 0 else (255, 255, 255)) - yes_rect = yes_text.get_rect(center=(config.screen_width // 2 - 100, rect_y + text_height + margin_top_bottom + line_height // 2)) - no_rect = no_text.get_rect(center=(config.screen_width // 2 + 100, rect_y + text_height + margin_top_bottom + line_height // 2)) + draw_stylized_button(screen, "Oui", rect_x + rect_width // 2 - 180, rect_y + text_height + margin_top_bottom, 160, button_height, selected=config.confirm_clear_selection == 1) + draw_stylized_button(screen, "Non", rect_x + rect_width // 2 + 20, rect_y + text_height + margin_top_bottom, 160, button_height, selected=config.confirm_clear_selection == 0) - screen.blit(yes_text, yes_rect) - screen.blit(no_text, no_rect) - -#Affichage du clavier virtuel sur non pc +# Affichage du clavier virtuel sur non-PC def draw_virtual_keyboard(screen): - """Affiche un clavier virtuel pour la saisie dans search_mode, centré verticalement.""" + """Affiche un clavier virtuel avec un style moderne.""" keyboard_layout = [ ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], ['A', 'Z', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'], ['Q', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M'], ['W', 'X', 'C', 'V', 'B', 'N'] ] - key_width = int(config.screen_width * 0.03125) # ~60px pour 1920p - key_height = int(config.screen_height * 0.0556) # ~60px pour 1080p - key_spacing = int(config.screen_width * 0.0052) # ~10px pour 1920p + key_width = int(config.screen_width * 0.03125) + key_height = int(config.screen_height * 0.0556) + key_spacing = int(config.screen_width * 0.0052) keyboard_width = len(keyboard_layout[0]) * (key_width + key_spacing) - key_spacing keyboard_height = len(keyboard_layout) * (key_height + key_spacing) - key_spacing start_x = (config.screen_width - keyboard_width) // 2 - search_bottom_y = int(config.screen_height * 0.111) + (config.search_font.get_height() + 40) // 2 # ~120px pour 1080p - controls_y = config.screen_height - int(config.screen_height * 0.037) # ~40px pour 1080p + search_bottom_y = int(config.screen_height * 0.111) + (config.search_font.get_height() + 40) // 2 + controls_y = config.screen_height - int(config.screen_height * 0.037) available_height = controls_y - search_bottom_y start_y = search_bottom_y + (available_height - keyboard_height - 40) // 2 keyboard_rect = pygame.Rect(start_x - 20, start_y - 20, keyboard_width + 40, keyboard_height + 40) - pygame.draw.rect(screen, (50, 50, 50, 200), keyboard_rect, border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), keyboard_rect, 2, border_radius=10) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], keyboard_rect, border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], keyboard_rect, 2, border_radius=12) for row_idx, row in enumerate(keyboard_layout): for col_idx, key in enumerate(row): @@ -557,19 +626,17 @@ def draw_virtual_keyboard(screen): y = start_y + row_idx * (key_height + key_spacing) key_rect = pygame.Rect(x, y, key_width, key_height) if (row_idx, col_idx) == config.selected_key: - pygame.draw.rect(screen, (0, 150, 255, 150), key_rect, border_radius=5) + pygame.draw.rect(screen, THEME_COLORS["fond_lignes"] + (150,), key_rect, border_radius=8) else: - pygame.draw.rect(screen, (80, 80, 80, 255), key_rect, border_radius=5) - pygame.draw.rect(screen, (255, 255, 255), key_rect, 1, border_radius=5) - text = config.font.render(key, True, (255, 255, 255)) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], key_rect, border_radius=8) + pygame.draw.rect(screen, THEME_COLORS["border"], key_rect, 1, border_radius=8) + text = config.font.render(key, True, THEME_COLORS["text"]) text_rect = text.get_rect(center=key_rect.center) screen.blit(text, text_rect) -#Ecran de progression de téléchargement/extraction +# Écran de progression de téléchargement/extraction def draw_progress_screen(screen): - """Affiche l'écran de progression des téléchargements avec taille en Mo.""" - #logger.debug("Début de draw_progress_screen") - + """Affiche l'écran de progression des téléchargements avec un style moderne.""" if not config.download_tasks: logger.debug("Aucune tâche de téléchargement active") return @@ -582,7 +649,6 @@ def draw_progress_screen(screen): downloaded_size = progress["downloaded_size"] total_size = progress["total_size"] progress_percent = progress["progress_percent"] - #logger.debug(f"Progression : game_name={game_name}, url={url}, status={status}, progress_percent={progress_percent}, downloaded_size={downloaded_size}, total_size={total_size}") screen.blit(OVERLAY, (0, 0)) @@ -591,63 +657,73 @@ def draw_progress_screen(screen): line_height = config.font.get_height() + 5 text_height = len(title_lines) * line_height margin_top_bottom = 20 - bar_height = int(config.screen_height * 0.0278) # ~30px pour 1080p - percent_height = line_height + bar_height = int(config.screen_height * 0.0278) + percent_height = config.progress_font.get_height() + 5 rect_height = text_height + bar_height + percent_height + 3 * margin_top_bottom max_text_width = max([config.font.size(line)[0] for line in title_lines], default=300) bar_width = max_text_width - rect_width = max_text_width + 40 + rect_width = max_text_width + 80 rect_x = (config.screen_width - rect_width) // 2 rect_y = (config.screen_height - rect_height) // 2 - pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12) for i, line in enumerate(title_lines): - title_render = config.font.render(line, True, (255, 255, 255)) + title_render = config.font.render(line, True, THEME_COLORS["text"]) title_rect = title_render.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) screen.blit(title_render, title_rect) - #logger.debug(f"Titre affiché : texte={line}, position={title_rect}, taille={title_render.get_size()}") bar_y = rect_y + text_height + margin_top_bottom progress_width = 0 - pygame.draw.rect(screen, (100, 100, 100), (rect_x + 20, bar_y, bar_width, bar_height)) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x + 20, bar_y, bar_width, bar_height), border_radius=8) if total_size > 0: progress_width = int(bar_width * (progress_percent / 100)) - pygame.draw.rect(screen, (0, 150, 255), (rect_x + 20, bar_y, progress_width, bar_height)) - pygame.draw.rect(screen, (255, 255, 255), (rect_x + 20, bar_y, bar_width, bar_height), 2) - #logger.debug(f"Barre de progression affichée : position=({rect_x + 20}, {bar_y}), taille=({bar_width}, {bar_height}), progress_width={progress_width}") + pygame.draw.rect(screen, THEME_COLORS["fond_lignes"], (rect_x + 20, bar_y, progress_width, bar_height), border_radius=8) + pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x + 20, bar_y, bar_width, bar_height), 2, border_radius=8) downloaded_mb = downloaded_size / (1024 * 1024) total_mb = total_size / (1024 * 1024) size_text = f"{downloaded_mb:.1f} Mo / {total_mb:.1f} Mo" percent_text = f"{int(progress_percent)}% {size_text}" - percent_lines = wrap_text(percent_text, config.font, config.screen_width - 80) + percent_lines = wrap_text(percent_text, config.progress_font, config.screen_width - 80) text_y = bar_y + bar_height + margin_top_bottom for i, line in enumerate(percent_lines): - percent_render = config.font.render(line, True, (255, 255, 255)) - percent_rect = percent_render.get_rect(center=(config.screen_width // 2, text_y + i * line_height + line_height // 2)) + percent_render = config.progress_font.render(line, True, THEME_COLORS["text"]) + percent_rect = percent_render.get_rect(center=(config.screen_width // 2, text_y + i * percent_height + percent_height // 2)) screen.blit(percent_render, percent_rect) -#Ecran popup resultat téléchargement -def draw_popup_message(screen, message, is_error): +# Écran popup résultat téléchargement +def draw_popup_result_download(screen, message, is_error): """Affiche une popup avec un message de résultat.""" screen.blit(OVERLAY, (0, 0)) if message is None: message = "Téléchargement annulé" logger.debug(f"Message popup : {message}, is_error={is_error}") - wrapped_message = wrap_text(message, config.small_font, config.screen_width - 80) + # Réduire la largeur maximale pour le wrapping + wrapped_message = wrap_text(message, config.small_font, config.screen_width - 160) + # Débogage pour vérifier les lignes wrappées + logger.debug(f"Lignes wrappées : {wrapped_message}") line_height = config.small_font.get_height() + 5 + text_height = len(wrapped_message) * line_height + margin_top_bottom = 20 + rect_height = text_height + 2 * margin_top_bottom + max_text_width = max([config.small_font.size(line)[0] for line in wrapped_message], default=300) + rect_width = max_text_width + 100 # Augmenter la marge + rect_x = (config.screen_width - rect_width) // 2 + rect_y = (config.screen_height - rect_height) // 2 + + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12) + for i, line in enumerate(wrapped_message): - text = config.small_font.render(line, True, (255, 0, 0) if is_error else (0, 255, 0)) - text_rect = text.get_rect(center=(config.screen_width // 2, config.screen_height // 2 - (len(wrapped_message) // 2 - i) * line_height)) + text = config.small_font.render(line, True, THEME_COLORS["error_text"] if is_error else THEME_COLORS["fond_lignes"]) + text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) screen.blit(text, text_rect) -#Ecran avertissement extension non supportée téléchargement +# Écran avertissement extension non supportée téléchargement def draw_extension_warning(screen): """Affiche un avertissement pour une extension non reconnue ou un fichier ZIP.""" - #logger.debug("Début de draw_extension_warning") - if not config.pending_download: logger.error("config.pending_download est None ou vide dans extension_warning") message = "Erreur : Aucun téléchargement en attente." @@ -673,36 +749,25 @@ def draw_extension_warning(screen): try: line_height = config.font.get_height() + 5 text_height = len(lines) * line_height - button_height = line_height + 20 + button_height = int(config.screen_height * 0.0463) margin_top_bottom = 20 rect_height = text_height + button_height + 2 * margin_top_bottom max_text_width = max([config.font.size(line)[0] for line in lines], default=300) - rect_width = max_text_width + 40 + rect_width = max_text_width + 80 rect_x = (config.screen_width - rect_width) // 2 rect_y = (config.screen_height - rect_height) // 2 screen.blit(OVERLAY, (0, 0)) - pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12) for i, line in enumerate(lines): - text_surface = config.font.render(line, True, (255, 255, 255)) + text_surface = config.font.render(line, True, THEME_COLORS["warning_text"]) text_rect = text_surface.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) screen.blit(text_surface, text_rect) - #logger.debug(f"Lignes affichées : {[(rect.center, text_surface.get_size()) for rect, text_surface in zip([text_surface.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) for i in range(len(lines))], [config.font.render(line, True, (255, 255, 255)) for line in lines])]}") - yes_text = "[Oui]" if config.extension_confirm_selection == 1 else "Oui" - no_text = "[Non]" if config.extension_confirm_selection == 0 else "Non" - yes_surface = config.font.render(yes_text, True, (0, 150, 255) if config.extension_confirm_selection == 1 else (255, 255, 255)) - no_surface = config.font.render(no_text, True, (0, 150, 255) if config.extension_confirm_selection == 0 else (255, 255, 255)) - - button_y = rect_y + text_height + margin_top_bottom + line_height // 2 - yes_rect = yes_surface.get_rect(center=(config.screen_width // 2 - 100, button_y)) - no_rect = no_surface.get_rect(center=(config.screen_width // 2 + 100, button_y)) - - screen.blit(yes_surface, yes_rect) - screen.blit(no_surface, no_rect) - #logger.debug(f"Boutons affichés : Oui={yes_rect}, Non={no_rect}, selection={config.extension_confirm_selection}") + draw_stylized_button(screen, "Oui", rect_x + rect_width // 2 - 180, rect_y + text_height + margin_top_bottom, 160, button_height, selected=config.extension_confirm_selection == 1) + draw_stylized_button(screen, "Non", rect_x + rect_width // 2 + 20, rect_y + text_height + margin_top_bottom, 160, button_height, selected=config.extension_confirm_selection == 0) except Exception as e: logger.error(f"Erreur lors du rendu de extension_warning : {str(e)}") @@ -711,38 +776,42 @@ def draw_extension_warning(screen): line_height = config.font.get_height() + 5 rect_height = len(wrapped_error) * line_height + 2 * 20 max_text_width = max([config.font.size(line)[0] for line in wrapped_error], default=300) - rect_width = max_text_width + 40 + rect_width = max_text_width + 80 rect_x = (config.screen_width - rect_width) // 2 rect_y = (config.screen_height - rect_height) // 2 screen.blit(OVERLAY, (0, 0)) - pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12) for i, line in enumerate(wrapped_error): - error_surface = config.font.render(line, True, (255, 0, 0)) + error_surface = config.font.render(line, True, THEME_COLORS["error_text"]) error_rect = error_surface.get_rect(center=(config.screen_width // 2, rect_y + 20 + i * line_height + line_height // 2)) screen.blit(error_surface, error_rect) -#Affichage des controles en bas de page +# Affichage des contrôles en bas de page def draw_controls(screen, menu_state): - """Affiche les contrôles sur une seule ligne en bas de l’écran pour tous les états du menu.""" + """Affiche les contrôles sur une seule ligne en bas de l’écran.""" start_button = get_control_display('start', 'START') - control_text = f"{start_button} : Options - History - Controls" + control_text = f"RGSX v{config.app_version} - {start_button} : Options - History - Help" max_width = config.screen_width - 40 wrapped_controls = wrap_text(control_text, config.small_font, max_width) line_height = config.small_font.get_height() + 5 rect_height = len(wrapped_controls) * line_height + 20 rect_y = config.screen_height - rect_height - 5 + rect_x = (config.screen_width - max_width) // 2 + + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, max_width, rect_height), border_radius=8) + pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, max_width, rect_height), 1, border_radius=8) for i, line in enumerate(wrapped_controls): - text_surface = config.small_font.render(line, True, (255, 255, 255)) + text_surface = config.small_font.render(line, True, THEME_COLORS["text"]) text_rect = text_surface.get_rect(center=(config.screen_width // 2, rect_y + 10 + i * line_height + line_height // 2)) screen.blit(text_surface, text_rect) -#Menu pause +# Menu pause def draw_pause_menu(screen, selected_option): - """Dessine le menu pause avec les options Aide, Configurer contrôles, Historique, Redownload game cache, Quitter.""" + """Dessine le menu pause avec un style moderne.""" screen.blit(OVERLAY, (0, 0)) options = [ @@ -752,27 +821,32 @@ def draw_pause_menu(screen, selected_option): "Redownload Games cache", "Quit" ] - - menu_width = int(config.screen_width * 0.8) # ~400px pour 1920p + + menu_width = int(config.screen_width * 0.8) line_height = config.font.get_height() + 10 - text_height = len(options) * line_height + button_height = int(config.screen_height * 0.0463) margin_top_bottom = 20 - menu_height = text_height + 2 * margin_top_bottom + menu_height = len(options) * (button_height + 10) + 2 * margin_top_bottom menu_x = (config.screen_width - menu_width) // 2 menu_y = (config.screen_height - menu_height) // 2 - pygame.draw.rect(screen, (50, 50, 50, 200), (menu_x, menu_y, menu_width, menu_height), border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), (menu_x, menu_y, menu_width, menu_height), 2, border_radius=10) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (menu_x, menu_y, menu_width, menu_height), border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], (menu_x, menu_y, menu_width, menu_height), 2, border_radius=12) for i, option in enumerate(options): - color = (0, 150, 255) if i == selected_option else (255, 255, 255) - text_surface = config.font.render(option, True, color) - text_rect = text_surface.get_rect(center=(config.screen_width // 2, menu_y + margin_top_bottom + i * line_height + line_height // 2)) - screen.blit(text_surface, text_rect) + draw_stylized_button( + screen, + option, + menu_x + 20, + menu_y + margin_top_bottom + i * (button_height + 10), + menu_width - 40, + button_height, + selected=i == selected_option + ) -#Menu aide controles +# Menu aide contrôles def draw_controls_help(screen, previous_state): - """Affiche la liste des contrôles pour l'état précédent du menu.""" + """Affiche la liste des contrôles avec un style moderne.""" common_controls = { "confirm": lambda action: f"{get_control_display('confirm', 'Entrée/A')} : {action}", "cancel": lambda action: f"{get_control_display('cancel', 'Échap/B')} : {action}", @@ -866,110 +940,104 @@ def draw_controls_help(screen, previous_state): popup_x = (config.screen_width - popup_width) // 2 popup_y = (config.screen_height - popup_height) // 2 + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (popup_x, popup_y, popup_width, popup_height), border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], (popup_x, popup_y, popup_width, popup_height), 2, border_radius=12) + for i, line in enumerate(wrapped_controls): - text = config.font.render(line, True, (255, 255, 255)) + text = config.font.render(line, True, THEME_COLORS["text"]) text_rect = text.get_rect(center=(config.screen_width // 2, popup_y + 40 + i * line_height)) screen.blit(text, text_rect) - -#Menu Quitter Appli +# Menu Quitter Appli def draw_confirm_dialog(screen): """Affiche la boîte de dialogue de confirmation pour quitter.""" global OVERLAY - #logger.debug("Rendu de draw_confirm_dialog") - # Vérifier si OVERLAY est valide, sinon le recréer if OVERLAY is None or OVERLAY.get_size() != (config.screen_width, config.screen_height): OVERLAY = pygame.Surface((config.screen_width, config.screen_height), pygame.SRCALPHA) - OVERLAY.fill((0, 0, 0, 128)) + OVERLAY.fill((0, 0, 0, 150)) logger.debug("OVERLAY recréé dans draw_confirm_dialog") - + screen.blit(OVERLAY, (0, 0)) message = "Quitter l'application ?" wrapped_message = wrap_text(message, config.font, config.screen_width - 80) line_height = config.font.get_height() + 5 text_height = len(wrapped_message) * line_height - button_height = line_height + 20 + button_height = int(config.screen_height * 0.0463) margin_top_bottom = 20 rect_height = text_height + button_height + 2 * margin_top_bottom max_text_width = max([config.font.size(line)[0] for line in wrapped_message], default=300) - rect_width = max_text_width + 40 + rect_width = max_text_width + 150 rect_x = (config.screen_width - rect_width) // 2 rect_y = (config.screen_height - rect_height) // 2 - pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) + + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12) + for i, line in enumerate(wrapped_message): - text = config.font.render(line, True, (255, 255, 255)) + text = config.font.render(line, True, THEME_COLORS["text"]) text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) screen.blit(text, text_rect) - yes_text = config.font.render("Oui", True, (0, 150, 255) if config.confirm_selection == 1 else (255, 255, 255)) - no_text = config.font.render("Non", True, (0, 150, 255) if config.confirm_selection == 0 else (255, 255, 255)) - yes_rect = yes_text.get_rect(center=(config.screen_width // 2 - 100, rect_y + text_height + margin_top_bottom + line_height // 2)) - no_rect = no_text.get_rect(center=(config.screen_width // 2 + 100, rect_y + text_height + margin_top_bottom + line_height // 2)) - screen.blit(yes_text, yes_rect) - screen.blit(no_text, no_rect) -#draw_redownload_game_cache_dialog + draw_stylized_button(screen, "Oui", rect_x + rect_width // 2 - 180, rect_y + text_height + margin_top_bottom, 160, button_height, selected=config.confirm_selection == 1) + draw_stylized_button(screen, "Non", rect_x + rect_width // 2 + 20, rect_y + text_height + margin_top_bottom, 160, button_height, selected=config.confirm_selection == 0) + +# draw_redownload_game_cache_dialog def draw_redownload_game_cache_dialog(screen): """Affiche la boîte de dialogue de confirmation pour retélécharger le cache des jeux.""" global OVERLAY - #logger.debug("Rendu de draw_redownload_game_cache_dialog") - # Vérifier si OVERLAY est valide, sinon le recréer if OVERLAY is None or OVERLAY.get_size() != (config.screen_width, config.screen_height): OVERLAY = pygame.Surface((config.screen_width, config.screen_height), pygame.SRCALPHA) - OVERLAY.fill((0, 0, 0, 128)) + OVERLAY.fill((0, 0, 0, 150)) logger.debug("OVERLAY recréé dans draw_redownload_game_cache_dialog") - + screen.blit(OVERLAY, (0, 0)) message = "Retélécharger le cache des jeux ?" wrapped_message = wrap_text(message, config.small_font, config.screen_width - 80) line_height = config.small_font.get_height() + 5 text_height = len(wrapped_message) * line_height - button_height = line_height + 20 + button_height = int(config.screen_height * 0.0463) margin_top_bottom = 20 rect_height = text_height + button_height + 2 * margin_top_bottom max_text_width = max([config.small_font.size(line)[0] for line in wrapped_message], default=300) - rect_width = max_text_width + 40 + rect_width = max_text_width + 80 rect_x = (config.screen_width - rect_width) // 2 rect_y = (config.screen_height - rect_height) // 2 - pygame.draw.rect(screen, (50, 50, 50, 200), (rect_x, rect_y, rect_width, rect_height), border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), (rect_x, rect_y, rect_width, rect_height), 2, border_radius=10) - + + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12) + for i, line in enumerate(wrapped_message): - text = config.small_font.render(line, True, (255, 255, 255)) + text = config.small_font.render(line, True, THEME_COLORS["text"]) text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2)) screen.blit(text, text_rect) - yes_text = config.small_font.render("Oui", True, (0, 150, 255) if config.redownload_confirm_selection == 1 else (255, 255, 255)) - no_text = config.small_font.render("Non", True, (0, 150, 255) if config.redownload_confirm_selection == 0 else (255, 255, 255)) - yes_rect = yes_text.get_rect(center=(config.screen_width // 2 - 100, rect_y + text_height + margin_top_bottom + line_height // 2)) - no_rect = no_text.get_rect(center=(config.screen_width // 2 + 100, rect_y + text_height + margin_top_bottom + line_height // 2)) - screen.blit(yes_text, yes_rect) - screen.blit(no_text, no_rect) + draw_stylized_button(screen, "Oui", rect_x + rect_width // 2 - 180, rect_y + text_height + margin_top_bottom, 160, button_height, selected=config.redownload_confirm_selection == 1) + draw_stylized_button(screen, "Non", rect_x + rect_width // 2 + 20, rect_y + text_height + margin_top_bottom, 160, button_height, selected=config.redownload_confirm_selection == 0) +# Popup avec compte à rebours def draw_popup(screen): """Dessine un popup avec un message et un compte à rebours.""" screen.blit(OVERLAY, (0, 0)) - - popup_width = int(config.screen_width * 0.8) # ~400px pour 1920p + + popup_width = int(config.screen_width * 0.8) line_height = config.small_font.get_height() + 10 text_lines = config.popup_message.split('\n') text_height = len(text_lines) * line_height margin_top_bottom = 20 - popup_height = text_height + 2 * margin_top_bottom + line_height # Espace pour le compte à rebours + popup_height = text_height + 2 * margin_top_bottom + line_height popup_x = (config.screen_width - popup_width) // 2 popup_y = (config.screen_height - popup_height) // 2 - pygame.draw.rect(screen, (50, 50, 50, 200), (popup_x, popup_y, popup_width, popup_height), border_radius=10) - pygame.draw.rect(screen, (255, 255, 255), (popup_x, popup_y, popup_width, popup_height), 2, border_radius=10) + pygame.draw.rect(screen, THEME_COLORS["button_idle"], (popup_x, popup_y, popup_width, popup_height), border_radius=12) + pygame.draw.rect(screen, THEME_COLORS["border"], (popup_x, popup_y, popup_width, popup_height), 2, border_radius=12) for i, line in enumerate(text_lines): - text_surface = config.small_font.render(line, True, (255, 255, 255)) + text_surface = config.small_font.render(line, True, THEME_COLORS["text"]) text_rect = text_surface.get_rect(center=(config.screen_width // 2, popup_y + margin_top_bottom + i * line_height + line_height // 2)) screen.blit(text_surface, text_rect) - - # Afficher le compte à rebours - remaining_time = max(0, config.popup_timer // 1000) # Convertir ms en secondes + + remaining_time = max(0, config.popup_timer // 1000) countdown_text = f"Ce message se fermera dans {remaining_time} seconde{'s' if remaining_time != 1 else ''}" - countdown_surface = config.small_font.render(countdown_text, True, (255, 255, 255)) + countdown_surface = config.small_font.render(countdown_text, True, THEME_COLORS["text"]) countdown_rect = countdown_surface.get_rect(center=(config.screen_width // 2, popup_y + margin_top_bottom + len(text_lines) * line_height + line_height // 2)) screen.blit(countdown_surface, countdown_rect) \ No newline at end of file diff --git a/network.py b/network.py index 0d1974e..25438a4 100644 --- a/network.py +++ b/network.py @@ -7,7 +7,6 @@ import pygame # type: ignore import zipfile import json import time -from urllib.parse import urljoin, unquote import asyncio import config from utils import sanitize_filename @@ -346,7 +345,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False): if os.path.exists(dest_path): os.remove(dest_path) result[0] = False - result[1] = str(e) + result[1] = f"Erreur téléchargement {game_name}" finally: logger.debug(f"Thread téléchargement terminé pour {url}") with lock: diff --git a/rom_extensions.json b/rom_extensions.json index d7b916a..bdf3fb8 100644 --- a/rom_extensions.json +++ b/rom_extensions.json @@ -2146,15 +2146,7 @@ ".elf", ".dol", ".m3u", - ".xci" - ] - }, - { - "system": "switch", - "folder": "/userdata/roms/switch", - "extensions": [ - ".nsp", - ".xci" + ".json" ] }, { diff --git a/utils.py b/utils.py index 8b4b55c..918ac11 100644 --- a/utils.py +++ b/utils.py @@ -98,7 +98,7 @@ def sanitize_filename(name): return re.sub(r'[<>:"/\/\\|?*]', '_', name).strip() def wrap_text(text, font, max_width): - """Divise le texte en lignes pour respecter la largeur maximale.""" + """Divise le texte en lignes pour respecter la largeur maximale, en coupant les mots longs si nécessaire.""" if not isinstance(text, str): text = str(text) if text is not None else "" @@ -107,15 +107,29 @@ def wrap_text(text, font, max_width): current_line = '' for word in words: - # Tester si ajouter le mot dépasse la largeur - test_line = current_line + (' ' if current_line else '') + word - test_surface = font.render(test_line, True, (255, 255, 255)) - if test_surface.get_width() <= max_width: - current_line = test_line + # Si le mot seul dépasse max_width, le couper caractère par caractère + if font.render(word, True, (255, 255, 255)).get_width() > max_width: + temp_line = current_line + for char in word: + test_line = temp_line + (' ' if temp_line else '') + char + test_surface = font.render(test_line, True, (255, 255, 255)) + if test_surface.get_width() <= max_width: + temp_line = test_line + else: + if temp_line: + lines.append(temp_line) + temp_line = char + current_line = temp_line else: - if current_line: - lines.append(current_line) - current_line = word + # Comportement standard pour les mots normaux + test_line = current_line + (' ' if current_line else '') + word + test_surface = font.render(test_line, True, (255, 255, 255)) + if test_surface.get_width() <= max_width: + current_line = test_line + else: + if current_line: + lines.append(current_line) + current_line = word if current_line: lines.append(current_line)