2025-07-06 19:47:21 +02:00
import pygame
import config
import math
from utils import truncate_text_end , wrap_text , load_system_image , load_games
import logging
logger = logging . getLogger ( __name__ )
2025-07-06 21:13:39 +02:00
# Cache global pour l'overlay semi-transparent
OVERLAY = pygame . Surface ( ( config . screen_width , config . screen_height ) , pygame . SRCALPHA )
OVERLAY . fill ( ( 0 , 0 , 0 , 180 ) )
2025-07-06 19:47:21 +02:00
def init_display ( ) :
""" Initialise l’ écran Pygame. """
screen = pygame . display . set_mode ( ( 0 , 0 ) , pygame . FULLSCREEN )
return screen
def draw_gradient ( screen , top_color , bottom_color ) :
""" Dessine un fond dégradé vertical. """
height = screen . get_height ( )
top_color = pygame . Color ( * top_color )
bottom_color = pygame . Color ( * bottom_color )
for y in range ( height ) :
ratio = y / height
color = top_color . lerp ( bottom_color , ratio )
pygame . draw . line ( screen , color , ( 0 , y ) , ( screen . get_width ( ) , y ) )
def draw_loading_screen ( screen ) :
""" Affiche l’ écran de chargement avec le disclaimer en haut, le texte de chargement et la barre de progression. """
disclaimer_lines = [
" Bienvenue dans RGSX " ,
" It ' s dangerous to go alone, take all you need! " ,
" Mais ne téléchargez que des jeux " ,
" dont vous possédez les originaux ! "
]
margin_horizontal = 20
padding_vertical = 20
padding_between = 8
border_radius = 16
border_width = 3
shadow_offset = 6
line_height = config . font . get_height ( ) + padding_between
total_height = line_height * len ( disclaimer_lines ) - padding_between
rect_width = config . screen_width - 2 * margin_horizontal
rect_height = total_height + 2 * padding_vertical
rect_x = margin_horizontal
rect_y = 20
shadow_rect = pygame . Rect ( rect_x + shadow_offset , rect_y + shadow_offset , rect_width , rect_height )
shadow_surface = pygame . Surface ( ( rect_width , rect_height ) , pygame . SRCALPHA )
pygame . draw . rect ( shadow_surface , ( 0 , 0 , 0 , 100 ) , shadow_surface . get_rect ( ) , border_radius = border_radius )
screen . blit ( shadow_surface , shadow_rect . topleft )
disclaimer_rect = pygame . Rect ( rect_x , rect_y , rect_width , rect_height )
disclaimer_surface = pygame . Surface ( ( rect_width , rect_height ) , pygame . SRCALPHA )
pygame . draw . rect ( disclaimer_surface , ( 30 , 30 , 30 , 220 ) , disclaimer_surface . get_rect ( ) , border_radius = border_radius )
screen . blit ( disclaimer_surface , disclaimer_rect . topleft )
pygame . draw . rect ( screen , ( 255 , 255 , 255 ) , disclaimer_rect , border_width , border_radius = border_radius )
for i , line in enumerate ( disclaimer_lines ) :
text_surface = config . font . render ( line , True , ( 255 , 255 , 255 ) )
text_rect = text_surface . get_rect ( center = (
config . screen_width / / 2 ,
rect_y + padding_vertical + ( i + 0.5 ) * line_height - padding_between / / 2
) )
screen . blit ( text_surface , text_rect )
loading_y = rect_y + rect_height + 100
text = config . font . render ( f " { config . current_loading_system } " , 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_rect = progress_text . get_rect ( center = ( config . screen_width / / 2 , loading_y + 50 ) )
screen . blit ( progress_text , progress_rect )
bar_width = 400
bar_height = 40
progress_width = ( bar_width * config . loading_progress ) / 100
pygame . draw . rect ( screen , ( 100 , 100 , 100 ) , ( config . screen_width / / 2 - bar_width / / 2 , loading_y + 100 , bar_width , bar_height ) )
pygame . draw . rect ( screen , ( 0 , 255 , 0 ) , ( config . screen_width / / 2 - bar_width / / 2 , loading_y + 100 , progress_width , bar_height ) )
def draw_error_screen ( screen ) :
""" Affiche l’ écran d’ erreur. """
error_font = pygame . font . SysFont ( " arial " , 28 )
text = error_font . render ( config . error_message , True , ( 255 , 0 , 0 ) )
text_rect = text . get_rect ( center = ( config . screen_width / / 2 , config . screen_height / / 2 ) )
screen . blit ( text , text_rect )
2025-07-06 21:13:39 +02:00
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 ) )
2025-07-06 19:47:21 +02:00
retry_rect = retry_text . get_rect ( center = ( config . screen_width / / 2 , config . screen_height / / 2 + 100 ) )
screen . blit ( retry_text , retry_rect )
def draw_platform_grid ( screen ) :
""" Affiche la grille des plateformes. """
margin_left = 50
margin_right = 50
margin_top = 120
margin_bottom = 70
num_cols = 3
num_rows = 3
systems_per_page = num_cols * num_rows
available_width = config . screen_width - margin_left - margin_right
available_height = config . screen_height - margin_top - margin_bottom
col_width = available_width / / num_cols
row_height = available_height / / num_rows
x_positions = [ margin_left + col_width * i + col_width / / 2 for i in range ( num_cols ) ]
y_positions = [ margin_top + row_height * i + row_height / / 2 for i in range ( num_rows ) ]
start_idx = config . current_page * systems_per_page
logger . debug ( f " Page { config . current_page } , start_idx: { start_idx } , total_platforms: { len ( config . platforms ) } " )
for idx in range ( start_idx , start_idx + systems_per_page ) :
if idx > = len ( config . platforms ) :
break
grid_idx = idx - start_idx
row = grid_idx / / num_cols
col = grid_idx % num_cols
x = x_positions [ col ]
y = y_positions [ row ]
scale = 1.5 if idx == config . selected_platform else 1.0
platform_dict = config . platform_dicts [ idx ]
image = load_system_image ( platform_dict )
if image :
# Calculer la taille en respectant le ratio d'origine
orig_width , orig_height = image . get_width ( ) , image . get_height ( )
max_size = int ( min ( col_width , row_height ) * scale * 0.9 )
ratio = min ( max_size / orig_width , max_size / orig_height )
new_width = int ( orig_width * ratio )
new_height = int ( orig_height * ratio )
image = pygame . transform . smoothscale ( image , ( new_width , new_height ) )
image_rect = image . get_rect ( center = ( x , y ) )
if idx == config . selected_platform :
neon_color = ( 0 , 255 , 255 )
border_radius = 24
padding = 24
rect_width = image_rect . width + 2 * padding
rect_height = image_rect . height + 2 * padding
neon_surface = pygame . Surface ( ( rect_width , rect_height ) , pygame . SRCALPHA )
pygame . draw . rect (
neon_surface ,
neon_color + ( 60 , ) ,
neon_surface . get_rect ( ) ,
width = 1 ,
border_radius = border_radius + 8
)
pygame . draw . rect (
neon_surface ,
neon_color + ( 180 , ) ,
neon_surface . get_rect ( ) . inflate ( - 8 , - 8 ) ,
width = 2 ,
border_radius = border_radius
)
screen . blit ( neon_surface , ( image_rect . left - padding , image_rect . top - padding ) , special_flags = pygame . BLEND_RGBA_ADD )
screen . blit ( image , image_rect )
def draw_virtual_keyboard ( screen ) :
""" Affiche un clavier virtuel pour la saisie dans search_mode, centré verticalement. """
keyboard_layout = [
[ ' 0 ' , ' 1 ' , ' 2 ' , ' 3 ' , ' 4 ' , ' 5 ' , ' 6 ' , ' 7 ' , ' 8 ' , ' 9 ' ] ,
[ ' A ' , ' Z ' , ' E ' , ' R ' , ' T ' , ' Y ' , ' U ' , ' I ' , ' O ' , ' P ' ] ,
[ ' Q ' , ' S ' , ' D ' , ' F ' , ' G ' , ' H ' , ' J ' , ' K ' , ' L ' , ' M ' ] ,
[ ' W ' , ' X ' , ' C ' , ' V ' , ' B ' , ' N ' ]
]
key_width = 60
key_height = 60
key_spacing = 10
keyboard_width = len ( keyboard_layout [ 0 ] ) * ( key_width + key_spacing ) - key_spacing
keyboard_height = len ( keyboard_layout ) * ( key_height + key_spacing ) - key_spacing
start_x = ( config . screen_width - keyboard_width ) / / 2
search_bottom_y = 120 + ( config . search_font . get_height ( ) + 40 ) / / 2
controls_y = config . screen_height - 20
available_height = controls_y - search_bottom_y
start_y = search_bottom_y + ( available_height - keyboard_height - 40 ) / / 2
keyboard_rect = pygame . Rect ( start_x - 20 , start_y - 20 , keyboard_width + 40 , keyboard_height + 40 )
pygame . draw . rect ( screen , ( 50 , 50 , 50 , 200 ) , keyboard_rect , border_radius = 10 )
pygame . draw . rect ( screen , ( 255 , 255 , 255 ) , keyboard_rect , 2 , border_radius = 10 )
for row_idx , row in enumerate ( keyboard_layout ) :
for col_idx , key in enumerate ( row ) :
x = start_x + col_idx * ( key_width + key_spacing )
y = start_y + row_idx * ( key_height + key_spacing )
key_rect = pygame . Rect ( x , y , key_width , key_height )
if ( row_idx , col_idx ) == config . selected_key :
pygame . draw . rect ( screen , ( 0 , 150 , 255 , 150 ) , key_rect , border_radius = 5 )
else :
pygame . draw . rect ( screen , ( 80 , 80 , 80 , 255 ) , key_rect , border_radius = 5 )
pygame . draw . rect ( screen , ( 255 , 255 , 255 ) , key_rect , 1 , border_radius = 5 )
text = config . font . render ( key , True , ( 255 , 255 , 255 ) )
text_rect = text . get_rect ( center = key_rect . center )
screen . blit ( text , text_rect )
def draw_progress_screen ( screen ) :
""" Affiche l ' écran de progression des téléchargements avec taille en Mo, et un message spécifique pour la conversion ISO. """
logger . debug ( " Début de draw_progress_screen " )
if not config . download_tasks :
logger . debug ( " Aucune tâche de téléchargement active " )
return
task = list ( config . download_tasks . keys ( ) ) [ 0 ]
game_name = config . download_tasks [ task ] [ 2 ]
url = config . download_tasks [ task ] [ 1 ]
progress = config . download_progress . get ( url , { " downloaded_size " : 0 , " total_size " : 0 , " status " : " Téléchargement " , " progress_percent " : 0 } )
status = progress . get ( " status " , " Téléchargement " )
downloaded_size = progress [ " downloaded_size " ]
total_size = progress [ " total_size " ]
progress_percent = progress [ " progress_percent " ]
logger . debug ( f " Progression : game_name= { game_name } , url= { url } , status= { status } , progress_percent= { progress_percent } , downloaded_size= { downloaded_size } , total_size= { total_size } " )
2025-07-06 21:13:39 +02:00
screen . blit ( OVERLAY , ( 0 , 0 ) )
2025-07-06 19:47:21 +02:00
if status == " Converting ISO " :
title_text = f " Converting : { truncate_text_end ( game_name , config . font , config . screen_width - 200 ) } "
else :
title_text = f " { status } : { truncate_text_end ( game_name , config . font , config . screen_width - 200 ) } "
title_render = config . font . render ( title_text , True , ( 255 , 255 , 255 ) )
title_rect = title_render . get_rect ( center = ( config . screen_width / / 2 , config . screen_height / / 2 - 100 ) )
pygame . draw . rect ( screen , ( 50 , 50 , 50 , 200 ) , title_rect . inflate ( 40 , 20 ) )
pygame . draw . rect ( screen , ( 255 , 255 , 255 ) , title_rect . inflate ( 40 , 20 ) , 2 )
screen . blit ( title_render , title_rect )
logger . debug ( f " Titre affiché : texte= { title_text } , position= { title_rect } , taille= { title_render . get_size ( ) } " )
if status == " Converting ISO " :
conversion_text = config . font . render ( " Conversion de l ' ISO en dossier .ps3 en cours... " , True , ( 255 , 255 , 255 ) )
conversion_rect = conversion_text . get_rect ( center = ( config . screen_width / / 2 , config . screen_height / / 2 ) )
pygame . draw . rect ( screen , ( 50 , 50 , 50 , 200 ) , conversion_rect . inflate ( 40 , 20 ) )
pygame . draw . rect ( screen , ( 255 , 255 , 255 ) , conversion_rect . inflate ( 40 , 20 ) , 2 )
screen . blit ( conversion_text , conversion_rect )
logger . debug ( f " Message de conversion affiché : position= { conversion_rect } , taille= { conversion_text . get_size ( ) } " )
else :
bar_width = config . screen_width / / 2
bar_height = 30
bar_x = ( config . screen_width - bar_width ) / / 2
bar_y = config . screen_height / / 2
progress_width = 0
pygame . draw . rect ( screen , ( 100 , 100 , 100 ) , ( bar_x , bar_y , bar_width , bar_height ) )
if total_size > 0 :
progress_width = int ( bar_width * ( progress_percent / 100 ) )
pygame . draw . rect ( screen , ( 0 , 150 , 255 ) , ( bar_x , bar_y , progress_width , bar_height ) )
pygame . draw . rect ( screen , ( 255 , 255 , 255 ) , ( bar_x , bar_y , bar_width , bar_height ) , 2 )
logger . debug ( f " Barre de progression affichée : position=( { bar_x } , { bar_y } ), taille=( { bar_width } , { bar_height } ), progress_width= { progress_width } " )
downloaded_mb = downloaded_size / ( 1024 * 1024 )
total_mb = total_size / ( 1024 * 1024 )
size_text = f " { downloaded_mb : .1f } Mo / { total_mb : .1f } Mo "
percent_text = f " { int ( progress_percent ) } % { size_text } "
percent_render = config . font . render ( percent_text , True , ( 255 , 255 , 255 ) )
text_y = bar_y + bar_height / / 2 + config . font . get_height ( ) + 20
percent_rect = percent_render . get_rect ( center = ( config . screen_width / / 2 , text_y ) )
pygame . draw . rect ( screen , ( 50 , 50 , 50 , 200 ) , percent_rect . inflate ( 20 , 10 ) )
pygame . draw . rect ( screen , ( 255 , 255 , 255 ) , percent_rect . inflate ( 20 , 10 ) , 2 )
screen . blit ( percent_render , percent_rect )
logger . debug ( f " Texte de progression affiché : texte= { percent_text } , 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_confirm_dialog ( screen ) :
""" Affiche la boîte de dialogue de confirmation pour quitter. """
2025-07-06 21:13:39 +02:00
screen . blit ( OVERLAY , ( 0 , 0 ) )
2025-07-06 19:47:21 +02:00
message = " Voulez-vous vraiment quitter ? "
text = config . font . render ( message , True , ( 255 , 255 , 255 ) )
text_rect = text . get_rect ( center = ( config . screen_width / / 2 , config . screen_height / / 2 - 50 ) )
pygame . draw . rect ( screen , ( 50 , 50 , 50 , 200 ) , text_rect . inflate ( 40 , 20 ) )
pygame . draw . rect ( screen , ( 255 , 255 , 255 ) , text_rect . inflate ( 40 , 20 ) , 2 )
screen . blit ( text , text_rect )
yes_text = config . font . render ( " Oui " , True , ( 255 , 255 , 255 ) )
no_text = config . font . render ( " Non " , True , ( 255 , 255 , 255 ) )
yes_rect = yes_text . get_rect ( center = ( config . screen_width / / 2 - 100 , config . screen_height / / 2 + 50 ) )
no_rect = no_text . get_rect ( center = ( config . screen_width / / 2 + 100 , config . screen_height / / 2 + 50 ) )
if config . confirm_selection == 1 :
pygame . draw . rect ( screen , ( 0 , 150 , 255 , 150 ) , yes_rect . inflate ( 40 , 20 ) )
else :
pygame . draw . rect ( screen , ( 0 , 150 , 255 , 150 ) , no_rect . inflate ( 40 , 20 ) )
pygame . draw . rect ( screen , ( 255 , 255 , 255 ) , yes_rect . inflate ( 40 , 20 ) , 2 )
pygame . draw . rect ( screen , ( 255 , 255 , 255 ) , no_rect . inflate ( 40 , 20 ) , 2 )
screen . blit ( yes_text , yes_rect )
screen . blit ( no_text , no_rect )
def draw_popup_message ( screen , message , is_error ) :
""" Affiche une popup avec un message de résultat. """
2025-07-06 21:13:39 +02:00
screen . blit ( OVERLAY , ( 0 , 0 ) )
2025-07-06 19:47:21 +02:00
text = config . font . render ( message , 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 ) )
pygame . draw . rect ( screen , ( 50 , 50 , 50 , 200 ) , text_rect . inflate ( 40 , 20 ) )
pygame . draw . rect ( screen , ( 255 , 255 , 255 ) , text_rect . inflate ( 40 , 20 ) , 2 )
screen . blit ( text , text_rect )
def draw_extension_warning ( screen ) :
""" Affiche un avertissement pour une extension non reconnue ou un fichier ZIP. """
2025-07-06 21:13:39 +02:00
logger . debug ( " Début de draw_extension_warning " )
2025-07-06 19:47:21 +02:00
if not config . pending_download :
2025-07-06 21:13:39 +02:00
logger . error ( " config.pending_download est None ou vide dans extension_warning " )
2025-07-06 19:47:21 +02:00
message = " Erreur : Aucun téléchargement en attente. "
is_zip = False
game_name = " Inconnu "
else :
url , platform , game_name , is_zip_non_supported = config . pending_download
2025-07-06 21:13:39 +02:00
logger . debug ( f " config.pending_download: url= { url } , platform= { platform } , game_name= { game_name } , is_zip_non_supported= { is_zip_non_supported } " )
2025-07-06 19:47:21 +02:00
is_zip = is_zip_non_supported
if not game_name :
game_name = " Inconnu "
logger . warning ( " game_name vide, utilisation de ' Inconnu ' " )
if is_zip :
message = f " Le fichier ' { game_name } ' est une archive et Batocera ne prend pas en charge les archives pour ce système. L ' extraction automatique du fichier aura lieu après le téléchargement, continuer ? "
else :
message = f " L ' extension du fichier ' { game_name } ' n ' est pas supportée par Batocera d ' après le fichier info.txt. Voulez-vous continuer ? "
max_width = config . screen_width - 80
lines = wrap_text ( message , config . font , max_width )
2025-07-06 21:13:39 +02:00
logger . debug ( f " Lignes générées : { lines } " )
2025-07-06 19:47:21 +02:00
try :
# Calcul de la hauteur de ligne
line_height = config . font . get_height ( ) + 5
# Hauteur pour les lignes de texte
text_height = len ( lines ) * line_height
# Hauteur pour les boutons (1 ligne + marges)
button_height = line_height + 20
# Marges en haut et en bas
margin_top_bottom = 20
# Hauteur totale du rectangle
rect_height = text_height + button_height + 2 * margin_top_bottom
# Largeur du rectangle
max_text_width = max ( [ config . font . size ( line ) [ 0 ] for line in lines ] , default = 300 )
rect_width = max_text_width + 40
# Position du rectangle
rect_x = ( config . screen_width - rect_width ) / / 2
rect_y = ( config . screen_height - rect_height ) / / 2
2025-07-06 21:13:39 +02:00
screen . blit ( OVERLAY , ( 0 , 0 ) )
2025-07-06 19:47:21 +02:00
# Dessiner le rectangle de fond
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 )
# Afficher les lignes de texte
text_surfaces = [ config . font . render ( line , True , ( 255 , 255 , 255 ) ) for line in lines ]
text_rects = [
surface . get_rect ( center = ( config . screen_width / / 2 , rect_y + margin_top_bottom + i * line_height + line_height / / 2 ) )
for i , surface in enumerate ( text_surfaces )
]
for surface , rect in zip ( text_surfaces , text_rects ) :
screen . blit ( surface , rect )
2025-07-06 21:13:39 +02:00
logger . debug ( f " Lignes affichées : { [ ( rect . center , surface . get_size ( ) ) for rect , surface in zip ( text_rects , text_surfaces ) ] } " )
2025-07-06 19:47:21 +02:00
# Afficher les boutons Oui/Non
yes_text = " [Oui] " if config . extension_confirm_selection == 1 else " Oui "
no_text = " [Non] " if config . extension_confirm_selection == 0 else " Non "
yes_surface = config . font . render ( yes_text , True , ( 0 , 150 , 255 ) if config . extension_confirm_selection == 1 else ( 255 , 255 , 255 ) )
no_surface = config . font . render ( no_text , True , ( 0 , 150 , 255 ) if config . extension_confirm_selection == 0 else ( 255 , 255 , 255 ) )
button_y = rect_y + text_height + margin_top_bottom + line_height / / 2
yes_rect = yes_surface . get_rect ( center = ( config . screen_width / / 2 - 100 , button_y ) )
no_rect = no_surface . get_rect ( center = ( config . screen_width / / 2 + 100 , button_y ) )
screen . blit ( yes_surface , yes_rect )
screen . blit ( no_surface , no_rect )
2025-07-06 21:13:39 +02:00
logger . debug ( f " Boutons affichés : Oui= { yes_rect } , Non= { no_rect } , selection= { config . extension_confirm_selection } " )
2025-07-06 19:47:21 +02:00
except Exception as e :
logger . error ( f " Erreur lors du rendu de extension_warning : { str ( e ) } " )
error_message = " Erreur d ' affichage de l ' avertissement. "
error_surface = config . font . render ( error_message , True , ( 255 , 0 , 0 ) )
error_rect = error_surface . get_rect ( center = ( config . screen_width / / 2 , config . screen_height / / 2 ) )
pygame . draw . rect ( screen , ( 50 , 50 , 50 , 200 ) , error_rect . inflate ( 40 , 20 ) , border_radius = 10 )
pygame . draw . rect ( screen , ( 255 , 255 , 255 ) , error_rect . inflate ( 40 , 20 ) , 2 , border_radius = 10 )
screen . blit ( error_surface , error_rect )
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. """
2025-07-06 21:13:39 +02:00
start_button = get_control_display ( ' start ' , ' START ' )
control_text = f " { start_button } : Menu - Controls "
2025-07-06 19:47:21 +02:00
max_width = config . screen_width - 40
control_text = truncate_text_end ( control_text , config . font , max_width )
text_surface = config . font . render ( control_text , True , ( 255 , 255 , 255 ) )
text_width , text_height = text_surface . get_size ( )
rect_width = text_width + 40
rect_height = text_height + 20
rect_x = ( config . screen_width - rect_width ) / / 2
rect_y = config . screen_height - rect_height - 20
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 )
text_rect = text_surface . get_rect ( center = ( config . screen_width / / 2 , rect_y + rect_height / / 2 ) )
screen . blit ( text_surface , text_rect )
def draw_validation_transition ( screen , platform_index ) :
""" Affiche une animation de transition pour la sélection d’ une plateforme. """
platform_dict = config . platform_dicts [ platform_index ] # Use platform dictionary
image = load_system_image ( platform_dict )
if not image :
return
orig_width , orig_height = image . get_width ( ) , image . get_height ( )
base_size = 150
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 )
def draw_pause_menu ( screen , selected_option ) :
""" Dessine le menu pause avec les options Aide, Configurer contrôles, Quitter. """
2025-07-06 21:13:39 +02:00
screen . blit ( OVERLAY , ( 0 , 0 ) )
2025-07-06 19:47:21 +02:00
options = [
" Controls " ,
" Remap controls " ,
" Quit "
]
menu_width = 400
menu_height = len ( options ) * 60 + 40
menu_x = ( config . screen_width - menu_width ) / / 2
menu_y = ( config . screen_height - menu_height ) / / 2
pygame . draw . rect ( screen , ( 50 , 50 , 50 , 200 ) , ( menu_x , menu_y , menu_width , menu_height ) )
pygame . draw . rect ( screen , ( 255 , 255 , 255 ) , ( menu_x , menu_y , menu_width , menu_height ) , 2 )
for i , option in enumerate ( options ) :
color = ( 0 , 150 , 255 ) if i == selected_option else ( 255 , 255 , 255 )
text_surface = config . font . render ( option , True , color )
text_rect = text_surface . get_rect ( center = ( config . screen_width / / 2 , menu_y + 30 + i * 60 ) )
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. """
2025-07-06 21:13:39 +02:00
if not config . controls_config :
logger . warning ( f " controls_config vide pour l ' action { action } , utilisation de la valeur par défaut " )
return default
2025-07-06 19:47:21 +02:00
return config . controls_config . get ( action , { } ) . get ( ' display ' , default )
def draw_controls_help ( screen , previous_state ) :
""" Affiche la liste des contrôles pour l ' état précédent du menu. """
2025-07-06 21:13:39 +02:00
# Dictionnaire des contrôles communs pour réduire la duplication
common_controls = {
" confirm " : lambda action : f " { get_control_display ( ' confirm ' , ' Entrée/A ' ) } : { action } " ,
" cancel " : lambda action : f " { get_control_display ( ' cancel ' , ' Échap/B ' ) } : { action } " ,
" start " : lambda : f " { get_control_display ( ' start ' , ' Start ' ) } : Menu " ,
" progress " : lambda action : f " { get_control_display ( ' progress ' , ' X ' ) } : { action } " ,
" up " : lambda action : f " { get_control_display ( ' up ' , ' Flèche Haut ' ) } : { action } " ,
" down " : lambda action : f " { get_control_display ( ' down ' , ' Flèche Bas ' ) } : { action } " ,
" page_up " : lambda action : f " { get_control_display ( ' page_up ' , ' Q/LB ' ) } : { action } " ,
" page_down " : lambda action : f " { get_control_display ( ' page_down ' , ' E/RB ' ) } : { action } " ,
" filter " : lambda action : f " { get_control_display ( ' filter ' , ' Select ' ) } : { action } " ,
" delete " : lambda : f " { get_control_display ( ' delete ' , ' Retour Arrière ' ) } : Supprimer " ,
" space " : lambda : f " { get_control_display ( ' space ' , ' Espace ' ) } : Espace "
}
2025-07-06 19:47:21 +02:00
# Dictionnaire des contrôles par état
state_controls = {
" error " : [
2025-07-06 21:13:39 +02:00
common_controls [ " confirm " ] ( " Retenter " ) ,
common_controls [ " cancel " ] ( " Quitter " )
2025-07-06 19:47:21 +02:00
] ,
" platform " : [
2025-07-06 21:13:39 +02:00
common_controls [ " confirm " ] ( " Sélectionner " ) ,
common_controls [ " cancel " ] ( " Quitter " ) ,
common_controls [ " start " ] ( ) ,
* ( [ common_controls [ " progress " ] ( " Progression " ) ] if config . download_tasks else [ ] )
2025-07-06 19:47:21 +02:00
] ,
" game " : [
2025-07-06 21:13:39 +02:00
common_controls [ " confirm " ] ( f " { ' Valider ' if config . search_mode else ' Télécharger ' } " ) ,
common_controls [ " cancel " ] ( f " { ' Annuler ' if config . search_mode else ' Retour ' } " ) ,
2025-07-06 19:47:21 +02:00
* ( [
2025-07-06 21:13:39 +02:00
common_controls [ " delete " ] ( ) ,
common_controls [ " space " ] ( )
2025-07-06 19:47:21 +02:00
] if config . search_mode and config . is_non_pc else [ ] ) ,
* ( [
2025-07-06 21:13:39 +02:00
f " Saisir texte : Filtrer " if config . search_mode else
f " { common_controls [ ' up ' ] ( ' Naviguer ' ) } / { common_controls [ ' down ' ] ( ' Naviguer ' ) } " ,
f " { common_controls [ ' page_up ' ] ( ' Page ' ) } / { common_controls [ ' page_down ' ] ( ' Page ' ) } " ,
common_controls [ " filter " ] ( " Filtrer " )
2025-07-06 19:47:21 +02:00
] if not config . is_non_pc or not config . search_mode else [ ] ) ,
2025-07-06 21:13:39 +02:00
common_controls [ " start " ] ( ) ,
* ( [ common_controls [ " progress " ] ( " Progression " ) ] if config . download_tasks and not config . search_mode else [ ] )
2025-07-06 19:47:21 +02:00
] ,
" download_progress " : [
2025-07-06 21:13:39 +02:00
common_controls [ " cancel " ] ( " Annuler le téléchargement " ) ,
common_controls [ " progress " ] ( " Arrière plan " ) ,
common_controls [ " start " ] ( )
2025-07-06 19:47:21 +02:00
] ,
" download_result " : [
2025-07-06 21:13:39 +02:00
common_controls [ " confirm " ] ( " Retour " )
2025-07-06 19:47:21 +02:00
] ,
" confirm_exit " : [
2025-07-06 21:13:39 +02:00
common_controls [ " confirm " ] ( " Confirmer " )
2025-07-06 19:47:21 +02:00
] ,
" extension_warning " : [
2025-07-06 21:13:39 +02:00
common_controls [ " confirm " ] ( " Confirmer " )
2025-07-06 19:47:21 +02:00
]
}
# Récupérer les contrôles pour l'état donné
controls = state_controls . get ( previous_state , [ ] )
2025-07-06 21:13:39 +02:00
if not controls :
return
2025-07-06 19:47:21 +02:00
2025-07-06 21:13:39 +02:00
# Créer l'overlay semi-transparent
screen . blit ( OVERLAY , ( 0 , 0 ) )
2025-07-06 19:47:21 +02:00
# Envelopper les textes pour respecter la largeur maximale
max_width = config . screen_width - 80
wrapped_controls = [ ]
current_line = " "
for control in controls :
test_line = f " { current_line } | { control } " if current_line else control
if config . font . size ( test_line ) [ 0 ] < = max_width :
current_line = test_line
else :
wrapped_controls . append ( current_line )
current_line = control
if current_line :
wrapped_controls . append ( current_line )
# Calculer les dimensions de la popup
line_height = config . font . get_height ( ) + 10
popup_width = max_width + 40
popup_height = len ( wrapped_controls ) * line_height + 60
popup_x = ( config . screen_width - popup_width ) / / 2
popup_y = ( config . screen_height - popup_height ) / / 2
# Dessiner la popup
pygame . draw . rect ( screen , ( 50 , 50 , 50 , 200 ) , ( popup_x , popup_y , popup_width , popup_height ) , border_radius = 16 )
pygame . draw . rect ( screen , ( 255 , 255 , 255 ) , ( popup_x , popup_y , popup_width , popup_height ) , 2 , border_radius = 16 )
# Afficher les textes
for i , line in enumerate ( wrapped_controls ) :
text = config . font . render ( line , True , ( 255 , 255 , 255 ) )
text_rect = text . get_rect ( center = ( config . screen_width / / 2 , popup_y + 40 + i * line_height ) )
screen . blit ( text , text_rect )