1
0
forked from Mirrors/RGSX

Ajout fonction 1fichier avec api + fonction de mise a jour du cache des jeux et corrections de bugs

This commit is contained in:
skymike03
2025-07-09 01:13:55 +02:00
parent c89d7100ef
commit 849f469455
9 changed files with 4794 additions and 442 deletions

View File

@@ -1,6 +1,6 @@
import os
os.environ["SDL_FBDEV"] = "/dev/fb0"
import pygame
import pygame # type: ignore
import asyncio
import platform
import subprocess
@@ -9,7 +9,7 @@ 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_scrollbar, draw_confirm_dialog, draw_controls, draw_gradient, draw_virtual_keyboard, draw_popup_message, draw_extension_warning, draw_pause_menu, draw_controls_help, draw_game_list, draw_history, draw_clear_history_dialog
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 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
@@ -44,8 +44,8 @@ OTA_data_ZIP = f"{OTA_SERVER_URL}/rgsx-data.zip"
# Constantes pour la répétition automatique dans pause_menu
REPEAT_DELAY = 300 # Délai initial avant répétition (ms)
REPEAT_INTERVAL = 100 # Intervalle entre répétitions (ms)
REPEAT_ACTION_DEBOUNCE = 50 # Délai anti-rebond pour répétitions (ms)
REPEAT_INTERVAL = 150 # Intervalle entre répétitions (ms), augmenté pour réduire la fréquence
REPEAT_ACTION_DEBOUNCE = 100 # Délai anti-rebond pour répétitions (ms), augmenté pour éviter les répétitions excessives
# Initialisation de Pygame et des polices
pygame.init()
@@ -276,6 +276,20 @@ async def main():
if config.menu_state == "download_progress" and current_time - last_redraw_time >= 100:
config.needs_redraw = True
last_redraw_time = current_time
# Dans __main__.py, dans la boucle principale
current_time = pygame.time.get_ticks()
delta_time = current_time - config.last_frame_time
config.last_frame_time = current_time
if config.menu_state == "restart_popup" and config.popup_timer > 0:
config.popup_timer -= delta_time
config.needs_redraw = True
if config.popup_timer <= 0:
config.menu_state = validate_menu_state(config.previous_menu_state)
config.popup_message = ""
config.popup_timer = 0
config.needs_redraw = True
logger.debug(f"Fermeture automatique du popup, retour à {config.menu_state}")
# Gestion des événements
events = pygame.event.get()
@@ -298,117 +312,15 @@ async def main():
if config.menu_state not in ["pause_menu", "controls_help", "controls_mapping", "history", "confirm_clear_history"]:
config.previous_menu_state = config.menu_state
config.menu_state = "pause_menu"
config.selected_pause_option = 0
config.selected_option = 0
config.needs_redraw = True
logger.debug(f"Ouverture menu pause depuis {config.previous_menu_state}")
continue
if config.menu_state == "pause_menu":
current_time = pygame.time.get_ticks()
if event.type in (pygame.KEYDOWN, pygame.JOYBUTTONDOWN, pygame.JOYAXISMOTION, pygame.JOYHATMOTION):
up_config = config.controls_config.get("up", {})
down_config = config.controls_config.get("down", {})
confirm_config = config.controls_config.get("confirm", {})
cancel_config = config.controls_config.get("cancel", {})
if current_time - config.last_state_change_time < config.debounce_delay:
continue
if (
(event.type == pygame.KEYDOWN and up_config and event.key == up_config.get("value")) or
(event.type == pygame.JOYBUTTONDOWN and up_config and up_config.get("type") == "button" and event.button == up_config.get("value")) or
(event.type == pygame.JOYAXISMOTION and up_config and up_config.get("type") == "axis" and event.axis == up_config.get("value")[0] and abs(event.value) > 0.5 and (1 if event.value > 0 else -1) == up_config.get("value")[1]) or
(event.type == pygame.JOYHATMOTION and up_config and up_config.get("type") == "hat" and event.value == tuple(up_config.get("value")))
):
config.selected_pause_option = max(0, config.selected_pause_option - 1)
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
logger.debug(f"Menu pause: Haut, selected_option={config.selected_pause_option}, repeat_action={config.repeat_action}")
elif (
(event.type == pygame.KEYDOWN and down_config and event.key == down_config.get("value")) or
(event.type == pygame.JOYBUTTONDOWN and down_config and down_config.get("type") == "button" and event.button == down_config.get("value")) or
(event.type == pygame.JOYAXISMOTION and down_config and down_config.get("type") == "axis" and event.axis == down_config.get("value")[0] and abs(event.value) > 0.5 and (1 if event.value > 0 else -1) == down_config.get("value")[1]) or
(event.type == pygame.JOYHATMOTION and down_config and down_config.get("type") == "hat" and event.value == tuple(down_config.get("value")))
):
config.selected_pause_option = min(3, config.selected_pause_option + 1)
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
logger.debug(f"Menu pause: Bas, selected_option={config.selected_pause_option}, repeat_action={config.repeat_action}")
elif (
(event.type == pygame.KEYDOWN and confirm_config and event.key == confirm_config.get("value")) or
(event.type == pygame.JOYBUTTONDOWN and confirm_config and confirm_config.get("type") == "button" and event.button == confirm_config.get("value")) or
(event.type == pygame.JOYAXISMOTION and confirm_config and confirm_config.get("type") == "axis" and event.axis == confirm_config.get("value")[0] and abs(event.value) > 0.5 and (1 if event.value > 0 else -1) == confirm_config.get("value")[1]) or
(event.type == pygame.JOYHATMOTION and confirm_config and confirm_config.get("type") == "hat" and event.value == tuple(confirm_config.get("value")))
):
if config.selected_pause_option == 0:
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
config.menu_state = "controls_help"
config.needs_redraw = True
logger.debug("Menu pause: Aide sélectionnée")
elif config.selected_pause_option == 1:
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
if map_controls(screen):
config.menu_state = config.previous_menu_state if config.previous_menu_state in ["platform", "game", "download_progress", "download_result", "confirm_exit", "extension_warning", "history"] else "platform"
config.controls_config = load_controls_config()
logger.debug(f"Mappage des contrôles terminé, retour à {config.menu_state}")
else:
config.menu_state = "error"
config.error_message = "Échec du mappage des contrôles"
config.needs_redraw = True
logger.debug("Échec du mappage des contrôles")
elif config.selected_pause_option == 2:
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
config.menu_state = "history"
config.current_history_item = 0
config.history_scroll_offset = 0
config.needs_redraw = True
logger.debug("Menu pause: Historique sélectionné")
elif config.selected_pause_option == 3:
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
config.menu_state = "confirm_exit"
config.confirm_selection = 0
config.needs_redraw = True
logger.debug("Menu pause: Quitter sélectionné")
elif (
(event.type == pygame.KEYDOWN and cancel_config and event.key == cancel_config.get("value")) or
(event.type == pygame.JOYBUTTONDOWN and cancel_config and cancel_config.get("type") == "button" and event.button == cancel_config.get("value")) or
(event.type == pygame.JOYAXISMOTION and cancel_config and cancel_config.get("type") == "axis" and event.axis == cancel_config.get("value")[0] and abs(event.value) > 0.5 and (1 if event.value > 0 else -1) == cancel_config.get("value")[1]) or
(event.type == pygame.JOYHATMOTION and cancel_config and cancel_config.get("type") == "hat" and event.value == tuple(cancel_config.get("value")))
):
config.menu_state = config.previous_menu_state if config.previous_menu_state in ["platform", "game", "download_progress", "download_result", "confirm_exit", "extension_warning", "history"] else "platform"
config.needs_redraw = True
logger.debug(f"Menu pause: Annulation, retour à {config.menu_state}")
elif event.type in (pygame.KEYUP, pygame.JOYBUTTONUP):
if (
(event.type == pygame.KEYUP and is_input_matched(event, "up") or is_input_matched(event, "down")) or
(event.type == pygame.JOYBUTTONUP and is_input_matched(event, "up") or is_input_matched(event, "down"))
):
config.repeat_action = None
config.repeat_key = None
config.repeat_start_time = 0
config.needs_redraw = True
logger.debug("Menu pause: Touche relâchée, répétition arrêtée")
if config.repeat_action in ["up", "down"] and current_time >= config.repeat_start_time:
if current_time - config.repeat_last_action < REPEAT_ACTION_DEBOUNCE:
continue
config.repeat_last_action = current_time
if config.repeat_action == "up":
config.selected_pause_option = max(0, config.selected_pause_option - 1)
config.needs_redraw = True
logger.debug(f"Menu pause: Répétition haut, selected_option={config.selected_pause_option}")
elif config.repeat_action == "down":
config.selected_pause_option = min(3, config.selected_pause_option + 1)
config.needs_redraw = True
logger.debug(f"Menu pause: Répétition bas, selected_option={config.selected_pause_option}")
config.repeat_start_time = current_time + REPEAT_INTERVAL
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
logger.debug(f"Événement transmis à handle_controls dans pause_menu: {event.type}")
continue
@@ -432,6 +344,11 @@ async def main():
config.needs_redraw = True
logger.debug(f"Événement transmis à handle_controls dans confirm_clear_history: {event.type}")
continue
if config.menu_state == "redownload_game_cache":
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
logger.debug(f"Événement transmis à handle_controls dans redownload_game_cache: {event.type}")
continue
if config.menu_state in ["platform", "game", "error", "confirm_exit", "download_progress", "download_result", "extension_warning", "history"]:
action = handle_controls(event, sources, joystick, screen)
@@ -522,43 +439,50 @@ async def main():
draw_gradient(screen, (28, 37, 38), (47, 59, 61))
if config.menu_state == "controls_mapping":
draw_controls_mapping(screen, ACTIONS[0], None, False, 0.0)
logger.debug("Rendu initial de draw_controls_mapping")
# logger.debug("Rendu initial de draw_controls_mapping")
elif config.menu_state == "loading":
draw_loading_screen(screen)
logger.debug("Rendu de draw_loading_screen")
# logger.debug("Rendu de draw_loading_screen")
elif config.menu_state == "error":
draw_error_screen(screen)
logger.debug("Rendu de draw_error_screen")
# logger.debug("Rendu de draw_error_screen")
elif config.menu_state == "platform":
draw_platform_grid(screen)
logger.debug("Rendu de draw_platform_grid")
# logger.debug("Rendu de draw_platform_grid")
elif config.menu_state == "game":
draw_game_list(screen)
logger.debug("Rendu de draw_game_list")
# logger.debug("Rendu de draw_game_list")
elif config.menu_state == "download_progress":
draw_progress_screen(screen)
logger.debug("Rendu de draw_progress_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)
logger.debug("Rendu de draw_popup_message")
# logger.debug("Rendu de draw_popup_message")
elif config.menu_state == "confirm_exit":
draw_confirm_dialog(screen)
logger.debug("Rendu de draw_confirm_dialog")
# logger.debug("Rendu de draw_confirm_dialog")
elif config.menu_state == "extension_warning":
draw_extension_warning(screen)
logger.debug("Rendu de draw_extension_warning")
# logger.debug("Rendu de draw_extension_warning")
elif config.menu_state == "pause_menu":
draw_pause_menu(screen, config.selected_pause_option)
draw_pause_menu(screen, config.selected_option)
logger.debug("Rendu de draw_pause_menu")
elif config.menu_state == "controls_help":
draw_controls_help(screen, config.previous_menu_state)
logger.debug("Rendu de draw_controls_help")
# logger.debug("Rendu de draw_controls_help")
elif config.menu_state == "history":
draw_history(screen)
logger.debug("Rendu de draw_history")
draw_history_list(screen)
# logger.debug("Rendu de draw_history_list")
elif config.menu_state == "confirm_clear_history":
draw_clear_history_dialog(screen)
logger.debug("Rendu de confirm_clear_history")
# logger.debug("Rendu de confirm_clear_history")
elif config.menu_state == "redownload_game_cache":
draw_redownload_game_cache_dialog(screen) # Fonction existante
elif config.menu_state == "restart_popup":
draw_popup(screen) # Nouvelle fonction
elif config.menu_state == "confirm_clear_history":
draw_clear_history_dialog(screen) # Fonction existante
else:
# Gestion des états non valides
config.menu_state = "platform"
@@ -624,14 +548,14 @@ async def main():
logger.debug(f"Erreur OTA : {message}")
else:
loading_step = "check_data"
config.current_loading_system = "Téléchargement des données ..."
config.current_loading_system = "Téléchargement des jeux et images ..."
config.loading_progress = 10.0
config.needs_redraw = True
logger.debug(f"Étape chargement : {loading_step}, progress={config.loading_progress}")
elif loading_step == "check_data":
games_data_dir = "/userdata/roms/ports/RGSX/games"
is_data_empty = not os.path.exists(games_data_dir) or not any(os.scandir(games_data_dir))
logger.debug(f"Dossier Data directory {games_data_dir} is {'empty' if is_data_empty else 'not empty'}")
#logger.debug(f"Dossier Data directory {games_data_dir} is {'empty' if is_data_empty else 'not empty'}")
if is_data_empty:
config.current_loading_system = "Téléchargement du Dossier Data initial..."

View File

@@ -1,11 +1,11 @@
import pygame
import pygame # type: ignore
import os
import logging
logger = logging.getLogger(__name__)
# Version actuelle de l'application
app_version = "1.5.0"
app_version = "1.7.0"
# Variables d'état
platforms = []
@@ -43,7 +43,7 @@ filter_active = False
extension_confirm_selection = 0
pending_download = None
controls_config = {}
selected_pause_option = 0
selected_option = 0
previous_menu_state = None
history = [] # Liste des entrées de l'historique
current_history_item = 0 # Index de l'élément sélectionné dans l'historique
@@ -55,10 +55,13 @@ debounce_delay = 200 # Délai de debounce en millisecondes
platform_dicts = [] # Liste des dictionnaires de plateformes
selected_key = (0, 0) # Position du curseur dans le clavier virtuel
is_non_pc = True # Indicateur pour plateforme non-PC (par exemple, console)
redownload_confirm_selection = 0 # Sélection pour la confirmation de redownload
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()
# Résolution de l'écran
screen_width = 800
screen_height = 600
# 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
"""Largeur de l'écran en pixels."""
SCREEN_HEIGHT = 600
@@ -100,9 +103,24 @@ def init_font():
small_font = None
def validate_resolution():
"""Valide la résolution de l'écran par rapport aux capacités du matériel."""
"""Valide la résolution de l'écran par rapport aux capacités de l'écran."""
display_info = pygame.display.Info()
if SCREEN_WIDTH > display_info.current_w or SCREEN_HEIGHT > display_info.current_h:
logger.warning(f"Résolution {SCREEN_WIDTH}x{SCREEN_HEIGHT} dépasse les limites de l'écran")
return display_info.current_w, display_info.current_h
return SCREEN_WIDTH, SCREEN_HEIGHT
return SCREEN_WIDTH, SCREEN_HEIGHT
def load_api_key_1fichier():
"""Charge la clé API 1fichier depuis /userdata/saves/ports/rgsx/1fichierAPI.txt, crée le fichier si absent."""
api_path = "/userdata/saves/ports/rgsx/1fichierAPI.txt"
if not os.path.exists(api_path):
# Crée le fichier vide si absent
with open(api_path, "w") as f:
f.write("")
return ""
with open(api_path, "r") as f:
key = f.read().strip()
return key
API_KEY_1FICHIER = load_api_key_1fichier()

View File

@@ -1,11 +1,13 @@
import pygame
import shutil
import pygame # type: ignore
import config
from config import CONTROLS_CONFIG_PATH
import asyncio
import math
import json
import os
from display import draw_validation_transition
from network import download_rom, check_extension_before_download
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
@@ -24,7 +26,7 @@ REPEAT_ACTION_DEBOUNCE = 50 # Délai anti-rebond pour répétitions up/down/lef
VALID_STATES = [
"platform", "game", "download_progress", "download_result", "confirm_exit",
"extension_warning", "pause_menu", "controls_help", "history", "remap_controls",
"error", "loading", "confirm_clear_history" # Ajout du nouvel état
"redownload_game_cache", "restart_popup", "error", "loading", "confirm_clear_history"
]
def validate_menu_state(state):
@@ -63,7 +65,8 @@ def load_controls_config(path=CONTROLS_CONFIG_PATH):
"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_t},
"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},
@@ -86,22 +89,22 @@ def is_input_matched(event, action_name):
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}")
#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}")
#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:
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}")
#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}")
#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}")
#logger.debug(f"Vérification mouse: event_button={event_button}, input_value={input_value}")
return event_button == input_value
return False
@@ -114,14 +117,14 @@ 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}")
#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)}")
#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):
@@ -143,12 +146,12 @@ def handle_controls(event, sources, joystick, screen):
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)}")
#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", "history", "remap_controls"):
if is_input_matched(event, "start") and config.menu_state not in ("pause_menu", "controls_help", "remap_controls", "redownload_game_cache"):
config.previous_menu_state = config.menu_state
config.menu_state = "pause_menu"
config.selected_option = 0
@@ -159,13 +162,9 @@ def handle_controls(event, sources, joystick, screen):
# Erreur
if config.menu_state == "error":
if is_input_matched(event, "confirm"):
config.menu_state = "loading"
config.needs_redraw = True
logger.debug("Sortie erreur avec Confirm")
elif is_input_matched(event, "cancel"):
config.menu_state = "confirm_exit"
config.confirm_selection = 0
config.menu_state = validate_menu_state(config.previous_menu_state)
config.needs_redraw = True
logger.debug("Sortie du menu erreur avec Confirm")
# Plateformes
elif config.menu_state == "platform":
@@ -235,7 +234,7 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_start_time = 0
config.repeat_last_action = current_time
config.needs_redraw = True
logger.debug("Page suivante, répétition réinitialisée")
#logger.debug("Page suivante, répétition réinitialisée")
elif is_input_matched(event, "page_up"):
if config.current_page > 0:
config.current_page -= 1
@@ -247,12 +246,16 @@ def handle_controls(event, sources, joystick, screen):
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")
#logger.debug("Page précédente, répétition réinitialisée")
elif is_input_matched(event, "progress"):
if config.download_tasks:
config.menu_state = "download_progress"
config.needs_redraw = True
logger.debug("Retour à download_progress depuis platform")
elif is_input_matched(event, "history"):
config.menu_state = "history"
config.needs_redraw = True
logger.debug("Ouverture history depuis platform")
elif is_input_matched(event, "confirm"):
if config.platforms:
config.current_platform = config.selected_platform
@@ -264,7 +267,7 @@ def handle_controls(event, sources, joystick, screen):
draw_validation_transition(screen, config.current_platform)
config.menu_state = "game"
config.needs_redraw = True
logger.debug(f"Plateforme sélectionnée: {config.platforms[config.current_platform]}, {len(config.games)} jeux chargés")
#logger.debug(f"Plateforme sélectionnée: {config.platforms[config.current_platform]}, {len(config.games)} jeux chargés")
elif is_input_matched(event, "cancel"):
config.menu_state = "confirm_exit"
config.confirm_selection = 0
@@ -329,14 +332,14 @@ def handle_controls(event, sources, joystick, screen):
config.current_game = 0
config.scroll_offset = 0
config.needs_redraw = True
logger.debug(f"Suppression caractère: query={config.search_query}, jeux filtrés={len(config.filtered_games)}")
#logger.debug(f"Suppression caractère: query={config.search_query}, jeux filtrés={len(config.filtered_games)}")
elif is_input_matched(event, "space"):
config.search_query += " "
config.filtered_games = [game for game in config.games if config.search_query.lower() in game[0].lower()]
config.current_game = 0
config.scroll_offset = 0
config.needs_redraw = True
logger.debug(f"Espace ajouté: query={config.search_query}, jeux filtrés={len(config.filtered_games)}")
#logger.debug(f"Espace ajouté: query={config.search_query}, jeux filtrés={len(config.filtered_games)}")
elif is_input_matched(event, "cancel"):
config.search_mode = False
config.search_query = ""
@@ -346,7 +349,45 @@ def handle_controls(event, sources, joystick, screen):
config.scroll_offset = 0
config.needs_redraw = True
logger.debug("Sortie du mode recherche")
elif config.search_mode and not config.is_non_pc:
# Gestion de la recherche sur PC
if event.type == pygame.KEYDOWN:
# Saisie de texte alphanumérique
if event.unicode.isalnum() or event.unicode == ' ':
config.search_query += event.unicode
config.filtered_games = [game for game in config.games if config.search_query.lower() in game[0].lower()]
config.current_game = 0
config.scroll_offset = 0
config.needs_redraw = True
logger.debug(f"Recherche mise à jour: query={config.search_query}, jeux filtrés={len(config.filtered_games)}")
# Gestion de la suppression
elif is_input_matched(event, "delete"):
if config.search_query:
config.search_query = config.search_query[:-1]
config.filtered_games = [game for game in config.games if config.search_query.lower() in game[0].lower()]
config.current_game = 0
config.scroll_offset = 0
config.needs_redraw = True
logger.debug(f"Suppression caractère: query={config.search_query}, jeux filtrés={len(config.filtered_games)}")
# Gestion de la validation
elif is_input_matched(event, "confirm"):
config.search_mode = False
config.filter_active = True # Conserver le filtre actif
config.current_game = 0
config.scroll_offset = 0
config.needs_redraw = True
logger.debug(f"Validation de la recherche: query={config.search_query}, jeux filtrés={len(config.filtered_games)}")
# Gestion de l'annulation
elif is_input_matched(event, "cancel"):
config.search_mode = False
config.search_query = ""
config.filtered_games = config.games
config.current_game = 0
config.scroll_offset = 0
config.needs_redraw = True
logger.debug("Sortie du mode recherche")
else:
if is_input_matched(event, "up"):
if config.current_game > 0:
config.current_game -= 1
@@ -370,7 +411,7 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_start_time = 0
config.repeat_last_action = current_time
config.needs_redraw = True
logger.debug("Page précédente dans la liste des jeux")
#logger.debug("Page précédente dans la liste des jeux")
elif is_input_matched(event, "page_down"):
config.current_game = min(len(games) - 1, config.current_game + config.visible_games)
config.repeat_action = None
@@ -378,7 +419,7 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_start_time = 0
config.repeat_last_action = current_time
config.needs_redraw = True
logger.debug("Page suivante dans la liste des jeux")
#logger.debug("Page suivante dans la liste des jeux")
elif is_input_matched(event, "filter"):
config.search_mode = True
config.search_query = ""
@@ -390,26 +431,56 @@ def handle_controls(event, sources, joystick, screen):
logger.debug("Entrée en mode recherche")
elif is_input_matched(event, "progress"):
if config.download_tasks:
config.previous_menu_state = config.menu_state
config.menu_state = "download_progress"
config.needs_redraw = True
logger.debug("Retour à download_progress depuis game")
logger.debug(f"Retour à download_progress depuis {config.previous_menu_state}")
elif is_input_matched(event, "history"):
config.menu_state = "history"
config.needs_redraw = True
logger.debug("Ouverture history depuis game")
elif is_input_matched(event, "confirm"):
if games:
config.pending_download = check_extension_before_download(games[config.current_game][0], config.platforms[config.current_platform], games[config.current_game][1])
config.pending_download = check_extension_before_download(
games[config.current_game][0],
config.platforms[config.current_platform],
games[config.current_game][1]
)
if config.pending_download:
url, platform, game_name, is_zip_non_supported = config.pending_download
if is_zip_non_supported:
is_supported = is_extension_supported(
sanitize_filename(game_name),
platform,
load_extensions_json()
)
if not is_supported:
config.previous_menu_state = config.menu_state # Ajouter cette ligne
config.menu_state = "extension_warning"
config.extension_confirm_selection = 0
config.needs_redraw = True
logger.debug(f"Extension non supportée, passage à extension_warning pour {game_name}")
else:
task = asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported))
config.download_tasks[task] = (task, url, game_name, platform) # Stocker tuple de 4 éléments
if is_1fichier_url(url):
if not config.API_KEY_1FICHIER:
config.previous_menu_state = config.menu_state # Ajouter cette ligne
config.menu_state = "error"
config.error_message = (
"Attention il faut renseigner sa clé API (premium only) dans le fichier /userdata/saves/ports/rgsx/1fichier.api à ouvrir dans un editeur de texte et coller la clé API"
)
config.needs_redraw = True
logger.error("Clé API 1fichier absente, téléchargement impossible.")
config.pending_download = None
return action
loop = asyncio.get_running_loop()
task = loop.run_in_executor(None, download_from_1fichier, url, platform, game_name, is_zip_non_supported)
else:
task = asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported))
config.download_tasks[task] = (task, url, game_name, platform)
config.previous_menu_state = config.menu_state # Ajouter cette ligne
config.menu_state = "download_progress"
config.needs_redraw = True
logger.debug(f"Début du téléchargement: {game_name} pour {platform} depuis {url}")
config.pending_download = None # Réinitialiser après démarrage
config.pending_download = None
action = "download"
else:
config.menu_state = "error"
@@ -423,7 +494,11 @@ def handle_controls(event, sources, joystick, screen):
config.scroll_offset = 0
config.needs_redraw = True
logger.debug("Retour à platform")
elif is_input_matched(event, "redownload_game_cache"):
config.previous_menu_state = config.menu_state
config.menu_state = "redownload_game_cache"
config.needs_redraw = True
logger.debug("Passage à redownload_game_cache depuis game")
elif config.menu_state == "history":
history = config.history
if is_input_matched(event, "up"):
@@ -449,7 +524,7 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_start_time = 0
config.repeat_last_action = current_time
config.needs_redraw = True
logger.debug("Page précédente dans l'historique")
#logger.debug("Page précédente dans l'historique")
elif is_input_matched(event, "page_down"):
config.current_history_item = min(len(history) - 1, config.current_history_item + config.visible_history_items)
config.repeat_action = None
@@ -457,7 +532,7 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_start_time = 0
config.repeat_last_action = current_time
config.needs_redraw = True
logger.debug("Page suivante dans l'historique")
#logger.debug("Page suivante dans l'historique")
elif is_input_matched(event, "progress"):
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
config.menu_state = "confirm_clear_history"
@@ -469,14 +544,13 @@ def handle_controls(event, sources, joystick, screen):
entry = history[config.current_history_item]
platform = entry["platform"]
game_name = entry["game_name"]
# Rechercher l'URL dans config.games
for game in config.games:
if game[0] == game_name and config.platforms[config.current_platform] == platform:
config.pending_download = check_extension_before_download(game_name, platform, game[1])
if config.pending_download:
url, platform, game_name, is_zip_non_supported = config.pending_download
if is_zip_non_supported:
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
config.previous_menu_state = config.menu_state # Remplacer cette ligne
config.menu_state = "extension_warning"
config.extension_confirm_selection = 0
config.needs_redraw = True
@@ -484,7 +558,7 @@ def handle_controls(event, sources, joystick, screen):
else:
task = asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported))
config.download_tasks[task] = (task, url, game_name, platform)
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
config.previous_menu_state = config.menu_state # Remplacer cette ligne
config.menu_state = "download_progress"
config.needs_redraw = True
logger.debug(f"Retéléchargement: {game_name} pour {platform} depuis {url}")
@@ -503,6 +577,7 @@ def handle_controls(event, sources, joystick, screen):
config.history_scroll_offset = 0
config.needs_redraw = True
logger.debug(f"Retour à {config.menu_state} depuis history")
# Ajouter un nouvel état "confirm_clear_history" après l'état "confirm_exit"
elif config.menu_state == "confirm_clear_history":
logger.debug(f"État confirm_clear_history, confirm_clear_selection={config.confirm_clear_selection}, événement={event.type}, valeur={getattr(event, 'value', None)}")
@@ -521,20 +596,21 @@ def handle_controls(event, sources, joystick, screen):
config.needs_redraw = True
logger.debug("Annulation du vidage de l'historique, retour à history")
elif is_input_matched(event, "left"):
logger.debug(f"Action left détectée dans confirm_clear_history")
#logger.debug(f"Action left détectée dans confirm_clear_history")
config.confirm_clear_selection = 1 # Sélectionner "Non"
config.needs_redraw = True
logger.debug(f"Changement sélection confirm_clear_history: {config.confirm_clear_selection}")
#logger.debug(f"Changement sélection confirm_clear_history: {config.confirm_clear_selection}")
elif is_input_matched(event, "right"):
logger.debug(f"Action right détectée dans confirm_clear_history")
#logger.debug(f"Action right détectée dans confirm_clear_history")
config.confirm_clear_selection = 0 # Sélectionner "Oui"
config.needs_redraw = True
logger.debug(f"Changement sélection confirm_clear_history: {config.confirm_clear_selection}")
#logger.debug(f"Changement sélection confirm_clear_history: {config.confirm_clear_selection}")
elif is_input_matched(event, "cancel"):
logger.debug(f"Action cancel détectée dans confirm_clear_history")
#logger.debug(f"Action cancel détectée dans confirm_clear_history")
config.menu_state = "history"
config.needs_redraw = True
logger.debug("Annulation du vidage de l'historique, retour à history")
# Progression téléchargement
elif config.menu_state == "download_progress":
if is_input_matched(event, "cancel"):
@@ -572,7 +648,7 @@ def handle_controls(event, sources, joystick, screen):
elif is_input_matched(event, "left") or is_input_matched(event, "right"):
config.confirm_selection = 1 - config.confirm_selection
config.needs_redraw = True
logger.debug(f"Changement sélection confirm_exit: {config.confirm_selection}")
#logger.debug(f"Changement sélection confirm_exit: {config.confirm_selection}")
# Avertissement extension
elif config.menu_state == "extension_warning":
@@ -602,15 +678,15 @@ def handle_controls(event, sources, joystick, screen):
elif is_input_matched(event, "left") or is_input_matched(event, "right"):
config.extension_confirm_selection = 1 - config.extension_confirm_selection
config.needs_redraw = True
logger.debug(f"Changement sélection extension_warning: {config.extension_confirm_selection}")
#logger.debug(f"Changement sélection extension_warning: {config.extension_confirm_selection}")
elif is_input_matched(event, "cancel"):
config.pending_download = None
config.menu_state = validate_menu_state(config.previous_menu_state)
config.needs_redraw = True
logger.debug(f"Retour à {config.menu_state} depuis extension_warning")
# Menu pause
elif config.menu_state == "pause_menu":
logger.debug(f"État pause_menu, selected_option={config.selected_option}, événement={event.type}, valeur={getattr(event, 'value', None)}")
if is_input_matched(event, "up"):
config.selected_option = max(0, config.selected_option - 1)
config.repeat_action = "up"
@@ -618,14 +694,17 @@ def handle_controls(event, sources, joystick, screen):
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
logger.debug(f"Navigation vers le haut: selected_option={config.selected_option}")
elif is_input_matched(event, "down"):
config.selected_option = min(3, config.selected_option + 1)
config.selected_option = min(4, config.selected_option + 1)
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
logger.debug(f"Navigation vers le bas: selected_option={config.selected_option}")
elif is_input_matched(event, "confirm"):
logger.debug(f"Confirmation dans pause_menu avec selected_option={config.selected_option}")
if config.selected_option == 0: # Controls
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
config.menu_state = "controls_help"
@@ -644,7 +723,13 @@ def handle_controls(event, sources, joystick, screen):
config.menu_state = "history"
config.needs_redraw = True
logger.debug(f"Passage à history depuis pause_menu")
elif config.selected_option == 3: # Quit
elif config.selected_option == 3: # Redownload game cache
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
config.menu_state = "redownload_game_cache"
config.redownload_confirm_selection = 0
config.needs_redraw = True
logger.debug(f"Passage à redownload_game_cache depuis pause_menu")
elif config.selected_option == 4: # Quit
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
config.menu_state = "confirm_exit"
config.confirm_selection = 0
@@ -668,6 +753,66 @@ def handle_controls(event, sources, joystick, screen):
config.menu_state = "pause_menu"
config.needs_redraw = True
logger.debug("Retour à pause_menu depuis remap_controls")
elif config.menu_state == "redownload_game_cache":
if is_input_matched(event, "left") or is_input_matched(event, "right"):
config.redownload_confirm_selection = 1 - config.redownload_confirm_selection
config.needs_redraw = True
logger.debug(f"Changement sélection redownload_game_cache: {config.redownload_confirm_selection}")
elif is_input_matched(event, "confirm"):
logger.debug(f"Action confirm dans redownload_game_cache, sélection={config.redownload_confirm_selection}")
if config.redownload_confirm_selection == 1: # Oui
logger.debug("Début du redownload des jeux")
config.download_tasks.clear()
config.download_progress.clear()
config.pending_download = None
if os.path.exists("/userdata/roms/ports/RGSX/sources.json"):
try:
os.remove("/userdata/roms/ports/RGSX/sources.json")
logger.debug("Fichier sources.json supprimé avec succès")
if os.path.exists("/userdata/roms/ports/RGSX/games"):
shutil.rmtree("/userdata/roms/ports/RGSX/games")
logger.debug("Dossier games supprimé avec succès")
if os.path.exists("/userdata/roms/ports/RGSX/images"):
shutil.rmtree("/userdata/roms/ports/RGSX/images")
logger.debug("Dossier images supprimé avec succès")
config.menu_state = "restart_popup"
config.popup_message = "Redownload des jeux effectué.\nVeuillez redémarrer l'application pour voir les changements."
config.popup_timer = 5000 # 5 secondes
config.needs_redraw = True
logger.debug("Passage à restart_popup")
except Exception as e:
logger.error(f"Erreur lors de la suppression du fichier sources.json ou dossiers: {e}")
config.menu_state = "error"
config.error_message = "Erreur lors de la suppression du fichier sources.json ou dossiers"
config.needs_redraw = True
return action
else:
logger.debug("Fichier sources.json non trouvé, passage à restart_popup")
config.menu_state = "restart_popup"
config.popup_message = "Aucun cache trouvé.\nVeuillez redémarrer l'application pour charger les jeux."
config.popup_timer = 5000 # 5 secondes
config.needs_redraw = True
logger.debug("Passage à restart_popup")
else: # Non
config.menu_state = validate_menu_state(config.previous_menu_state)
config.needs_redraw = True
logger.debug(f"Annulation du redownload, retour à {config.menu_state}")
elif is_input_matched(event, "cancel"):
config.menu_state = validate_menu_state(config.previous_menu_state)
config.needs_redraw = True
logger.debug(f"Retour à {config.menu_state} depuis redownload_game_cache")
# Popup de redémarrage
elif config.menu_state == "restart_popup":
if is_input_matched(event, "confirm") or is_input_matched(event, "cancel"):
config.menu_state = validate_menu_state(config.previous_menu_state)
config.popup_message = ""
config.popup_timer = 0
config.needs_redraw = True
logger.debug(f"Retour manuel à {config.menu_state} depuis restart_popup")
# Gestion de la répétition automatique (relâchement)
if event.type in (pygame.KEYUP, pygame.JOYBUTTONUP, pygame.JOYAXISMOTION, pygame.JOYHATMOTION):
@@ -678,7 +823,7 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_action = None
config.repeat_key = None
config.repeat_start_time = 0
logger.debug("Répétition arrêtée")
#logger.debug("Répétition arrêtée")
return action

View File

@@ -1,4 +1,4 @@
import pygame
import pygame # type: ignore
import json
import os
import logging
@@ -19,9 +19,10 @@ ACTIONS = [
{"name": "down", "display": "Bas", "description": "Naviguer vers le bas"},
{"name": "left", "display": "Gauche", "description": "Naviguer à gauche"},
{"name": "right", "display": "Droite", "description": "Naviguer à droite"},
{"name": "page_up", "display": "Page Précédente", "description": "Page précédente (ex: PageUp, LB)"},
{"name": "page_down", "display": "Page Suivante", "description": "Page suivante (ex: PageDown, RB)"},
{"name": "page_up", "display": "Page Précédente", "description": "Page précédente/Défilement Rapide Haut (ex: PageUp, LB)"},
{"name": "page_down", "display": "Page Suivante", "description": "Page suivante/Défilement Rapide Bas (ex: PageDown, RB)"},
{"name": "progress", "display": "Progression", "description": "Voir progression (ex: X)"},
{"name": "history", "display": "Historique", "description": "Ouvrir l'historique (ex: H, Y)"},
{"name": "filter", "display": "Filtrer", "description": "Ouvrir filtre (ex: F, Select)"},
{"name": "delete", "display": "Supprimer", "description": "Supprimer caractère (ex: LT, Suppr)"},
{"name": "space", "display": "Espace", "description": "Ajouter espace (ex: RT, Espace)"},
@@ -200,7 +201,7 @@ MOUSE_BUTTON_NAMES = {
HOLD_DURATION = 1000
def load_controls_config():
"""Charge la configuration des contrôles depuis controls.json."""
#Charge la configuration des contrôles depuis controls.json
try:
if os.path.exists(CONTROLS_CONFIG_PATH):
with open(CONTROLS_CONFIG_PATH, "r") as f:
@@ -215,7 +216,7 @@ def load_controls_config():
return {}
def save_controls_config(controls_config):
"""Enregistre la configuration des contrôles dans controls.json."""
#Enregistre la configuration des contrôles dans controls.json
try:
os.makedirs(os.path.dirname(CONTROLS_CONFIG_PATH), exist_ok=True)
with open(CONTROLS_CONFIG_PATH, "w") as f:
@@ -225,7 +226,7 @@ def save_controls_config(controls_config):
logger.error(f"Erreur lors de l'enregistrement de controls.json : {e}")
def get_readable_input_name(event):
"""Retourne un nom lisible pour une entrée (touche, bouton, axe, hat, ou souris)."""
#Retourne un nom lisible pour une entrée (touche, bouton, axe, hat, ou souris)
if event.type == pygame.KEYDOWN:
key_value = SDL_TO_PYGAME_KEY.get(event.key, event.key)
return KEY_NAMES.get(key_value, pygame.key.name(key_value) or f"Touche {key_value}")
@@ -240,7 +241,6 @@ def get_readable_input_name(event):
return MOUSE_BUTTON_NAMES.get(event.button, f"Souris Bouton {event.button}")
return "Inconnu"
ACTIONS = ["start", "confirm", "cancel"]
def map_controls(screen):
mapping = True
@@ -249,7 +249,7 @@ def map_controls(screen):
while mapping:
clock.tick(100) # 100 FPS
for event in pygame.event.get():
"""Interface de mappage des contrôles avec validation par maintien de 3 secondes."""
#Interface de mappage des contrôles avec validation par maintien de 3 secondes
controls_config = load_controls_config()
current_action_index = 0
current_input = None
@@ -411,21 +411,21 @@ def map_controls(screen):
pass
def save_controls_config(config):
"""Enregistre la configuration des contrôles dans un fichier JSON."""
#Enregistre la configuration des contrôles dans un fichier JSON
try:
with open(CONTROLS_CONFIG_PATH, "w") as f:
json.dump(config, f, indent=4)
logging.debug("Configuration des contrôles enregistrée")
logger.debug("Configuration des contrôles enregistrée")
except Exception as e:
logging.error(f"Erreur lors de l'enregistrement de controls.json : {e}")
logger.error(f"Erreur lors de l'enregistrement de controls.json : {e}")
return False
return True
def draw_controls_mapping(screen, action, last_input, waiting_for_input, hold_progress):
"""Affiche l'interface de mappage des contrôles avec une barre de progression pour le maintien."""
#Affiche l'interface de mappage des contrôles avec une barre de progression pour le maintien
draw_gradient(screen, (28, 37, 38), (47, 59, 61))
max_width = config.screen_width // 1.2
max_width = config.screen_width // 1.2
padding_horizontal = 40
padding_vertical = 30
padding_between = 10
@@ -434,18 +434,18 @@ def draw_controls_mapping(screen, action, last_input, waiting_for_input, hold_pr
shadow_offset = 8
# Instructions
instruction_text = f"Maintenez une touche/bouton pendant 3s pour '{action['display']}'"
instruction_text = f"Maintenez pendant 3s pour : '{action['display']}'"
description_text = action['description']
skip_text = "Appuyez sur Échap pour passer"
instruction_surface = config.font.render(instruction_text, True, (255, 255, 255))
description_surface = config.font.render(description_text, True, (200, 200, 200))
skip_text = "Appuyez sur Échap pour passer(Pc only)"
instruction_surface = config.small_font.render(instruction_text, True, (255, 255, 255))
description_surface = config.small_font.render(description_text, True, (200, 200, 200))
skip_surface = config.font.render(skip_text, True, (255, 255, 255))
instruction_width, instruction_height = instruction_surface.get_size()
description_width, description_height = description_surface.get_size()
skip_width, skip_height = skip_surface.get_size()
# Input détecté
input_text = last_input or (f"En attente d'une entrée..." if waiting_for_input else "Maintenez une touche/bouton")
input_text = last_input or (f"Attente..." if waiting_for_input else "Maintenez 3s")
input_surface = config.font.render(input_text, True, (0, 255, 0) if last_input else (255, 255, 255))
input_width, input_height = input_surface.get_size()
@@ -483,7 +483,7 @@ def draw_controls_mapping(screen, action, last_input, waiting_for_input, hold_pr
input_rect = input_surface.get_rect(center=(config.screen_width // 2, start_y + input_height // 2))
screen.blit(input_surface, input_rect)
start_y += input_height + padding_between
skip_rect = skip_surface.get_rect(center=(config.screen_width // 2, start_y + skip_height // 2))
skip_rect = skip_surface.get_rect(center=(config.screen_width // 2, start_y + skip_height // 2))
screen.blit(skip_surface, skip_rect)
# Barre de progression pour le maintien
@@ -491,7 +491,7 @@ def draw_controls_mapping(screen, action, last_input, waiting_for_input, hold_pr
bar_height = 20
bar_x = (config.screen_width - bar_width) // 2
bar_y = start_y + skip_height + 20
pygame.draw.rect(screen, (100, 100, 100), (bar_x, bar_y, bar_width, bar_height))
pygame.draw.rect(screen, (100, 100, 100), (bar_x, bar_y, bar_width, bar_height))
progress_width = bar_width * hold_progress
pygame.draw.rect(screen, (0, 255, 0), (bar_x, bar_y, progress_width, bar_height))
pygame.draw.rect(screen, (0, 255, 0), (bar_x, bar_y, progress_width, bar_height))
pygame.draw.rect(screen, (255, 255, 255), (bar_x, bar_y, bar_width, bar_height), 2)

View File

@@ -1,15 +1,15 @@
import pygame
import pygame # type: ignore
import config
import math
from utils import truncate_text_end, wrap_text, load_system_image, load_games
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__)
# Cache global pour l'overlay semi-transparent
OVERLAY = None # Initialisé dans init_display()
#Général, résolution, overlay
def init_display():
"""Initialise l'écran et les ressources globales."""
global OVERLAY
@@ -26,6 +26,7 @@ def init_display():
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()
@@ -36,6 +37,30 @@ 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
def draw_validation_transition(screen, platform_index):
"""Affiche une animation de transition pour la sélection dune 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 = [
@@ -52,7 +77,7 @@ def draw_loading_screen(screen):
border_width = 3
shadow_offset = 6
line_height = config.font.get_height() + padding_between
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
@@ -73,9 +98,9 @@ def draw_loading_screen(screen):
max_text_width = rect_width - 2 * padding_vertical
for i, line in enumerate(disclaimer_lines):
wrapped_lines = wrap_text(line, config.font, max_text_width)
wrapped_lines = wrap_text(line, config.small_font, max_text_width)
for j, wrapped_line in enumerate(wrapped_lines):
text_surface = config.font.render(wrapped_line, True, (255, 255, 255))
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
@@ -83,11 +108,11 @@ def draw_loading_screen(screen):
screen.blit(text_surface, text_rect)
loading_y = rect_y + rect_height + int(config.screen_height * 0.0926) # ~100px pour 1080p
text = config.font.render(truncate_text_end(f"{config.current_loading_system}", config.font, config.screen_width - 2 * margin_horizontal), True, (255, 255, 255))
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.font.render(f"Progression : {int(config.loading_progress)}%", True, (255, 255, 255))
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)
@@ -97,6 +122,7 @@ def draw_loading_screen(screen):
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 derreur."""
error_font = pygame.font.SysFont("arial", 28)
@@ -106,10 +132,20 @@ def draw_error_screen(screen):
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)
retry_text = config.font.render(f"{get_control_display('confirm', 'Entrée/A')} : retenter, {get_control_display('cancel', 'Échap/B')} : quitter", True, (255, 255, 255))
retry_rect = retry_text.get_rect(center=(config.screen_width // 2, config.screen_height // 2 + int(config.screen_height * 0.0926))) # ~100px pour 1080p
screen.blit(retry_text, retry_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
@@ -149,7 +185,7 @@ 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)}")
#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):
@@ -196,9 +232,10 @@ def draw_platform_grid(screen):
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")
#logger.debug("Début de draw_game_list")
platform = config.platforms[config.current_platform]
platform_name = config.platform_names.get(platform, platform)
@@ -228,25 +265,26 @@ def draw_game_list(screen):
screen.blit(text_surface, text_rect)
return
line_height = config.font.get_height() + 10
line_height = config.small_font.get_height() + 10
#header_height = line_height
margin_top_bottom = 20
extra_margin_top = 5
extra_margin_bottom = 40
title_height = max(config.title_font.get_height(), config.search_font.get_height(), config.small_font.get_height()) + 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
games_per_page = available_height // line_height
config.visible_games = games_per_page # Mettre à jour config.visible_games
max_text_width = max([config.font.size(truncate_text_end(game[0] if isinstance(game, (list, tuple)) else game, config.font, config.screen_width - 80))[0] for game in games], default=300)
rect_width = max_text_width + 40
rect_height = games_per_page * line_height + 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) - games_per_page)))
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 + games_per_page:
config.scroll_offset = config.current_game - games_per_page + 1
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))
@@ -281,33 +319,68 @@ def draw_game_list(screen):
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 + games_per_page, len(games))):
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_end(game_name, config.font, config.screen_width - 80)
text_surface = config.font.render(game_text, True, color)
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}")
#logger.debug(f"Jeu affiché : texte={game_text}, position={text_rect}, selected={i == config.current_game}")
draw_scrollbar(screen)
if config.search_mode and config.is_non_pc:
draw_virtual_keyboard(screen)
# 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."""
logger.debug("Début de draw_history_list")
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
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
@@ -324,33 +397,26 @@ def draw_history_list(screen):
screen.blit(text_surface, text_rect)
return
line_height = config.small_font.get_height() + 10
margin_top_bottom = 20
extra_margin_top = 5
extra_margin_bottom = 40
title_height = config.title_font.get_height() + 20
# 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
config.visible_history_items = items_per_page # Mettre à jour config.visible_history_items
# Calculer la largeur des colonnes
col_platform_width = config.screen_width // 4 # ~25% pour le système
col_game_width = config.screen_width // 2 # ~50% pour le nom du jeu
col_status_width = config.screen_width // 4 # ~25% pour l'état
max_text_width = col_platform_width + col_game_width + col_status_width
rect_width = max_text_width + 40
rect_height = items_per_page * line_height + 2 * margin_top_bottom
# 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))
@@ -360,12 +426,13 @@ def draw_history_list(screen):
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 - line_height // 2
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,
@@ -376,18 +443,18 @@ def draw_history_list(screen):
text_rect = text_surface.get_rect(center=(x_pos, header_y))
screen.blit(text_surface, text_rect)
# Lignes du tableau
for i in range(config.history_scroll_offset, min(config.history_scroll_offset + items_per_page, len(history))):
# 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["platform"]
game_name = entry["game_name"]
status = entry["status"]
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_end(platform, config.small_font, col_platform_width - 10)
game_text = truncate_text_end(game_name, config.small_font, col_game_width - 10)
status_text = truncate_text_end(status, config.small_font, col_status_width - 10)
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 + (i - config.history_scroll_offset + 1) * line_height + line_height // 2
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)
@@ -399,20 +466,68 @@ def draw_history_list(screen):
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}")
#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}")
draw_history_scrollbar(screen)
# 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)}")
def draw_history_scrollbar(screen):
"""Affiche la barre de défilement pour l'historique."""
if len(config.history) <= config.visible_history_items:
#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_history_items / len(config.history))
scrollbar_y = 120 + (game_area_height - scrollbar_height) * (config.history_scroll_offset / max(1, len(config.history) - config.visible_history_items))
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 = [
@@ -450,9 +565,10 @@ def draw_virtual_keyboard(screen):
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")
#logger.debug("Début de draw_progress_screen")
if not config.download_tasks:
logger.debug("Aucune tâche de téléchargement active")
@@ -466,11 +582,11 @@ 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}")
#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_end(game_name, config.font, config.screen_width - 200)}"
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
@@ -491,7 +607,7 @@ def draw_progress_screen(screen):
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()}")
#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
@@ -500,7 +616,7 @@ def draw_progress_screen(screen):
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}")
#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)
@@ -512,64 +628,25 @@ def draw_progress_screen(screen):
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)
logger.debug(f"Texte de progression affiché : texte={line}, position={percent_rect}, taille={percent_render.get_size()}")
def draw_scrollbar(screen):
"""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) * (config.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))
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)
#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))
wrapped_message = wrap_text(message, config.font, config.screen_width - 80)
line_height = config.font.get_height() + 5
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.font.render(line, True, (255, 0, 0) if is_error else (0, 255, 0))
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")
#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")
@@ -612,7 +689,7 @@ def draw_extension_warning(screen):
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])]}")
#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"
@@ -625,7 +702,7 @@ def draw_extension_warning(screen):
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}")
#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)}")
@@ -647,10 +724,11 @@ def draw_extension_warning(screen):
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"Menu {menu_state} - {start_button} : Options - History - Controls"
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
@@ -662,40 +740,20 @@ def draw_controls(screen, menu_state):
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)
def draw_validation_transition(screen, platform_index):
"""Affiche une animation de transition pour la sélection dune 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)
#Menu pause
def draw_pause_menu(screen, selected_option):
"""Dessine le menu pause avec les options Aide, Configurer contrôles, Historique, Quitter."""
"""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.2083) # ~400px pour 1920p
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
@@ -712,13 +770,7 @@ def draw_pause_menu(screen, selected_option):
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)
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)
#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 = {
@@ -731,6 +783,7 @@ def draw_controls_help(screen, previous_state):
"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"
}
@@ -744,11 +797,13 @@ def draw_controls_help(screen, previous_state):
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"{'Valider' if config.search_mode else 'Télécharger'}"),
common_controls["cancel"](f"{'Annuler' if config.search_mode else 'Retour'}"),
common_controls["history"]("Historique"),
*( [
common_controls["delete"](),
common_controls["space"]()
@@ -814,63 +869,13 @@ def draw_controls_help(screen, previous_state):
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)
def draw_history(screen):
"""Affiche la liste de l'historique des téléchargements."""
if not config.history:
text = config.font.render("Aucun téléchargement dans l'historique", True, (255, 255, 255))
screen.blit(text, (config.screen_width // 2 - text.get_width() // 2, config.screen_height // 2))
return
# Calculer le nombre d'éléments visibles
item_height = config.small_font.get_height() + 10
config.visible_history_items = (config.screen_height - 200) // item_height
max_scroll = max(0, len(config.history) - config.visible_history_items)
config.history_scroll_offset = max(0, min(config.history_scroll_offset, max_scroll))
# Cadre semi-transparent
panel_width = config.screen_width - 100
panel_height = config.visible_history_items * item_height + 20
panel_x = (config.screen_width - panel_width) // 2
panel_y = (config.screen_height - panel_height) // 2
panel_surface = pygame.Surface((panel_width, panel_height), pygame.SRCALPHA)
panel_surface.fill((0, 0, 0, 128))
screen.blit(panel_surface, (panel_x, panel_y))
pygame.draw.rect(screen, (255, 255, 255), (panel_x, panel_y, panel_width, panel_height), 2)
# Afficher les colonnes
headers = ["Système", "Nom du jeu", "État"]
col_widths = [panel_width // 4, panel_width // 2, panel_width // 4]
header_y = panel_y + 10
for i, header in enumerate(headers):
text = config.small_font.render(header, True, (255, 255, 255))
screen.blit(text, (panel_x + sum(col_widths[:i]) + 10, header_y))
# Afficher les entrées
start_index = config.history_scroll_offset
end_index = min(start_index + config.visible_history_items, len(config.history))
for i, entry in enumerate(config.history[start_index:end_index]):
y = panel_y + 40 + i * item_height
color = (255, 255, 0) if i + start_index == config.current_history_item else (255, 255, 255)
system = config.platform_names.get(entry["platform"], entry["platform"])
system_text = truncate_text_end(system, config.small_font, col_widths[0] - 20)
game_text = truncate_text_end(entry["game_name"], config.small_font, col_widths[1] - 20)
status_text = entry["status"]
texts = [system_text, game_text, status_text]
for j, text in enumerate(texts):
rendered = config.small_font.render(text, True, color)
screen.blit(rendered, (panel_x + sum(col_widths[:j]) + 10, y))
if i + start_index == config.current_history_item:
pygame.draw.rect(screen, (255, 255, 0), (panel_x + 5, y - 5, panel_width - 10, item_height), 1)
# Barre de défilement
if len(config.history) > config.visible_history_items:
draw_scrollbar(screen, config.history_scroll_offset, len(config.history), config.visible_history_items, panel_x + panel_width - 10, panel_y, panel_height)
#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")
#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)
@@ -902,34 +907,68 @@ def draw_confirm_dialog(screen):
screen.blit(yes_text, yes_rect)
screen.blit(no_text, no_rect)
def draw_clear_history_dialog(screen):
"""Affiche la boîte de dialogue de confirmation pour vider l'historique."""
#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 = "Vider l'historique ?"
wrapped_message = wrap_text(message, config.font, config.screen_width - 80)
line_height = config.font.get_height() + 5
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.font.size(line)[0] for line in wrapped_message], default=300)
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.font.render(line, True, (255, 255, 255))
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.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_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)
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)

View File

@@ -3,7 +3,7 @@ import subprocess
import re
import os
import threading
import pygame
import pygame # type: ignore
import zipfile
import json
import time
@@ -313,7 +313,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False):
config.download_progress[url]["status"] = "Téléchargement"
config.download_progress[url]["progress_percent"] = (downloaded / total_size * 100) if total_size > 0 else 0
config.needs_redraw = True # Forcer le redraw
logger.debug(f"Progression: {downloaded}/{total_size} octets, {config.download_progress[url]['progress_percent']:.1f}%")
#logger.debug(f"Progression: {downloaded}/{total_size} octets, {config.download_progress[url]['progress_percent']:.1f}%")
if is_zip_non_supported:
with lock:
@@ -356,7 +356,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False):
config.menu_state = "download_result"
config.needs_redraw = True # Forcer le redraw
# Enregistrement dans l'historique
add_to_history(platform, game_name, "Download_OK" if result[0] else "Erreur")
add_to_history(platform, game_name, "OK" if result[0] else "Error")
config.history = load_history() # Recharger l'historique
logger.debug(f"Enregistrement dans l'historique: platform={platform}, game_name={game_name}, status={'Download_OK' if result[0] else 'Erreur'}")
@@ -387,12 +387,196 @@ def check_extension_before_download(game_name, platform, url):
if is_supported:
logger.debug(f"L'extension de {sanitized_name} est supportée pour {platform}")
return (url, platform, game_name, False)
elif is_archive:
logger.debug(f"Archive {extension.upper()} détectée pour {sanitized_name}, extraction automatique prévue")
return (url, platform, game_name, True)
else:
if is_archive:
logger.debug(f"Fichier {extension.upper()} détecté pour {sanitized_name}, extraction automatique prévue")
return (url, platform, game_name, True)
logger.debug(f"L'extension de {sanitized_name} n'est pas supportée pour {platform}")
return None
logger.debug(f"Extension non supportée ({extension}) pour {sanitized_name}, avertissement affiché")
return (url, platform, game_name, False)
except Exception as e:
logger.error(f"Erreur vérification extension {url}: {str(e)}")
return None
return None
def is_1fichier_url(url):
"""Détecte si l'URL est un lien 1fichier."""
return "1fichier.com" in url
def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False):
"""Télécharge un fichier depuis 1fichier en utilisant l'API officielle."""
logger.debug(f"Début téléchargement 1fichier: {game_name} depuis {url}, is_zip_non_supported={is_zip_non_supported}")
result = [None, None]
def download_thread():
logger.debug(f"Thread téléchargement 1fichier démarré pour {url}")
try:
# Nettoyer l'URL
link = url.split('&af=')[0]
# Déterminer le répertoire de destination
dest_dir = None
for platform_dict in config.platform_dicts:
if platform_dict["platform"] == platform:
dest_dir = platform_dict.get("folder")
break
if not dest_dir:
logger.warning(f"Aucun dossier 'folder' trouvé pour la plateforme {platform}")
dest_dir = os.path.join("/userdata/roms", platform)
logger.debug(f"Vérification répertoire destination: {dest_dir}")
os.makedirs(dest_dir, exist_ok=True)
if not os.access(dest_dir, os.W_OK):
raise PermissionError(f"Pas de permission d'écriture dans {dest_dir}")
# Préparer les en-têtes et le payload
headers = {
"Authorization": f"Bearer {config.API_KEY_1FICHIER}",
"Content-Type": "application/json"
}
payload = {
"url": link,
"pretty": 1
}
# Étape 1 : Obtenir les informations du fichier
logger.debug(f"Envoi requête POST à https://api.1fichier.com/v1/file/info.cgi pour {url}")
response = requests.post("https://api.1fichier.com/v1/file/info.cgi", headers=headers, json=payload, timeout=30)
logger.debug(f"Réponse reçue, status: {response.status_code}")
response.raise_for_status()
file_info = response.json()
if "error" in file_info and file_info["error"] == "Resource not found":
logger.error(f"Le fichier {game_name} n'existe pas sur 1fichier")
result[0] = False
result[1] = f"Le fichier {game_name} n'existe pas"
return
filename = file_info.get("filename", "").strip()
if not filename:
logger.error("Impossible de récupérer le nom du fichier")
result[0] = False
result[1] = "Impossible de récupérer le nom du fichier"
return
sanitized_filename = sanitize_filename(filename)
dest_path = os.path.join(dest_dir, sanitized_filename)
logger.debug(f"Chemin destination: {dest_path}")
# Étape 2 : Obtenir le jeton de téléchargement
logger.debug(f"Envoi requête POST à https://api.1fichier.com/v1/download/get_token.cgi pour {url}")
response = requests.post("https://api.1fichier.com/v1/download/get_token.cgi", headers=headers, json=payload, timeout=30)
logger.debug(f"Réponse reçue, status: {response.status_code}")
response.raise_for_status()
download_info = response.json()
final_url = download_info.get("url")
if not final_url:
logger.error("Impossible de récupérer l'URL de téléchargement")
result[0] = False
result[1] = "Impossible de récupérer l'URL de téléchargement"
return
# Étape 3 : Initialiser la progression
lock = threading.Lock()
with lock:
config.download_progress[url] = {
"downloaded_size": 0,
"total_size": 0,
"status": "Téléchargement",
"progress_percent": 0,
"game_name": game_name
}
config.needs_redraw = True
logger.debug(f"Progression initialisée pour {url}")
# Étape 4 : Télécharger le fichier
retries = 10
retry_delay = 10
for attempt in range(retries):
try:
logger.debug(f"Tentative {attempt + 1} : Envoi requête GET à {final_url}")
with requests.get(final_url, stream=True, headers={'User-Agent': 'Mozilla/5.0'}, timeout=30) as response:
logger.debug(f"Réponse reçue, status: {response.status_code}")
response.raise_for_status()
total_size = int(response.headers.get('content-length', 0))
logger.debug(f"Taille totale: {total_size} octets")
with lock:
config.download_progress[url]["total_size"] = total_size
config.needs_redraw = True
downloaded = 0
with open(dest_path, 'wb') as f:
logger.debug(f"Ouverture fichier: {dest_path}")
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
downloaded += len(chunk)
with lock:
config.download_progress[url]["downloaded_size"] = downloaded
config.download_progress[url]["status"] = "Téléchargement"
config.download_progress[url]["progress_percent"] = (downloaded / total_size * 100) if total_size > 0 else 0
config.needs_redraw = True
#logger.debug(f"Progression: {downloaded}/{total_size} octets, {config.download_progress[url]['progress_percent']:.1f}%")
# Étape 5 : Extraire si nécessaire
if is_zip_non_supported:
with lock:
config.download_progress[url]["downloaded_size"] = 0
config.download_progress[url]["total_size"] = 0
config.download_progress[url]["status"] = "Extracting"
config.download_progress[url]["progress_percent"] = 0
config.needs_redraw = True
extension = os.path.splitext(dest_path)[1].lower()
if extension == ".zip":
success, msg = extract_zip(dest_path, dest_dir, url)
elif extension == ".rar":
success, msg = extract_rar(dest_path, dest_dir, url)
else:
raise Exception(f"Type d'archive non supporté: {extension}")
if not success:
raise Exception(f"Échec de l'extraction de l'archive: {msg}")
result[0] = True
result[1] = f"Downloaded / extracted : {game_name}"
else:
os.chmod(dest_path, 0o644)
logger.debug(f"Téléchargement terminé: {dest_path}")
result[0] = True
result[1] = f"Download_OK : {game_name}"
return
except requests.exceptions.RequestException as e:
logger.error(f"Tentative {attempt + 1} échouée : {e}")
if attempt < retries - 1:
import time
time.sleep(retry_delay)
else:
logger.error("Nombre maximum de tentatives atteint")
result[0] = False
result[1] = f"Échec du téléchargement après {retries} tentatives"
return
except requests.exceptions.RequestException as e:
logger.error(f"Erreur API 1fichier : {e}")
result[0] = False
result[1] = f"Erreur lors de la requête API, la clé est peut etre incorrecte: {str(e)}"
finally:
logger.debug(f"Thread téléchargement 1fichier terminé pour {url}")
with lock:
config.download_result_message = result[1]
config.download_result_error = not result[0]
config.download_result_start_time = pygame.time.get_ticks()
config.menu_state = "download_result"
config.needs_redraw = True
# Enregistrement dans l'historique
add_to_history(platform, game_name, "Download_OK" if result[0] else "Erreur")
config.history = load_history()
logger.debug(f"Enregistrement dans l'historique: platform={platform}, game_name={game_name}, status={'Download_OK' if result[0] else 'Erreur'}")
thread = threading.Thread(target=download_thread)
logger.debug(f"Démarrage thread pour {url}")
thread.start()
thread.join()
logger.debug(f"Thread rejoint pour {url}")
return result[0], result[1]

3997
output.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2146,7 +2146,15 @@
".elf",
".dol",
".m3u",
".json"
".xci"
]
},
{
"system": "switch",
"folder": "/userdata/roms/switch",
"extensions": [
".nsp",
".xci"
]
},
{

View File

@@ -1,4 +1,4 @@
import pygame
import pygame # type: ignore
import re
import json
import os
@@ -25,18 +25,52 @@ def create_placeholder(width=400):
return placeholder
def truncate_text_middle(text, font, max_width):
"""Tronque le texte en insérant '...' au milieu."""
"""Tronque le texte en insérant '...' au milieu, en préservant le début et la fin, sans extension de fichier."""
# Supprimer l'extension de fichier
text = text.rsplit('.', 1)[0] if '.' in text else text
text_width = font.size(text)[0]
if text_width <= max_width:
return text
ellipsis = "..."
ellipsis_width = font.size(ellipsis)[0]
max_text_width = max_width - ellipsis_width
while text_width > max_text_width and len(text) > 0:
text = text[:-1]
text_width = font.size(text)[0]
mid = len(text) // 2
return text[:mid] + ellipsis + text[mid:]
if max_text_width <= 0:
return ellipsis
# Diviser la largeur disponible entre début et fin
chars = list(text)
left = []
right = []
left_width = 0
right_width = 0
left_idx = 0
right_idx = len(chars) - 1
while left_idx <= right_idx and (left_width + right_width) < max_text_width:
if left_idx < right_idx:
left.append(chars[left_idx])
left_width = font.size(''.join(left))[0]
if left_width + right_width > max_text_width:
left.pop()
break
left_idx += 1
if left_idx <= right_idx:
right.insert(0, chars[right_idx])
right_width = font.size(''.join(right))[0]
if left_width + right_width > max_text_width:
right.pop(0)
break
right_idx -= 1
# Reculer jusqu'à un espace pour éviter de couper un mot
while left and left[-1] != ' ' and left_width + right_width > max_text_width:
left.pop()
left_width = font.size(''.join(left))[0] if left else 0
while right and right[0] != ' ' and left_width + right_width > max_text_width:
right.pop(0)
right_width = font.size(''.join(right))[0] if right else 0
return ''.join(left).rstrip() + ellipsis + ''.join(right).lstrip()
def truncate_text_end(text, font, max_width):
"""Tronque le texte à la fin pour qu'il tienne dans max_width avec la police donnée."""
@@ -65,6 +99,9 @@ def sanitize_filename(name):
def wrap_text(text, font, max_width):
"""Divise le texte en lignes pour respecter la largeur maximale."""
if not isinstance(text, str):
text = str(text) if text is not None else ""
words = text.split(' ')
lines = []
current_line = ''