forked from Mirrors/RGSX
2287 lines
113 KiB
Python
2287 lines
113 KiB
Python
import pygame # type: ignore
|
||
import config
|
||
from utils import truncate_text_middle, wrap_text, load_system_image, truncate_text_end
|
||
import logging
|
||
import math
|
||
from history import load_history # Ajout de l'import
|
||
from language import _ # Import de la fonction de traduction
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
OVERLAY = None # Initialisé dans init_display()
|
||
|
||
# Couleurs modernes pour le thème
|
||
THEME_COLORS = {
|
||
# Fond des lignes sélectionnées
|
||
"fond_lignes": (0, 255, 0), # vert
|
||
# Fond par défaut des images de grille des systèmes
|
||
"fond_image": (50, 50, 70), # Bleu sombre métal
|
||
# Néon image grille des systèmes
|
||
"neon": (0, 134, 179), # bleu
|
||
# Dégradé sombre pour le fond
|
||
"background_top": (30, 40, 50),
|
||
"background_bottom": (60, 80, 100), # noir vers bleu foncé
|
||
# Fond des cadres
|
||
"button_idle": (50, 50, 70, 150), # Bleu sombre métal
|
||
# Fond des boutons sélectionnés dans les popups ou menu
|
||
"button_hover": (255, 0, 255, 220), # Rose
|
||
# Générique
|
||
"text": (255, 255, 255), # blanc
|
||
# Texte sélectionné (alias pour compatibilité)
|
||
"text_selected": (0, 255, 0), # utilise le même vert que fond_lignes
|
||
# Erreur
|
||
"error_text": (255, 0, 0), # rouge
|
||
# Avertissement
|
||
"warning_text": (255, 100, 0), # orange
|
||
# Titres
|
||
"title_text": (200, 200, 200), # gris clair
|
||
# Bordures
|
||
"border": (150, 150, 150), # Bordures grises subtiles
|
||
}
|
||
|
||
# Général, résolution, overlay
|
||
def init_display():
|
||
"""Initialise l'écran et les ressources globales."""
|
||
global OVERLAY
|
||
logger.debug("Initialisation de l'écran")
|
||
display_info = pygame.display.Info()
|
||
screen_width = display_info.current_w
|
||
screen_height = display_info.current_h
|
||
screen = pygame.display.set_mode((screen_width, screen_height))
|
||
config.screen_width = screen_width
|
||
config.screen_height = screen_height
|
||
# Initialisation de OVERLAY
|
||
OVERLAY = pygame.Surface((screen_width, screen_height), pygame.SRCALPHA)
|
||
OVERLAY.fill((0, 0, 0, 150)) # Transparence augmentée
|
||
logger.debug(f"Écran initialisé avec résolution : {screen_width}x{screen_height}")
|
||
return screen
|
||
|
||
# Fond d'écran dégradé
|
||
def draw_gradient(screen, top_color, bottom_color):
|
||
"""Dessine un fond dégradé vertical avec des couleurs vibrantes."""
|
||
height = screen.get_height()
|
||
top_color = pygame.Color(*top_color)
|
||
bottom_color = pygame.Color(*bottom_color)
|
||
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))
|
||
|
||
# Nouvelle fonction pour dessiner un bouton stylisé
|
||
def draw_stylized_button(screen, text, x, y, width, height, selected=False):
|
||
"""Dessine un bouton moderne avec effet de survol et bordure arrondie."""
|
||
button_surface = pygame.Surface((width, height), pygame.SRCALPHA)
|
||
button_color = THEME_COLORS["button_hover"] if selected else THEME_COLORS["button_idle"]
|
||
pygame.draw.rect(button_surface, button_color, (0, 0, width, height), border_radius=12)
|
||
pygame.draw.rect(button_surface, THEME_COLORS["border"], (0, 0, width, height), 2, border_radius=12)
|
||
if selected:
|
||
glow_surface = pygame.Surface((width + 10, height + 10), pygame.SRCALPHA)
|
||
pygame.draw.rect(glow_surface, THEME_COLORS["fond_lignes"] + (50,), (5, 5, width, height), border_radius=12)
|
||
screen.blit(glow_surface, (x - 5, y - 5))
|
||
screen.blit(button_surface, (x, y))
|
||
text_surface = config.font.render(text, True, THEME_COLORS["text"])
|
||
text_rect = text_surface.get_rect(center=(x + width // 2, y + height // 2))
|
||
screen.blit(text_surface, text_rect)
|
||
|
||
# Transition d'image lors de la sélection d'un système
|
||
def draw_validation_transition(screen, platform_index):
|
||
"""Affiche une animation de transition fluide pour la sélection d’une plateforme.
|
||
Utilise le mapping par nom pour éviter les décalages d'image si l'ordre d'affichage
|
||
diffère de l'ordre de stockage."""
|
||
# Récupérer le nom affiché correspondant à l'index trié
|
||
if platform_index < 0 or platform_index >= len(config.platforms):
|
||
return
|
||
platform_name = config.platforms[platform_index]
|
||
platform_dict = getattr(config, 'platform_dict_by_name', {}).get(platform_name)
|
||
if not platform_dict:
|
||
# Fallback index direct si mapping absent
|
||
try:
|
||
platform_dict = config.platform_dicts[platform_index]
|
||
except Exception:
|
||
return
|
||
image = load_system_image(platform_dict)
|
||
if not image:
|
||
return
|
||
|
||
# Dimensions originales et calcul du ratio pour préserver les proportions
|
||
orig_width, orig_height = image.get_width(), image.get_height()
|
||
base_size = int(config.screen_width * 0.0781) # ~150px pour 1920p
|
||
ratio = min(base_size / orig_width, base_size / orig_height) # Maintenir les proportions
|
||
base_width = int(orig_width * ratio)
|
||
base_height = int(orig_height * ratio)
|
||
|
||
# Paramètres de l'animation
|
||
start_time = pygame.time.get_ticks()
|
||
duration = 1000 # Durée augmentée à 1 seconde
|
||
fps = 60
|
||
frame_time = 1000 / fps # Temps par frame en ms
|
||
|
||
while pygame.time.get_ticks() - start_time < duration:
|
||
# Fond dégradé
|
||
draw_gradient(screen, THEME_COLORS["background_top"], THEME_COLORS["background_bottom"])
|
||
|
||
# Calcul de l'échelle avec une courbe sinusoïdale pour une transition fluide
|
||
elapsed = pygame.time.get_ticks() - start_time
|
||
progress = elapsed / duration
|
||
# Courbe sinusoïdale pour une montée/descente douce
|
||
scale = 1.5 + 1.0 * math.sin(math.pi * progress) # Échelle de 1.5 à 2.5
|
||
new_width = int(base_width * scale)
|
||
new_height = int(base_height * scale)
|
||
|
||
# Redimensionner l'image en préservant les proportions
|
||
scaled_image = pygame.transform.smoothscale(image, (new_width, new_height))
|
||
image_rect = scaled_image.get_rect(center=(config.screen_width // 2, config.screen_height // 2))
|
||
|
||
# Effet de fondu (opacité de 50% à 100% puis retour à 50%)
|
||
alpha = int(128 + 127 * math.cos(math.pi * progress)) # Opacité entre 128 et 255
|
||
scaled_image.set_alpha(alpha)
|
||
|
||
# Effet de glow néon pour l'image sélectionnée
|
||
neon_color = THEME_COLORS["neon"] # Cyan vif
|
||
padding = 24
|
||
neon_surface = pygame.Surface((new_width + 2 * padding, new_height + 2 * padding), pygame.SRCALPHA)
|
||
pygame.draw.rect(neon_surface, neon_color + (40,), neon_surface.get_rect(), border_radius=24)
|
||
pygame.draw.rect(neon_surface, neon_color + (100,), neon_surface.get_rect().inflate(-10, -10), border_radius=18)
|
||
screen.blit(neon_surface, (image_rect.left - padding, image_rect.top - padding), special_flags=pygame.BLEND_RGBA_ADD)
|
||
|
||
# Afficher l'image
|
||
screen.blit(scaled_image, image_rect)
|
||
pygame.display.flip()
|
||
|
||
# Contrôler la fréquence de rendu
|
||
pygame.time.wait(int(frame_time))
|
||
|
||
# Afficher l'image finale sans effet pour une transition propre
|
||
draw_gradient(screen, THEME_COLORS["background_top"], THEME_COLORS["background_bottom"])
|
||
final_image = pygame.transform.smoothscale(image, (base_width, base_height))
|
||
final_image.set_alpha(255) # Opacité complète
|
||
final_rect = final_image.get_rect(center=(config.screen_width // 2, config.screen_height // 2))
|
||
screen.blit(final_image, final_rect)
|
||
pygame.display.flip()
|
||
|
||
# Écran de chargement
|
||
def draw_loading_screen(screen):
|
||
"""Affiche l’écran de chargement avec un style moderne."""
|
||
disclaimer_lines = [
|
||
_("welcome_message"),
|
||
_("disclaimer_line1"),
|
||
_("disclaimer_line2"),
|
||
_("disclaimer_line3"),
|
||
_("disclaimer_line4"),
|
||
_("disclaimer_line5"),
|
||
]
|
||
|
||
margin_horizontal = int(config.screen_width * 0.025)
|
||
padding_vertical = int(config.screen_height * 0.0185)
|
||
padding_between = int(config.screen_height * 0.0074)
|
||
border_radius = 16
|
||
border_width = 3
|
||
shadow_offset = 6
|
||
|
||
line_height = config.small_font.get_height() + padding_between
|
||
total_height = line_height * len(disclaimer_lines) - padding_between
|
||
rect_width = config.screen_width - 2 * margin_horizontal
|
||
rect_height = total_height + 2 * padding_vertical
|
||
rect_x = margin_horizontal
|
||
rect_y = int(config.screen_height * 0.0185)
|
||
|
||
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, THEME_COLORS["button_idle"], disclaimer_surface.get_rect(), border_radius=border_radius)
|
||
screen.blit(disclaimer_surface, disclaimer_rect.topleft)
|
||
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], disclaimer_rect, border_width, border_radius=border_radius)
|
||
|
||
max_text_width = rect_width - 2 * padding_vertical
|
||
for i, line in enumerate(disclaimer_lines):
|
||
wrapped_lines = wrap_text(line, config.small_font, max_text_width)
|
||
for j, wrapped_line in enumerate(wrapped_lines):
|
||
text_surface = config.small_font.render(wrapped_line, True, THEME_COLORS["title_text"])
|
||
text_rect = text_surface.get_rect(center=(
|
||
config.screen_width // 2,
|
||
rect_y + padding_vertical + (i * len(wrapped_lines) + j + 0.5) * line_height - padding_between // 2
|
||
))
|
||
screen.blit(text_surface, text_rect)
|
||
|
||
loading_y = rect_y + rect_height + int(config.screen_height * 0.0926)
|
||
text = config.small_font.render(truncate_text_middle(f"{config.current_loading_system}", config.small_font, config.screen_width - 2 * margin_horizontal), True, THEME_COLORS["text"])
|
||
text_rect = text.get_rect(center=(config.screen_width // 2, loading_y))
|
||
screen.blit(text, text_rect)
|
||
|
||
progress_text = config.small_font.render(_("loading_progress").format(int(config.loading_progress)), True, THEME_COLORS["text"])
|
||
progress_rect = progress_text.get_rect(center=(config.screen_width // 2, loading_y + int(config.screen_height * 0.0463)))
|
||
screen.blit(progress_text, progress_rect)
|
||
|
||
bar_width = int(config.screen_width * 0.2083)
|
||
bar_height = int(config.screen_height * 0.037)
|
||
progress_width = (bar_width * config.loading_progress) / 100
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (config.screen_width // 2 - bar_width // 2, loading_y + int(config.screen_height * 0.0926), bar_width, bar_height), border_radius=8)
|
||
pygame.draw.rect(screen, THEME_COLORS["fond_lignes"], (config.screen_width // 2 - bar_width // 2, loading_y + int(config.screen_height * 0.0926), progress_width, bar_height), border_radius=8)
|
||
|
||
# Écran d'erreur
|
||
def draw_error_screen(screen):
|
||
"""Affiche l’écran d’erreur avec un style moderne."""
|
||
wrapped_message = wrap_text(config.error_message, config.small_font, config.screen_width - 80)
|
||
line_height = config.small_font.get_height() + 5
|
||
text_height = len(wrapped_message) * line_height
|
||
button_height = int(config.screen_height * 0.0463)
|
||
margin_top_bottom = 20
|
||
rect_height = text_height + button_height + 2 * margin_top_bottom
|
||
max_text_width = max([config.small_font.size(line)[0] for line in wrapped_message], default=300)
|
||
rect_width = max_text_width + 80
|
||
rect_x = (config.screen_width - rect_width) // 2
|
||
rect_y = (config.screen_height - rect_height) // 2
|
||
|
||
screen.blit(OVERLAY, (0, 0))
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||
|
||
for i, line in enumerate(wrapped_message):
|
||
text = config.small_font.render(line, True, THEME_COLORS["error_text"])
|
||
text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2))
|
||
screen.blit(text, text_rect)
|
||
|
||
draw_stylized_button(screen, _("button_OK"), rect_x + rect_width // 2 - 80, rect_y + text_height + margin_top_bottom, 160, button_height, selected=True)
|
||
|
||
# Récupérer les noms d'affichage des contrôles
|
||
def get_control_display(action, default):
|
||
"""Récupère le nom d'affichage d'une action depuis controls_config."""
|
||
if not config.controls_config:
|
||
logger.warning(f"controls_config vide pour l'action {action}, utilisation de la valeur par défaut")
|
||
return default
|
||
|
||
control_config = config.controls_config.get(action, {})
|
||
control_type = control_config.get('type', '')
|
||
|
||
# Si un libellé personnalisé est défini dans controls.json, on le privilégie
|
||
custom_label = control_config.get('display')
|
||
if isinstance(custom_label, str) and custom_label.strip():
|
||
return custom_label
|
||
|
||
# Générer le nom d'affichage basé sur la configuration réelle
|
||
if control_type == 'key':
|
||
key_code = control_config.get('key')
|
||
key_names = {
|
||
pygame.K_RETURN: "Enter",
|
||
pygame.K_ESCAPE: "Échap",
|
||
pygame.K_SPACE: "Espace",
|
||
pygame.K_UP: "↑",
|
||
pygame.K_DOWN: "↓",
|
||
pygame.K_LEFT: "←",
|
||
pygame.K_RIGHT: "→",
|
||
pygame.K_BACKSPACE: "Backspace",
|
||
pygame.K_TAB: "Tab",
|
||
pygame.K_LALT: "Alt",
|
||
pygame.K_RALT: "AltGR",
|
||
pygame.K_LCTRL: "LCtrl",
|
||
pygame.K_RCTRL: "RCtrl",
|
||
pygame.K_LSHIFT: "LShift",
|
||
pygame.K_RSHIFT: "RShift",
|
||
pygame.K_LMETA: "LMeta",
|
||
pygame.K_RMETA: "RMeta",
|
||
pygame.K_CAPSLOCK: "Verr Maj",
|
||
pygame.K_NUMLOCK: "Verr Num",
|
||
pygame.K_SCROLLOCK: "Verr Déf",
|
||
pygame.K_a: "A",
|
||
pygame.K_b: "B",
|
||
pygame.K_c: "C",
|
||
pygame.K_d: "D",
|
||
pygame.K_e: "E",
|
||
pygame.K_f: "F",
|
||
pygame.K_g: "G",
|
||
pygame.K_h: "H",
|
||
pygame.K_i: "I",
|
||
pygame.K_j: "J",
|
||
pygame.K_k: "K",
|
||
pygame.K_l: "L",
|
||
pygame.K_m: "M",
|
||
pygame.K_n: "N",
|
||
pygame.K_o: "O",
|
||
pygame.K_p: "P",
|
||
pygame.K_q: "Q",
|
||
pygame.K_r: "R",
|
||
pygame.K_s: "S",
|
||
pygame.K_t: "T",
|
||
pygame.K_u: "U",
|
||
pygame.K_v: "V",
|
||
pygame.K_w: "W",
|
||
pygame.K_x: "X",
|
||
pygame.K_y: "Y",
|
||
pygame.K_z: "Z",
|
||
pygame.K_0: "0",
|
||
pygame.K_1: "1",
|
||
pygame.K_2: "2",
|
||
pygame.K_3: "3",
|
||
pygame.K_4: "4",
|
||
pygame.K_5: "5",
|
||
pygame.K_6: "6",
|
||
pygame.K_7: "7",
|
||
pygame.K_8: "8",
|
||
pygame.K_9: "9",
|
||
pygame.K_KP0: "Num 0",
|
||
pygame.K_KP1: "Num 1",
|
||
pygame.K_KP2: "Num 2",
|
||
pygame.K_KP3: "Num 3",
|
||
pygame.K_KP4: "Num 4",
|
||
pygame.K_KP5: "Num 5",
|
||
pygame.K_KP6: "Num 6",
|
||
pygame.K_KP7: "Num 7",
|
||
pygame.K_KP8: "Num 8",
|
||
pygame.K_KP9: "Num 9",
|
||
pygame.K_KP_PERIOD: "Num .",
|
||
pygame.K_KP_DIVIDE: "Num /",
|
||
pygame.K_KP_MULTIPLY: "Num *",
|
||
pygame.K_KP_MINUS: "Num -",
|
||
pygame.K_KP_PLUS: "Num +",
|
||
pygame.K_KP_ENTER: "Num Enter",
|
||
pygame.K_KP_EQUALS: "Num =",
|
||
pygame.K_F1: "F1",
|
||
pygame.K_F2: "F2",
|
||
pygame.K_F3: "F3",
|
||
pygame.K_F4: "F4",
|
||
pygame.K_F5: "F5",
|
||
pygame.K_F6: "F6",
|
||
pygame.K_F7: "F7",
|
||
pygame.K_F8: "F8",
|
||
pygame.K_F9: "F9",
|
||
pygame.K_F10: "F10",
|
||
pygame.K_F11: "F11",
|
||
pygame.K_F12: "F12",
|
||
pygame.K_F13: "F13",
|
||
pygame.K_F14: "F14",
|
||
pygame.K_F15: "F15",
|
||
pygame.K_INSERT: "Inser",
|
||
pygame.K_DELETE: "Suppr",
|
||
pygame.K_HOME: "Début",
|
||
pygame.K_END: "Fin",
|
||
pygame.K_PAGEUP: "Page+",
|
||
pygame.K_PAGEDOWN: "Page-",
|
||
pygame.K_PRINT: "Printscreen",
|
||
pygame.K_SYSREQ: "SysReq",
|
||
pygame.K_BREAK: "Pause",
|
||
pygame.K_PAUSE: "Pause",
|
||
pygame.K_BACKQUOTE: "`",
|
||
pygame.K_MINUS: "-",
|
||
pygame.K_EQUALS: "=",
|
||
pygame.K_LEFTBRACKET: "[",
|
||
pygame.K_RIGHTBRACKET: "]",
|
||
pygame.K_BACKSLASH: "\\",
|
||
pygame.K_SEMICOLON: ";",
|
||
pygame.K_QUOTE: "'",
|
||
pygame.K_COMMA: ",",
|
||
pygame.K_PERIOD: ".",
|
||
pygame.K_SLASH: "/",
|
||
}
|
||
return key_names.get(key_code, chr(key_code) if 32 <= key_code <= 126 else f"Key{key_code}")
|
||
|
||
elif control_type == 'button':
|
||
button_id = control_config.get('button')
|
||
# Étendre le mapping pour couvrir plus de manettes (incl. Trimui)
|
||
button_names = {
|
||
0: "A", 1: "B", 2: "X", 3: "Y",
|
||
4: "LB", 5: "RB",
|
||
6: "Select", 7: "Start",
|
||
8: "Select", 9: "Start",
|
||
10: "L3", 11: "R3",
|
||
}
|
||
return button_names.get(button_id, f"Btn{button_id}")
|
||
|
||
elif control_type == 'hat':
|
||
hat_value = control_config.get('value', (0, 0))
|
||
hat_names = {
|
||
(0, 1): "D↑", (0, -1): "D↓",
|
||
(-1, 0): "D←", (1, 0): "D→"
|
||
}
|
||
return hat_names.get(tuple(hat_value) if isinstance(hat_value, list) else hat_value, "D-Pad")
|
||
|
||
elif control_type == 'axis':
|
||
axis_id = control_config.get('axis')
|
||
direction = control_config.get('direction')
|
||
axis_names = {
|
||
(0, -1): "J←", (0, 1): "J→",
|
||
(1, -1): "J↑", (1, 1): "J↓"
|
||
}
|
||
return axis_names.get((axis_id, direction), f"Joy{axis_id}")
|
||
|
||
# Fallback vers l'ancien système ou valeur par défaut
|
||
return control_config.get('display', default)
|
||
|
||
# Cache pour les images des plateformes
|
||
platform_images_cache = {}
|
||
|
||
# Grille des systèmes 3x3
|
||
def draw_platform_grid(screen):
|
||
"""Affiche la grille des plateformes avec un style moderne et fluide."""
|
||
global platform_images_cache
|
||
|
||
if not config.platforms or config.selected_platform >= len(config.platforms):
|
||
platform_name = _("platform_no_platform")
|
||
logger.warning("Aucune plateforme ou selected_platform hors limites")
|
||
else:
|
||
platform = config.platforms[config.selected_platform]
|
||
platform_name = config.platform_names.get(platform, platform)
|
||
|
||
# Affichage du titre avec animation subtile
|
||
# Afficher le nombre total de jeux disponibles (tous systèmes) pour cohérence avec l'écran jeux
|
||
# Nombre de jeux pour la plateforme sélectionnée (utilise le cache pre-calculé si disponible)
|
||
game_count = 0
|
||
try:
|
||
if hasattr(config, 'games_count') and isinstance(config.games_count, dict):
|
||
game_count = config.games_count.get(platform_name, 0)
|
||
# Fallback dynamique si pas dans le cache (ex: plateformes modifiées à chaud)
|
||
if game_count == 0 and hasattr(config, 'platform_dict_by_name'):
|
||
from utils import load_games # import local pour éviter import circulaire global
|
||
game_count = len(load_games(platform_name))
|
||
except Exception:
|
||
game_count = 0
|
||
title_text = f"{platform_name} ({game_count})" if game_count > 0 else f"{platform_name}"
|
||
title_surface = config.title_font.render(title_text, True, THEME_COLORS["text"])
|
||
title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 20))
|
||
title_rect_inflated = title_rect.inflate(60, 30)
|
||
title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 10)
|
||
|
||
# Effet de pulsation subtil pour le titre - calculé une seule fois par frame
|
||
current_time = pygame.time.get_ticks()
|
||
pulse_factor = 0.05 * (1 + math.sin(current_time / 500))
|
||
title_glow = pygame.Surface((title_rect_inflated.width + 10, title_rect_inflated.height + 10), pygame.SRCALPHA)
|
||
pygame.draw.rect(title_glow, THEME_COLORS["neon"] + (int(40 * pulse_factor),),
|
||
title_glow.get_rect(), border_radius=14)
|
||
screen.blit(title_glow, (title_rect_inflated.left - 5, title_rect_inflated.top - 5))
|
||
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12)
|
||
screen.blit(title_surface, title_rect)
|
||
|
||
# Configuration de la grille - calculée une seule fois
|
||
margin_left = int(config.screen_width * 0.026)
|
||
margin_right = int(config.screen_width * 0.026)
|
||
margin_top = int(config.screen_height * 0.140)
|
||
margin_bottom = int(config.screen_height * 0.0648)
|
||
num_cols = getattr(config, 'GRID_COLS', 3)
|
||
num_rows = getattr(config, 'GRID_ROWS', 4)
|
||
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)]
|
||
|
||
# Filtrage éventuel des systèmes premium selon réglage
|
||
try:
|
||
from rgsx_settings import get_hide_premium_systems
|
||
hide_premium = get_hide_premium_systems()
|
||
except Exception:
|
||
hide_premium = False
|
||
premium_markers = getattr(config, 'PREMIUM_HOST_MARKERS', [])
|
||
if hide_premium and premium_markers:
|
||
visible_platforms = [p for p in config.platforms if not any(m.lower() in p.lower() for m in premium_markers)]
|
||
else:
|
||
visible_platforms = list(config.platforms)
|
||
|
||
# Ajuster selected_platform et current_platform/page si liste réduite
|
||
if config.selected_platform >= len(visible_platforms):
|
||
config.selected_platform = max(0, len(visible_platforms) - 1)
|
||
# Recalcule la page courante en fonction de selected_platform
|
||
systems_per_page = num_cols * num_rows
|
||
if systems_per_page <= 0:
|
||
systems_per_page = 1
|
||
config.current_page = config.selected_platform // systems_per_page if systems_per_page else 0
|
||
|
||
total_pages = (len(visible_platforms) + systems_per_page - 1) // systems_per_page
|
||
if total_pages > 1:
|
||
page_indicator_text = _("platform_page").format(config.current_page + 1, total_pages)
|
||
page_indicator = config.small_font.render(page_indicator_text, True, THEME_COLORS["text"])
|
||
page_rect = page_indicator.get_rect(center=(config.screen_width // 2, config.screen_height - margin_bottom // 2))
|
||
screen.blit(page_indicator, page_rect)
|
||
|
||
# Calculer une seule fois la pulsation pour les éléments sélectionnés
|
||
pulse = 0.1 * math.sin(current_time / 300)
|
||
glow_intensity = 40 + int(30 * math.sin(current_time / 300))
|
||
|
||
# Pré-calcul des images pour optimiser le rendu
|
||
start_idx = config.current_page * systems_per_page
|
||
for idx in range(start_idx, start_idx + systems_per_page):
|
||
if idx >= len(visible_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]
|
||
|
||
# Animation fluide pour l'item sélectionné
|
||
is_selected = idx == config.selected_platform
|
||
scale_base = 1.5 if is_selected else 1.0
|
||
scale = scale_base + pulse if is_selected else scale_base
|
||
|
||
# Récupération robuste du dict via nom
|
||
display_name = visible_platforms[idx]
|
||
platform_dict = getattr(config, 'platform_dict_by_name', {}).get(display_name)
|
||
if not platform_dict:
|
||
# Fallback index brut
|
||
# Chercher en parcourant platform_dicts pour correspondance nom
|
||
for pd in config.platform_dicts:
|
||
n = pd.get("platform_name") or pd.get("platform")
|
||
if n == display_name:
|
||
platform_dict = pd
|
||
break
|
||
else:
|
||
continue
|
||
platform_id = platform_dict.get("platform_name") or platform_dict.get("platform") or display_name
|
||
|
||
# Utiliser le cache d'images pour éviter de recharger/redimensionner à chaque frame
|
||
cache_key = f"{platform_id}_{scale:.2f}"
|
||
if cache_key not in platform_images_cache:
|
||
image = load_system_image(platform_dict)
|
||
if image:
|
||
orig_width, orig_height = image.get_width(), image.get_height()
|
||
max_size = int(min(col_width, row_height) * scale * 1.1) # Légèrement plus grand que la cellule
|
||
ratio = min(max_size / orig_width, max_size / orig_height)
|
||
new_width = int(orig_width * ratio)
|
||
new_height = int(orig_height * ratio)
|
||
scaled_image = pygame.transform.smoothscale(image, (new_width, new_height))
|
||
platform_images_cache[cache_key] = {
|
||
"image": scaled_image,
|
||
"width": new_width,
|
||
"height": new_height,
|
||
"last_used": current_time
|
||
}
|
||
else:
|
||
continue
|
||
else:
|
||
# Mettre à jour le timestamp de dernière utilisation
|
||
platform_images_cache[cache_key]["last_used"] = current_time
|
||
scaled_image = platform_images_cache[cache_key]["image"]
|
||
new_width = platform_images_cache[cache_key]["width"]
|
||
new_height = platform_images_cache[cache_key]["height"]
|
||
|
||
image_rect = scaled_image.get_rect(center=(x, y))
|
||
|
||
# Effet visuel amélioré pour l'item sélectionné
|
||
if is_selected:
|
||
neon_color = THEME_COLORS["neon"]
|
||
border_radius = 12
|
||
padding = 12
|
||
rect_width = image_rect.width + 2 * padding
|
||
rect_height = image_rect.height + 2 * padding
|
||
|
||
# Effet de glow dynamique
|
||
neon_surface = pygame.Surface((rect_width, rect_height), pygame.SRCALPHA)
|
||
pygame.draw.rect(neon_surface, neon_color + (glow_intensity,), neon_surface.get_rect(), border_radius=border_radius)
|
||
pygame.draw.rect(neon_surface, neon_color + (100,), neon_surface.get_rect().inflate(-10, -10), border_radius=border_radius)
|
||
pygame.draw.rect(neon_surface, neon_color + (200,), neon_surface.get_rect().inflate(-20, -20), width=1, border_radius=border_radius)
|
||
screen.blit(neon_surface, (image_rect.left - padding, image_rect.top - padding), special_flags=pygame.BLEND_RGBA_ADD)
|
||
|
||
# Fond pour toutes les images
|
||
background_surface = pygame.Surface((image_rect.width + 10, image_rect.height + 10), pygame.SRCALPHA)
|
||
bg_alpha = 220 if is_selected else 180 # Plus opaque pour l'item sélectionné
|
||
pygame.draw.rect(background_surface, THEME_COLORS["fond_image"] + (bg_alpha,), background_surface.get_rect(), border_radius=12)
|
||
screen.blit(background_surface, (image_rect.left - 5, image_rect.top - 5))
|
||
|
||
# Affichage de l'image avec un léger effet de transparence pour les items non sélectionnés
|
||
if not is_selected:
|
||
# Appliquer la transparence seulement si nécessaire
|
||
temp_image = scaled_image.copy()
|
||
temp_image.set_alpha(220)
|
||
screen.blit(temp_image, image_rect)
|
||
else:
|
||
screen.blit(scaled_image, image_rect)
|
||
|
||
# Nettoyer le cache périodiquement (garder seulement les images utilisées récemment)
|
||
if len(platform_images_cache) > 50: # Limite arbitraire pour éviter une croissance excessive
|
||
current_time = pygame.time.get_ticks()
|
||
cache_timeout = 30000 # 30 secondes
|
||
keys_to_remove = [k for k, v in platform_images_cache.items()
|
||
if current_time - v["last_used"] > cache_timeout]
|
||
for key in keys_to_remove:
|
||
del platform_images_cache[key]
|
||
|
||
# Liste des jeux
|
||
def draw_game_list(screen):
|
||
"""Affiche la liste des jeux avec un style moderne."""
|
||
platform = config.platforms[config.current_platform]
|
||
platform_name = config.platform_names.get(platform, platform)
|
||
games = config.filtered_games if config.filter_active or config.search_mode else config.games
|
||
game_count = len(games)
|
||
|
||
if not games:
|
||
logger.debug("Aucune liste de jeux disponible")
|
||
message = _("game_no_games")
|
||
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 + 80
|
||
rect_x = (config.screen_width - rect_width) // 2
|
||
rect_y = (config.screen_height - rect_height) // 2
|
||
|
||
screen.blit(OVERLAY, (0, 0))
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||
|
||
for i, line in enumerate(lines):
|
||
text_surface = config.font.render(line, True, THEME_COLORS["text"])
|
||
text_rect = text_surface.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2))
|
||
screen.blit(text_surface, text_rect)
|
||
return
|
||
|
||
line_height = config.small_font.get_height() + 10
|
||
header_height = line_height # hauteur de l'en-tête identique à une ligne
|
||
margin_top_bottom = 20
|
||
extra_margin_top = 20
|
||
extra_margin_bottom = 60
|
||
title_height = config.title_font.get_height() + 20
|
||
|
||
# Réserver de l'espace pour l'en-tête (header_height)
|
||
available_height = config.screen_height - title_height - extra_margin_top - extra_margin_bottom - 2 * margin_top_bottom - header_height
|
||
items_per_page = max(1, available_height // line_height)
|
||
|
||
rect_height = header_height + items_per_page * line_height + 2 * margin_top_bottom
|
||
rect_width = int(0.95 * config.screen_width)
|
||
rect_x = (config.screen_width - rect_width) // 2
|
||
rect_y = title_height + extra_margin_top + (config.screen_height - title_height - extra_margin_top - extra_margin_bottom - rect_height) // 2
|
||
|
||
config.scroll_offset = max(0, min(config.scroll_offset, max(0, len(games) - items_per_page)))
|
||
if config.current_game < config.scroll_offset:
|
||
config.scroll_offset = config.current_game
|
||
elif config.current_game >= config.scroll_offset + items_per_page:
|
||
config.scroll_offset = config.current_game - items_per_page + 1
|
||
|
||
screen.blit(OVERLAY, (0, 0))
|
||
|
||
if config.search_mode:
|
||
search_text = _("game_search").format(config.search_query + "_")
|
||
title_surface = config.search_font.render(search_text, True, THEME_COLORS["text"])
|
||
title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 20))
|
||
title_rect_inflated = title_rect.inflate(60, 30)
|
||
title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 10)
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12)
|
||
screen.blit(title_surface, title_rect)
|
||
elif config.filter_active:
|
||
filter_text = _("game_filter").format(config.search_query)
|
||
title_surface = config.font.render(filter_text, True, THEME_COLORS["fond_lignes"])
|
||
title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 20))
|
||
title_rect_inflated = title_rect.inflate(60, 30)
|
||
title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 10)
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12)
|
||
screen.blit(title_surface, title_rect)
|
||
else:
|
||
title_text = _("game_count").format(platform_name, game_count)
|
||
title_surface = config.title_font.render(title_text, True, THEME_COLORS["text"])
|
||
title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 20))
|
||
title_rect_inflated = title_rect.inflate(60, 30)
|
||
title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 10)
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12)
|
||
screen.blit(title_surface, title_rect)
|
||
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||
|
||
# Largeur colonne taille (15%) mini 120px, reste pour nom
|
||
size_col_width = max(120, int(rect_width * 0.15))
|
||
name_col_width = rect_width - 40 - size_col_width # padding horizontal 40
|
||
|
||
# ---- En-tête ----
|
||
header_name = _("game_header_name")
|
||
header_size = _("game_header_size")
|
||
header_y_center = rect_y + margin_top_bottom + header_height // 2
|
||
# Nom aligné gauche
|
||
header_name_surface = config.small_font.render(header_name, True, THEME_COLORS["text"])
|
||
header_name_rect = header_name_surface.get_rect()
|
||
header_name_rect.midleft = (rect_x + 20, header_y_center)
|
||
# Taille alignée droite
|
||
header_size_surface = config.small_font.render(header_size, True, THEME_COLORS["text"])
|
||
header_size_rect = header_size_surface.get_rect()
|
||
header_size_rect.midright = (rect_x + rect_width - 20, header_y_center)
|
||
screen.blit(header_name_surface, header_name_rect)
|
||
screen.blit(header_size_surface, header_size_rect)
|
||
# Ligne de séparation sous l'en-tête
|
||
separator_y = rect_y + margin_top_bottom + header_height
|
||
pygame.draw.line(screen, THEME_COLORS["border"], (rect_x + 20, separator_y), (rect_x + rect_width - 20, separator_y), 2)
|
||
|
||
# Position de départ des lignes après l'en-tête
|
||
list_start_y = rect_y + margin_top_bottom + header_height
|
||
|
||
for i in range(config.scroll_offset, min(config.scroll_offset + items_per_page, len(games))):
|
||
item = games[i]
|
||
if isinstance(item, (list, tuple)) and item:
|
||
game_name = item[0]
|
||
size_val = item[2] if len(item) > 2 else None
|
||
else:
|
||
game_name = str(item)
|
||
size_val = None
|
||
size_text = size_val if (isinstance(size_val, str) and size_val.strip()) else "N/A"
|
||
is_marked = i in getattr(config, 'selected_games', set())
|
||
color = THEME_COLORS["fond_lignes"] if (i == config.current_game or is_marked) else THEME_COLORS["text"]
|
||
prefix = "[X] " if is_marked else " "
|
||
truncated_name = truncate_text_middle(prefix + game_name, config.small_font, name_col_width, is_filename=False)
|
||
name_surface = config.small_font.render(truncated_name, True, color)
|
||
size_surface = config.small_font.render(size_text, True, THEME_COLORS["text"])
|
||
row_center_y = list_start_y + (i - config.scroll_offset) * line_height + line_height // 2
|
||
# Position nom (aligné à gauche dans la boite)
|
||
name_rect = name_surface.get_rect()
|
||
name_rect.midleft = (rect_x + 20, row_center_y)
|
||
size_rect = size_surface.get_rect()
|
||
size_rect.midright = (rect_x + rect_width - 20, row_center_y)
|
||
if i == config.current_game:
|
||
glow_width = rect_width - 40
|
||
glow_height = name_rect.height + 10
|
||
glow_surface = pygame.Surface((glow_width, glow_height), pygame.SRCALPHA)
|
||
pygame.draw.rect(glow_surface, THEME_COLORS["fond_lignes"] + (50,), (0, 0, glow_width, glow_height), border_radius=8)
|
||
screen.blit(glow_surface, (rect_x + 20, row_center_y - glow_height // 2))
|
||
screen.blit(name_surface, name_rect)
|
||
screen.blit(size_surface, size_rect)
|
||
|
||
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, THEME_COLORS["fond_lignes"], (x, scrollbar_y, 15, scrollbar_height), border_radius=4)
|
||
|
||
def format_size(size):
|
||
"""Convertit une taille en octets en format lisible."""
|
||
if not isinstance(size, (int, float)) or size == 0:
|
||
return "N/A"
|
||
|
||
for unit in ['o', 'Ko', 'Mo', 'Go', 'To']:
|
||
if size < 1024.0:
|
||
return f"{size:.1f} {unit}"
|
||
size /= 1024.0
|
||
return f"{size:.1f} Po"
|
||
|
||
|
||
def draw_history_list(screen):
|
||
# logger.debug(f"Dessin historique, history={config.history}, needs_redraw={config.needs_redraw}")
|
||
history = config.history if hasattr(config, 'history') else load_history()
|
||
history_count = len(history)
|
||
|
||
# Cherche une entrée en cours de téléchargement pour afficher la vitesse
|
||
speed_str = ""
|
||
for entry in history:
|
||
if entry.get("status") in ["Téléchargement", "downloading"]:
|
||
speed = entry.get("speed", 0.0)
|
||
if speed and speed > 0:
|
||
speed_str = f" - {speed:.2f} Mo/s"
|
||
break
|
||
|
||
screen.blit(OVERLAY, (0, 0))
|
||
title_text = _("history_title").format(history_count) + speed_str
|
||
title_surface = config.title_font.render(title_text, True, THEME_COLORS["text"])
|
||
title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 20))
|
||
title_rect_inflated = title_rect.inflate(60, 30)
|
||
title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 10)
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=12) # fond opaque
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12)
|
||
screen.blit(title_surface, title_rect)
|
||
|
||
# Define column widths as percentages of available space (give more space to status/error messages)
|
||
column_width_percentages = {
|
||
"platform": 0.15, # narrower platform column
|
||
"game_name": 0.45, # game name column
|
||
"size": 0.10, # size column remains compact
|
||
"status": 0.30 # wider status column for long error codes/messages
|
||
}
|
||
available_width = int(0.95 * config.screen_width - 60) # Total available width for columns
|
||
col_platform_width = int(available_width * column_width_percentages["platform"])
|
||
col_game_width = int(available_width * column_width_percentages["game_name"])
|
||
col_size_width = int(available_width * column_width_percentages["size"])
|
||
col_status_width = int(available_width * column_width_percentages["status"])
|
||
rect_width = int(0.95 * config.screen_width)
|
||
|
||
line_height = config.small_font.get_height() + 10
|
||
header_height = line_height
|
||
margin_top_bottom = 20
|
||
extra_margin_top = 40
|
||
extra_margin_bottom = 80
|
||
title_height = config.title_font.get_height() + 20
|
||
|
||
# Sécuriser current_history_item pour éviter IndexError
|
||
if history:
|
||
if config.current_history_item < 0 or config.current_history_item >= len(history):
|
||
config.current_history_item = max(0, min(len(history) - 1, config.current_history_item))
|
||
else:
|
||
config.current_history_item = 0
|
||
|
||
speed = 0.0
|
||
if history and history[config.current_history_item].get("status") in ["Téléchargement", "downloading"]:
|
||
speed = history[config.current_history_item].get("speed", 0.0)
|
||
if speed > 0:
|
||
speed_str = f"{speed:.2f} Mo/s"
|
||
title_text = _("history_title").format(history_count) + f" {speed_str}"
|
||
else:
|
||
title_text = _("history_title").format(history_count)
|
||
title_surface = config.title_font.render(title_text, True, THEME_COLORS["text"])
|
||
|
||
|
||
if not history:
|
||
logger.debug("Aucun historique disponible")
|
||
message = _("history_empty")
|
||
lines = wrap_text(message, config.font, config.screen_width - 80)
|
||
line_height = config.font.get_height() + 5
|
||
text_height = len(lines) * line_height
|
||
rect_height = text_height + 2 * margin_top_bottom
|
||
max_text_width = max([config.font.size(line)[0] for line in lines], default=300)
|
||
rect_width = max_text_width + 80
|
||
rect_x = (config.screen_width - rect_width) // 2
|
||
rect_y = (config.screen_height - rect_height) // 2
|
||
|
||
screen.blit(OVERLAY, (0, 0))
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||
|
||
for i, line in enumerate(lines):
|
||
text_surface = config.font.render(line, True, THEME_COLORS["text"])
|
||
text_rect = text_surface.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2))
|
||
screen.blit(text_surface, text_rect)
|
||
return
|
||
|
||
# Espace visible garanti entre le titre et la liste, et au-dessus du footer
|
||
top_gap = 20
|
||
bottom_reserved = 70 # réserve pour le footer (barre des contrôles) + marge visuelle (réduit)
|
||
|
||
# Positionner la liste juste après le titre, avec un espace dédié
|
||
# Utiliser le rectangle du titre déjà dessiné pour une meilleure précision
|
||
title_bottom = title_rect_inflated.bottom
|
||
rect_y = title_bottom + top_gap
|
||
|
||
# Calculer l'espace disponible en bas en réservant une zone pour le footer
|
||
available_height = max(0, config.screen_height - rect_y - bottom_reserved)
|
||
# Déterminer le nombre d'éléments par page en tenant compte de l'en-tête et des marges internes
|
||
items_per_page = max(1, (available_height - header_height - 2 * margin_top_bottom) // line_height)
|
||
|
||
rect_height = header_height + items_per_page * line_height + 2 * margin_top_bottom
|
||
rect_x = (config.screen_width - rect_width) // 2
|
||
|
||
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
|
||
|
||
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||
|
||
headers = [_("history_column_system"), _("history_column_game"), _("history_column_size"), _("history_column_status")]
|
||
header_y = rect_y + margin_top_bottom + header_height // 2
|
||
header_x_positions = [
|
||
rect_x + 20 + col_platform_width // 2,
|
||
rect_x + 20 + col_platform_width + col_game_width // 2,
|
||
rect_x + 20 + col_platform_width + col_game_width + col_size_width // 2,
|
||
rect_x + 20 + col_platform_width + col_game_width + col_size_width + col_status_width // 2
|
||
]
|
||
for header, x_pos in zip(headers, header_x_positions):
|
||
text_surface = config.small_font.render(header, True, THEME_COLORS["text"])
|
||
text_rect = text_surface.get_rect(center=(x_pos, header_y))
|
||
screen.blit(text_surface, text_rect)
|
||
|
||
separator_y = rect_y + margin_top_bottom + header_height
|
||
pygame.draw.line(screen, THEME_COLORS["border"], (rect_x + 20, separator_y), (rect_x + rect_width - 20, separator_y), 2)
|
||
|
||
for idx, i in enumerate(range(config.history_scroll_offset, min(config.history_scroll_offset + items_per_page, len(history)))):
|
||
entry = history[i]
|
||
platform = entry.get("platform", "Inconnu")
|
||
game_name = entry.get("game_name", "Inconnu")
|
||
|
||
# Correction du calcul de la taille
|
||
size = entry.get("total_size", 0)
|
||
color = THEME_COLORS["fond_lignes"] if i == config.current_history_item else THEME_COLORS["text"]
|
||
size_text = format_size(size)
|
||
|
||
status = entry.get("status", "Inconnu")
|
||
progress = entry.get("progress", 0)
|
||
progress = max(0, min(100, progress)) # Clamp progress between 0 and 100
|
||
|
||
# Precompute provider prefix once
|
||
provider_prefix = entry.get("provider_prefix") or (entry.get("provider") + ":" if entry.get("provider") else "")
|
||
# Compute status text (optimized version without redundant prefix for errors)
|
||
if status in ["Téléchargement", "downloading"]:
|
||
status_text = _("history_status_downloading").format(progress)
|
||
# Coerce to string and prefix provider when relevant
|
||
status_text = str(status_text or "")
|
||
if provider_prefix and not status_text.startswith(provider_prefix):
|
||
status_text = f"{provider_prefix} {status_text}"
|
||
elif status == "Extracting":
|
||
status_text = _("history_status_extracting").format(progress)
|
||
status_text = str(status_text or "")
|
||
if provider_prefix and not status_text.startswith(provider_prefix):
|
||
status_text = f"{provider_prefix} {status_text}"
|
||
elif status == "Download_OK":
|
||
# Completed: no provider prefix (per requirement)
|
||
status_text = _("history_status_completed")
|
||
status_text = str(status_text or "")
|
||
elif status == "Erreur":
|
||
# Prefer friendly mapped message now stored in 'message'
|
||
status_text = entry.get('message')
|
||
if not status_text:
|
||
# Some legacy entries might have only raw in result[1] or auxiliary field
|
||
status_text = entry.get('raw_error_realdebrid') or entry.get('error') or 'Échec'
|
||
# Coerce to string early for safe operations
|
||
status_text = str(status_text or "")
|
||
# Strip redundant prefixes if any
|
||
for prefix in ["Erreur :", "Erreur:", "Error:", "Error :"]:
|
||
if status_text.startswith(prefix):
|
||
status_text = status_text[len(prefix):].strip()
|
||
break
|
||
if provider_prefix and not status_text.startswith(provider_prefix):
|
||
status_text = f"{provider_prefix} {status_text}"
|
||
elif status == "Canceled":
|
||
status_text = _("history_status_canceled")
|
||
status_text = str(status_text or "")
|
||
else:
|
||
status_text = str(status or "")
|
||
|
||
# Determine color dedicated to status (independent from selection for better readability)
|
||
if status == "Erreur":
|
||
status_color = THEME_COLORS.get("error_text", (255, 0, 0))
|
||
elif status == "Canceled":
|
||
status_color = THEME_COLORS.get("warning_text", (255, 100, 0))
|
||
elif status == "Download_OK":
|
||
# Use green OK color
|
||
status_color = THEME_COLORS.get("fond_lignes", (0, 255, 0))
|
||
else:
|
||
status_color = THEME_COLORS.get("text", (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)
|
||
size_text = truncate_text_end(size_text, config.small_font, col_size_width - 10)
|
||
status_text = truncate_text_middle(str(status_text or ""), config.small_font, col_status_width - 10, is_filename=False)
|
||
|
||
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)
|
||
size_surface = config.small_font.render(size_text, True, color) # Correction ici
|
||
status_surface = config.small_font.render(status_text, True, status_color)
|
||
|
||
platform_rect = platform_surface.get_rect(center=(header_x_positions[0], y_pos))
|
||
game_rect = game_surface.get_rect(center=(header_x_positions[1], y_pos))
|
||
size_rect = size_surface.get_rect(center=(header_x_positions[2], y_pos))
|
||
status_rect = status_surface.get_rect(center=(header_x_positions[3], y_pos))
|
||
|
||
if i == config.current_history_item:
|
||
glow_surface = pygame.Surface((rect_width - 40, line_height), pygame.SRCALPHA)
|
||
pygame.draw.rect(glow_surface, THEME_COLORS["fond_lignes"] + (50,), (0, 0, rect_width - 40, line_height), border_radius=8)
|
||
screen.blit(glow_surface, (rect_x + 20, y_pos - line_height // 2))
|
||
|
||
screen.blit(platform_surface, platform_rect)
|
||
screen.blit(game_surface, game_rect)
|
||
screen.blit(size_surface, size_rect)
|
||
screen.blit(status_surface, status_rect)
|
||
|
||
if len(history) > items_per_page:
|
||
try:
|
||
draw_history_scrollbar(
|
||
screen,
|
||
config.history_scroll_offset,
|
||
len(history),
|
||
items_per_page,
|
||
rect_x + rect_width - 10,
|
||
rect_y,
|
||
rect_height
|
||
)
|
||
except NameError as e:
|
||
logger.error(f"Erreur : draw_history_scrollbar non défini: {str(e)}")
|
||
|
||
# Barre de défilement de l'historique
|
||
def draw_history_scrollbar(screen, scroll_offset, total_items, visible_items, x, y, height):
|
||
"""Affiche la barre de défilement avec un style moderne."""
|
||
if total_items <= visible_items:
|
||
return
|
||
game_area_height = height
|
||
scrollbar_height = game_area_height * (visible_items / total_items) - 10
|
||
scrollbar_y = y + (game_area_height - scrollbar_height) * (scroll_offset / max(1, total_items - visible_items)) + 10
|
||
pygame.draw.rect(screen, THEME_COLORS["fond_lignes"], (x, scrollbar_y, 5, scrollbar_height), border_radius=4)
|
||
|
||
# Écran confirmation vider historique
|
||
def draw_clear_history_dialog(screen):
|
||
"""Affiche la boîte de dialogue de confirmation pour vider l'historique."""
|
||
screen.blit(OVERLAY, (0, 0))
|
||
|
||
message = _("confirm_clear_history")
|
||
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 = int(config.screen_height * 0.0463)
|
||
margin_top_bottom = 20
|
||
rect_height = text_height + button_height + 2 * margin_top_bottom
|
||
max_text_width = max([config.font.size(line)[0] for line in wrapped_message], default=300)
|
||
rect_width = max_text_width + 150
|
||
rect_x = (config.screen_width - rect_width) // 2
|
||
rect_y = (config.screen_height - rect_height) // 2
|
||
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||
|
||
for i, line in enumerate(wrapped_message):
|
||
text = config.font.render(line, True, THEME_COLORS["text"])
|
||
text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2))
|
||
screen.blit(text, text_rect)
|
||
|
||
button_width = min(160, (rect_width - 60) // 2)
|
||
draw_stylized_button(screen, _("button_yes"), rect_x + rect_width // 2 - button_width - 10, rect_y + text_height + margin_top_bottom, button_width, button_height, selected=config.confirm_clear_selection == 1)
|
||
draw_stylized_button(screen, _("button_no"), rect_x + rect_width // 2 + 10, rect_y + text_height + margin_top_bottom, button_width, button_height, selected=config.confirm_clear_selection == 0)
|
||
|
||
def draw_cancel_download_dialog(screen):
|
||
"""Affiche la boîte de dialogue de confirmation pour annuler un téléchargement."""
|
||
screen.blit(OVERLAY, (0, 0))
|
||
|
||
message = _("confirm_cancel_download")
|
||
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 = int(config.screen_height * 0.0463)
|
||
margin_top_bottom = 20
|
||
rect_height = text_height + button_height + 2 * margin_top_bottom
|
||
max_text_width = max([config.font.size(line)[0] for line in wrapped_message], default=300)
|
||
rect_width = max_text_width + 150
|
||
rect_x = (config.screen_width - rect_width) // 2
|
||
rect_y = (config.screen_height - rect_height) // 2
|
||
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||
|
||
for i, line in enumerate(wrapped_message):
|
||
text = config.font.render(line, True, THEME_COLORS["text"])
|
||
text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2))
|
||
screen.blit(text, text_rect)
|
||
|
||
button_width = min(160, (rect_width - 60) // 2)
|
||
draw_stylized_button(screen, _("button_yes"), rect_x + rect_width // 2 - button_width - 10, rect_y + text_height + margin_top_bottom, button_width, button_height, selected=config.confirm_cancel_selection == 1)
|
||
draw_stylized_button(screen, _("button_no"), rect_x + rect_width // 2 + 10, rect_y + text_height + margin_top_bottom, button_width, button_height, selected=config.confirm_cancel_selection == 0)
|
||
|
||
# Affichage du clavier virtuel sur non-PC
|
||
def draw_virtual_keyboard(screen):
|
||
"""Affiche un clavier virtuel avec un style moderne."""
|
||
keyboard_layout = [
|
||
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
||
['A', 'Z', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'],
|
||
['Q', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M'],
|
||
['W', 'X', 'C', 'V', 'B', 'N']
|
||
]
|
||
key_width = int(config.screen_width * 0.03125)
|
||
key_height = int(config.screen_height * 0.0556)
|
||
key_spacing = int(config.screen_width * 0.0052)
|
||
keyboard_width = len(keyboard_layout[0]) * (key_width + key_spacing) - key_spacing
|
||
keyboard_height = len(keyboard_layout) * (key_height + key_spacing) - key_spacing
|
||
start_x = (config.screen_width - keyboard_width) // 2
|
||
search_bottom_y = int(config.screen_height * 0.111) + (config.search_font.get_height() + 40) // 2
|
||
controls_y = config.screen_height - int(config.screen_height * 0.037)
|
||
available_height = controls_y - search_bottom_y
|
||
start_y = search_bottom_y + (available_height - keyboard_height - 40) // 2
|
||
|
||
keyboard_rect = pygame.Rect(start_x - 20, start_y - 20, keyboard_width + 40, keyboard_height + 40)
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], keyboard_rect, border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], keyboard_rect, 2, border_radius=12)
|
||
|
||
for row_idx, row in enumerate(keyboard_layout):
|
||
for col_idx, key in enumerate(row):
|
||
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, THEME_COLORS["fond_lignes"] + (150,), key_rect, border_radius=8)
|
||
else:
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], key_rect, border_radius=8)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], key_rect, 1, border_radius=8)
|
||
text = config.font.render(key, True, THEME_COLORS["text"])
|
||
text_rect = text.get_rect(center=key_rect.center)
|
||
screen.blit(text, text_rect)
|
||
|
||
# Écran de progression de téléchargement/extraction
|
||
def draw_progress_screen(screen):
|
||
"""Affiche l'écran de progression des téléchargements avec un style moderne."""
|
||
if not config.download_tasks:
|
||
logger.debug("Aucune tâche de téléchargement active")
|
||
return
|
||
|
||
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"]
|
||
# S'assurer que le pourcentage est entre 0 et 100
|
||
progress_percent = max(0, min(100, progress_percent))
|
||
|
||
screen.blit(OVERLAY, (0, 0))
|
||
|
||
title_text = _("download_status").format(status, truncate_text_middle(game_name, config.font, config.screen_width - 200))
|
||
title_lines = wrap_text(title_text, config.font, config.screen_width - 80)
|
||
line_height = config.font.get_height() + 5
|
||
text_height = len(title_lines) * line_height
|
||
margin_top_bottom = 20
|
||
bar_height = int(config.screen_height * 0.0278)
|
||
percent_height = config.progress_font.get_height() + 5
|
||
rect_height = text_height + bar_height + percent_height + 3 * margin_top_bottom
|
||
max_text_width = max([config.font.size(line)[0] for line in title_lines], default=300)
|
||
bar_width = max_text_width
|
||
rect_width = max_text_width + 80
|
||
rect_x = (config.screen_width - rect_width) // 2
|
||
rect_y = (config.screen_height - rect_height) // 2
|
||
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||
|
||
for i, line in enumerate(title_lines):
|
||
title_render = config.font.render(line, True, THEME_COLORS["text"])
|
||
title_rect = title_render.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2))
|
||
screen.blit(title_render, title_rect)
|
||
|
||
bar_y = rect_y + text_height + margin_top_bottom
|
||
progress_width = 0
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x + 20, bar_y, bar_width, bar_height), border_radius=8)
|
||
if total_size > 0:
|
||
# Limiter le pourcentage entre 0 et 100 pour l'affichage de la barre
|
||
progress_width = int(bar_width * (min(100, max(0, progress_percent)) / 100))
|
||
|
||
# Écran avertissement extension non supportée téléchargement
|
||
def draw_extension_warning(screen):
|
||
"""Affiche un avertissement pour une extension non reconnue ou un fichier ZIP."""
|
||
if not config.pending_download:
|
||
logger.error("config.pending_download est None ou vide dans extension_warning")
|
||
message = "Erreur : Aucun téléchargement en attente."
|
||
is_zip = False
|
||
game_name = "Inconnu"
|
||
else:
|
||
url, platform, game_name, is_zip_non_supported = config.pending_download
|
||
# Log réduit: pas de détail verbeux ici
|
||
is_zip = is_zip_non_supported
|
||
if not game_name:
|
||
game_name = "Inconnu"
|
||
logger.warning("game_name vide, utilisation de 'Inconnu'")
|
||
|
||
if is_zip:
|
||
core = _("extension_warning_zip").format(game_name)
|
||
hint = ""
|
||
else:
|
||
# Ajout d'un indice pour activer le téléchargement des extensions inconnues
|
||
try:
|
||
hint = _("extension_warning_enable_unknown_hint")
|
||
except Exception:
|
||
hint = ""
|
||
core = _("extension_warning_unsupported").format(game_name)
|
||
|
||
# Nettoyer et préparer les lignes
|
||
max_width = config.screen_width - 80
|
||
core_lines = wrap_text(core, config.font, max_width)
|
||
hint_text = (hint or "").replace("\n", " ").strip()
|
||
hint_lines = wrap_text(hint_text, config.small_font, max_width) if hint_text else []
|
||
|
||
try:
|
||
line_height_core = config.font.get_height() + 5
|
||
line_height_hint = config.small_font.get_height() + 4
|
||
spacing_between = 6 if hint_lines else 0
|
||
text_height = len(core_lines) * line_height_core + (spacing_between) + len(hint_lines) * line_height_hint
|
||
button_height = int(config.screen_height * 0.0463)
|
||
margin_top_bottom = 20
|
||
rect_height = text_height + button_height + 2 * margin_top_bottom
|
||
max_text_width = max(
|
||
[config.font.size(l)[0] for l in core_lines] + ([config.small_font.size(l)[0] for l in hint_lines] if hint_lines else []),
|
||
default=300,
|
||
)
|
||
rect_width = max_text_width + 80
|
||
rect_x = (config.screen_width - rect_width) // 2
|
||
rect_y = (config.screen_height - rect_height) // 2
|
||
|
||
screen.blit(OVERLAY, (0, 0))
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||
|
||
# Lignes du cœur du message (orange)
|
||
for i, line in enumerate(core_lines):
|
||
text_surface = config.font.render(line, True, THEME_COLORS["warning_text"])
|
||
text_rect = text_surface.get_rect(center=(
|
||
config.screen_width // 2,
|
||
rect_y + margin_top_bottom + i * line_height_core + line_height_core // 2,
|
||
))
|
||
screen.blit(text_surface, text_rect)
|
||
|
||
# Lignes d'indice (blanc/gris) si présentes
|
||
if hint_lines:
|
||
hint_start_y = rect_y + margin_top_bottom + len(core_lines) * line_height_core + spacing_between
|
||
for j, hline in enumerate(hint_lines):
|
||
hsurf = config.small_font.render(hline, True, THEME_COLORS["text"])
|
||
hrect = hsurf.get_rect(center=(
|
||
config.screen_width // 2,
|
||
hint_start_y + j * line_height_hint + line_height_hint // 2,
|
||
))
|
||
screen.blit(hsurf, hrect)
|
||
|
||
draw_stylized_button(screen, _("button_yes"), rect_x + rect_width // 2 - 180, rect_y + text_height + margin_top_bottom, 160, button_height, selected=config.extension_confirm_selection == 1)
|
||
draw_stylized_button(screen, _("button_no"), rect_x + rect_width // 2 + 20, rect_y + text_height + margin_top_bottom, 160, button_height, selected=config.extension_confirm_selection == 0)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Erreur lors du rendu de extension_warning : {str(e)}")
|
||
error_message = "Erreur d'affichage de l'avertissement."
|
||
wrapped_error = wrap_text(error_message, config.font, config.screen_width - 80)
|
||
line_height = config.font.get_height() + 5
|
||
rect_height = len(wrapped_error) * line_height + 2 * 20
|
||
max_text_width = max([config.font.size(line)[0] for line in wrapped_error], default=300)
|
||
rect_width = max_text_width + 80
|
||
rect_x = (config.screen_width - rect_width) // 2
|
||
rect_y = (config.screen_height - rect_height) // 2
|
||
|
||
screen.blit(OVERLAY, (0, 0))
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||
|
||
for i, line in enumerate(wrapped_error):
|
||
error_surface = config.font.render(line, True, THEME_COLORS["error_text"])
|
||
error_rect = error_surface.get_rect(center=(config.screen_width // 2, rect_y + 20 + i * line_height + line_height // 2))
|
||
screen.blit(error_surface, error_rect)
|
||
|
||
# Affichage des contrôles en bas de page
|
||
def draw_controls(screen, menu_state, current_music_name=None, music_popup_start_time=0):
|
||
"""Affiche les contrôles sur une seule ligne en bas de l’écran."""
|
||
start_button = get_control_display('start', 'START')
|
||
start_text = _("controls_action_start")
|
||
control_text = f"RGSX v{config.app_version} - {start_button} : {start_text}"
|
||
|
||
# Ajouter le nom de la musique si disponible
|
||
if config.current_music_name and config.music_popup_start_time > 0:
|
||
current_time = pygame.time.get_ticks() / 1000
|
||
if current_time - config.music_popup_start_time < 3.0: # Afficher pendant 3 secondes
|
||
control_text += f" | {config.current_music_name}"
|
||
max_width = config.screen_width - 40
|
||
wrapped_controls = wrap_text(control_text, config.small_font, max_width)
|
||
line_height = config.small_font.get_height() + 5
|
||
rect_height = len(wrapped_controls) * line_height + 20
|
||
rect_y = config.screen_height - rect_height - 5
|
||
rect_x = (config.screen_width - max_width) // 2
|
||
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, max_width, rect_height), border_radius=8)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, max_width, rect_height), 1, border_radius=8)
|
||
|
||
for i, line in enumerate(wrapped_controls):
|
||
text_surface = config.small_font.render(line, True, THEME_COLORS["text"])
|
||
text_rect = text_surface.get_rect(center=(config.screen_width // 2, rect_y + 10 + i * line_height + line_height // 2))
|
||
screen.blit(text_surface, text_rect)
|
||
|
||
# Menu pause
|
||
def draw_language_menu(screen):
|
||
"""Dessine le menu de sélection de langue avec un style moderne.
|
||
|
||
Améliorations:
|
||
- Hauteur des boutons réduite et responsive selon la taille d'écran.
|
||
- Bloc (titre + liste de langues) centré verticalement.
|
||
- Gestion d'overflow: réduit légèrement la hauteur/espacement si nécessaire.
|
||
"""
|
||
from language import get_available_languages, get_language_name
|
||
|
||
screen.blit(OVERLAY, (0, 0))
|
||
|
||
# Obtenir les langues disponibles
|
||
available_languages = get_available_languages()
|
||
|
||
if not available_languages:
|
||
logger.error("Aucune langue disponible")
|
||
return
|
||
|
||
# Titre (mesuré d'abord pour connaître la hauteur réelle du fond)
|
||
title_text = _("language_select_title")
|
||
title_surface = config.font.render(title_text, True, THEME_COLORS["text"])
|
||
# On calcule un rect neutre, on positionnera ensuite pour centrer le bloc
|
||
title_rect = title_surface.get_rect()
|
||
# Padding responsive plus léger pour réduire la hauteur
|
||
hpad = max(24, min(36, int(config.screen_width * 0.04)))
|
||
vpad = max(8, min(14, int(title_surface.get_height() * 0.4)))
|
||
title_bg_rect = title_rect.inflate(hpad, vpad)
|
||
|
||
# Dimensions responsives des boutons
|
||
# Largeur bornée entre 260 et 380px (~40% de la largeur écran)
|
||
button_width = max(260, min(380, int(config.screen_width * 0.4)))
|
||
# Hauteur réduite et responsive (env. 5.5% de la hauteur écran), bornée 28..56
|
||
button_height = max(28, min(56, int(config.screen_height * 0.055)))
|
||
# Espacement vertical proportionnel et borné
|
||
button_spacing = max(8, int(button_height * 0.35))
|
||
|
||
# Calcul des dimensions globales pour centrer verticalement (titre + boutons)
|
||
n = len(available_languages)
|
||
total_buttons_height = n * button_height + (n - 1) * button_spacing
|
||
content_height = title_bg_rect.height + button_spacing + total_buttons_height
|
||
|
||
# Si le contenu dépasse, on réduit légèrement la hauteur/espacement jusqu'à rentrer
|
||
available_h = config.screen_height - 80 # marges haut/bas de confort
|
||
safety_counter = 0
|
||
while content_height > available_h and safety_counter < 20:
|
||
if button_height > 28:
|
||
button_height -= 2
|
||
elif button_spacing > 6:
|
||
button_spacing -= 1
|
||
else:
|
||
break
|
||
total_buttons_height = n * button_height + (n - 1) * button_spacing
|
||
content_height = title_bg_rect.height + button_spacing + total_buttons_height
|
||
safety_counter += 1
|
||
|
||
# Positionner le bloc au centre verticalement
|
||
content_top = max(10, (config.screen_height - content_height) // 2)
|
||
# Positionner le titre
|
||
title_bg_rect.centerx = config.screen_width // 2
|
||
title_bg_rect.y = content_top
|
||
title_rect.center = (title_bg_rect.centerx, title_bg_rect.y + title_bg_rect.height // 2)
|
||
|
||
# Dessiner le titre
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_bg_rect, border_radius=10)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], title_bg_rect, 2, border_radius=10)
|
||
screen.blit(title_surface, title_rect)
|
||
|
||
# Démarrer la liste juste sous le titre avec le même écart que les boutons
|
||
start_y = title_bg_rect.bottom + button_spacing
|
||
|
||
for i, lang_code in enumerate(available_languages):
|
||
# Obtenir le nom de la langue
|
||
lang_name = get_language_name(lang_code)
|
||
|
||
# Position du bouton
|
||
button_x = (config.screen_width - button_width) // 2
|
||
button_y = start_y + i * (button_height + button_spacing)
|
||
|
||
# Dessiner le bouton
|
||
button_color = THEME_COLORS["button_hover"] if i == config.selected_language_index else THEME_COLORS["button_idle"]
|
||
pygame.draw.rect(screen, button_color, (button_x, button_y, button_width, button_height), border_radius=10)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (button_x, button_y, button_width, button_height), 2, border_radius=10)
|
||
|
||
# Texte du bouton
|
||
text_surface = config.font.render(lang_name, True, THEME_COLORS["text"])
|
||
text_rect = text_surface.get_rect(center=(button_x + button_width // 2, button_y + button_height // 2))
|
||
screen.blit(text_surface, text_rect)
|
||
|
||
# Instructions (placer juste au-dessus du footer sans chevauchement)
|
||
instruction_text = _("language_select_instruction")
|
||
instruction_surface = config.small_font.render(instruction_text, True, THEME_COLORS["text"])
|
||
footer_reserved = 72 # hauteur approximative footer (barre bas) + marge
|
||
bottom_margin = 12
|
||
instruction_y = config.screen_height - footer_reserved - bottom_margin
|
||
# Empêcher un chevauchement avec les derniers boutons si espace réduit
|
||
last_button_bottom = start_y + (len(available_languages) - 1) * (button_height + button_spacing) + button_height
|
||
min_gap = 16
|
||
if instruction_y - last_button_bottom < min_gap:
|
||
instruction_y = last_button_bottom + min_gap
|
||
instruction_rect = instruction_surface.get_rect(center=(config.screen_width // 2, instruction_y))
|
||
screen.blit(instruction_surface, instruction_rect)
|
||
|
||
def draw_menu_instruction(screen, instruction_text, last_button_bottom=None):
|
||
"""Dessine une ligne d'instruction centrée au-dessus du footer.
|
||
|
||
- Réserve une zone footer (72px) + marge bas.
|
||
- Si last_button_bottom est fourni, s'assure d'un écart minimal (16px).
|
||
- Utilise la petite police et couleurs du thème.
|
||
"""
|
||
if not instruction_text:
|
||
return
|
||
try:
|
||
instruction_surface = config.small_font.render(instruction_text, True, THEME_COLORS["text"])
|
||
footer_reserved = 72
|
||
bottom_margin = 12
|
||
instruction_y = config.screen_height - footer_reserved - bottom_margin
|
||
min_gap = 16
|
||
if last_button_bottom is not None and instruction_y - last_button_bottom < min_gap:
|
||
instruction_y = last_button_bottom + min_gap
|
||
instruction_rect = instruction_surface.get_rect(center=(config.screen_width // 2, instruction_y))
|
||
screen.blit(instruction_surface, instruction_rect)
|
||
except Exception as e:
|
||
logger.error(f"Erreur draw_menu_instruction: {e}")
|
||
|
||
def draw_display_menu(screen):
|
||
"""Affiche le sous-menu Affichage (layout, taille de police, systèmes non supportés)."""
|
||
screen.blit(OVERLAY, (0, 0))
|
||
|
||
# États actuels
|
||
layout_str = f"{getattr(config, 'GRID_COLS', 3)}x{getattr(config, 'GRID_ROWS', 4)}"
|
||
font_scale = config.accessibility_settings.get("font_scale", 1.0)
|
||
from rgsx_settings import get_show_unsupported_platforms, get_allow_unknown_extensions
|
||
show_unsupported = get_show_unsupported_platforms()
|
||
allow_unknown = get_allow_unknown_extensions()
|
||
|
||
# Compter les systèmes non supportés actuellement masqués
|
||
unsupported_list = getattr(config, "unsupported_platforms", []) or []
|
||
try:
|
||
hidden_count = 0 if show_unsupported else len(list(unsupported_list))
|
||
except Exception:
|
||
hidden_count = 0
|
||
if hidden_count > 0:
|
||
unsupported_label = _("menu_show_unsupported_and_hidden").format(hidden_count)
|
||
else:
|
||
unsupported_label = _("menu_show_unsupported_all_displayed")
|
||
|
||
# Libellés
|
||
options = [
|
||
f"{_('display_layout')}: {layout_str}",
|
||
_("accessibility_font_size").format(f"{font_scale:.1f}"),
|
||
unsupported_label,
|
||
_("menu_allow_unknown_ext_on") if allow_unknown else _("menu_allow_unknown_ext_off"),
|
||
_("menu_filter_platforms"),
|
||
]
|
||
|
||
selected = getattr(config, 'display_menu_selection', 0)
|
||
|
||
# Dimensions du cadre (cohérent avec le menu pause)
|
||
title_text = _("menu_display")
|
||
title_surface = config.title_font.render(title_text, True, THEME_COLORS["text"])
|
||
title_height = title_surface.get_height() + 10
|
||
menu_width = int(config.screen_width * 0.7)
|
||
button_height = int(config.screen_height * 0.0463)
|
||
margin_top_bottom = 20
|
||
vertical_spacing = 10
|
||
menu_height = title_height + len(options) * (button_height + vertical_spacing) + 2 * margin_top_bottom
|
||
menu_x = (config.screen_width - menu_width) // 2
|
||
menu_y = (config.screen_height - menu_height) // 2
|
||
|
||
# Cadre
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (menu_x, menu_y, menu_width, menu_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (menu_x, menu_y, menu_width, menu_height), 2, border_radius=12)
|
||
|
||
# Titre centré dans le cadre
|
||
title_rect = title_surface.get_rect(center=(config.screen_width // 2, menu_y + margin_top_bottom + title_surface.get_height() // 2))
|
||
screen.blit(title_surface, title_rect)
|
||
|
||
# Boutons des options
|
||
for i, option_text in enumerate(options):
|
||
y = menu_y + margin_top_bottom + title_height + i * (button_height + vertical_spacing)
|
||
draw_stylized_button(
|
||
screen,
|
||
option_text,
|
||
menu_x + 20,
|
||
y,
|
||
menu_width - 40,
|
||
button_height,
|
||
selected=(i == selected)
|
||
)
|
||
|
||
# Aide en bas de l'écran
|
||
instruction_text = _("language_select_instruction")
|
||
instruction_surface = config.small_font.render(instruction_text, True, THEME_COLORS["text"])
|
||
instruction_rect = instruction_surface.get_rect(center=(config.screen_width // 2, config.screen_height - 50))
|
||
screen.blit(instruction_surface, instruction_rect)
|
||
|
||
def draw_pause_menu(screen, selected_option):
|
||
"""Dessine le menu pause racine (catégories)."""
|
||
screen.blit(OVERLAY, (0, 0))
|
||
# Nouvel ordre: Language / Controls / Display / Games / Settings / Restart / Quit
|
||
options = [
|
||
_("menu_language") if _ else "Language", # 0 -> sélecteur de langue direct
|
||
_("menu_controls"), # 1 -> sous-menu controls
|
||
_("menu_display"), # 2 -> sous-menu display
|
||
_("menu_games") if _ else "Games", # 3 -> sous-menu games (history + sources + update)
|
||
_("menu_settings_category") if _ else "Settings", # 4 -> sous-menu settings
|
||
_("menu_restart"), # 5 -> reboot
|
||
_("menu_quit") # 6 -> quit
|
||
]
|
||
menu_width = int(config.screen_width * 0.6)
|
||
button_height = int(config.screen_height * 0.048)
|
||
margin_top_bottom = 24
|
||
menu_height = len(options) * (button_height + 12) + 2 * margin_top_bottom
|
||
menu_x = (config.screen_width - menu_width) // 2
|
||
menu_y = (config.screen_height - menu_height) // 2
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (menu_x, menu_y, menu_width, menu_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (menu_x, menu_y, menu_width, menu_height), 2, border_radius=12)
|
||
for i, option in enumerate(options):
|
||
draw_stylized_button(
|
||
screen,
|
||
option,
|
||
menu_x + 20,
|
||
menu_y + margin_top_bottom + i * (button_height + 12),
|
||
menu_width - 40,
|
||
button_height,
|
||
selected=i == selected_option
|
||
)
|
||
config.pause_menu_total_options = len(options)
|
||
|
||
# Instruction contextuelle pour l'option sélectionnée
|
||
# Mapping des clés i18n parallèles à la liste options (même ordre)
|
||
instruction_keys = [
|
||
"instruction_pause_language",
|
||
"instruction_pause_controls",
|
||
"instruction_pause_display",
|
||
"instruction_pause_games",
|
||
"instruction_pause_settings",
|
||
"instruction_pause_restart",
|
||
"instruction_pause_quit",
|
||
]
|
||
try:
|
||
key = instruction_keys[selected_option]
|
||
instruction_text = _(key)
|
||
except Exception:
|
||
instruction_text = "" # Sécurité si index hors borne
|
||
|
||
if instruction_text:
|
||
# Calcul de la position du dernier bouton pour éviter chevauchement
|
||
last_button_bottom = menu_y + margin_top_bottom + (len(options) - 1) * (button_height + 12) + button_height
|
||
draw_menu_instruction(screen, instruction_text, last_button_bottom)
|
||
|
||
def _draw_submenu_generic(screen, title, options, selected_index):
|
||
"""Helper générique pour dessiner un sous-menu hiérarchique."""
|
||
screen.blit(OVERLAY, (0, 0))
|
||
menu_width = int(config.screen_width * 0.72)
|
||
button_height = int(config.screen_height * 0.045)
|
||
margin_top_bottom = 26
|
||
menu_height = (len(options)+1) * (button_height + 10) + 2 * margin_top_bottom # +1 pour le titre
|
||
menu_x = (config.screen_width - menu_width) // 2
|
||
menu_y = (config.screen_height - menu_height) // 2
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (menu_x, menu_y, menu_width, menu_height), border_radius=14)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (menu_x, menu_y, menu_width, menu_height), 2, border_radius=14)
|
||
# Title
|
||
title_surface = config.font.render(title, True, THEME_COLORS["text"])
|
||
title_rect = title_surface.get_rect(center=(config.screen_width//2, menu_y + margin_top_bottom//2 + title_surface.get_height()//2))
|
||
screen.blit(title_surface, title_rect)
|
||
# Options
|
||
start_y = title_rect.bottom + 10
|
||
for i, opt in enumerate(options):
|
||
draw_stylized_button(
|
||
screen,
|
||
opt,
|
||
menu_x + 20,
|
||
start_y + i * (button_height + 10),
|
||
menu_width - 40,
|
||
button_height,
|
||
selected=(i == selected_index)
|
||
)
|
||
|
||
def draw_pause_controls_menu(screen, selected_index):
|
||
options = [
|
||
_("menu_controls"), # aide contrôles (réutilisée)
|
||
_("menu_remap_controls"), # remap
|
||
_("menu_back") if _ else "Back"
|
||
]
|
||
_draw_submenu_generic(screen, _("menu_controls") if _ else "Controls", options, selected_index)
|
||
# Instructions contextuelles
|
||
instruction_keys = [
|
||
"instruction_controls_help", # pour menu_controls (afficher l'aide)
|
||
"instruction_controls_remap", # remap
|
||
"instruction_generic_back", # retour
|
||
]
|
||
key = instruction_keys[selected_index] if 0 <= selected_index < len(instruction_keys) else None
|
||
if key:
|
||
last_button_bottom = None # recalculer via géométrie si nécessaire; ici on réutilise calcul simple
|
||
# Reconstituer la position du dernier bouton comme dans _draw_submenu_generic
|
||
menu_width = int(config.screen_width * 0.72)
|
||
button_height = int(config.screen_height * 0.045)
|
||
margin_top_bottom = 26
|
||
menu_height = (len(options)+1) * (button_height + 10) + 2 * margin_top_bottom
|
||
menu_y = (config.screen_height - menu_height) // 2
|
||
# Title height approximatif
|
||
title_surface = config.font.render("X", True, THEME_COLORS["text"]) # hauteur représentative
|
||
title_rect_height = title_surface.get_height()
|
||
start_y = menu_y + margin_top_bottom//2 + title_rect_height + 10 + 10 # approx: title center adjust + bottom spacing
|
||
last_button_bottom = start_y + (len(options)-1) * (button_height + 10) + button_height
|
||
text = _(key)
|
||
if key == "instruction_display_hide_premium":
|
||
# Inject dynamic list of premium providers from config.PREMIUM_HOST_MARKERS
|
||
try:
|
||
from config import PREMIUM_HOST_MARKERS
|
||
# Clean, preserve order, remove duplicates (case-insensitive)
|
||
seen = set()
|
||
providers_clean = []
|
||
for p in PREMIUM_HOST_MARKERS:
|
||
if not p: continue
|
||
norm = p.strip()
|
||
if not norm: continue
|
||
low = norm.lower()
|
||
if low in seen: continue
|
||
seen.add(low)
|
||
providers_clean.append(norm)
|
||
providers_str = ", ".join(providers_clean)
|
||
if not providers_str:
|
||
providers_str = "-"
|
||
if "{providers}" in text:
|
||
try:
|
||
text = text.format(providers=providers_str)
|
||
except Exception:
|
||
# Fallback if formatting fails
|
||
text = f"{text.replace('{providers}','').strip()} {providers_str}".strip()
|
||
else:
|
||
# Append providers if placeholder missing (backward compatibility)
|
||
text = f"{text} : {providers_str}" if providers_str else text
|
||
except Exception:
|
||
pass
|
||
draw_menu_instruction(screen, text, last_button_bottom)
|
||
|
||
def draw_pause_display_menu(screen, selected_index):
|
||
from rgsx_settings import (
|
||
get_show_unsupported_platforms,
|
||
get_allow_unknown_extensions,
|
||
get_hide_premium_systems,
|
||
get_font_family
|
||
)
|
||
# Layout label
|
||
layouts = [(3,3),(3,4),(4,3),(4,4)]
|
||
try:
|
||
idx = layouts.index((config.GRID_COLS, config.GRID_ROWS))
|
||
except ValueError:
|
||
idx = 0
|
||
layout_value = f"{layouts[idx][0]}x{layouts[idx][1]}"
|
||
layout_txt = f"{_('submenu_display_layout') if _ else 'Layout'}: < {layout_value} >"
|
||
# Font size
|
||
opts = getattr(config, 'font_scale_options', [0.75, 1.0, 1.25, 1.5, 1.75])
|
||
cur_idx = getattr(config, 'current_font_scale_index', 1)
|
||
font_value = f"{opts[cur_idx]}x"
|
||
font_txt = f"{_('submenu_display_font_size') if _ else 'Font Size'}: < {font_value} >"
|
||
# Font family
|
||
current_family = get_font_family()
|
||
# Nom user-friendly
|
||
family_map = {
|
||
"pixel": "Pixel",
|
||
"dejavu": "DejaVu Sans"
|
||
}
|
||
fam_label = family_map.get(current_family, current_family)
|
||
font_family_txt = f"{_('submenu_display_font_family') if _ else 'Font'}: < {fam_label} >"
|
||
|
||
unsupported = get_show_unsupported_platforms()
|
||
status_unsupported = _('status_on') if unsupported else _('status_off')
|
||
# Construire label sans statut pour insérer les chevrons proprement
|
||
raw_unsupported_label = _('submenu_display_show_unsupported') if _ else 'Show unsupported systems: {status}'
|
||
# Retirer éventuel placeholder et ponctuation finale
|
||
if '{status}' in raw_unsupported_label:
|
||
raw_unsupported_label = raw_unsupported_label.split('{status}')[0].rstrip(' :')
|
||
unsupported_txt = f"{raw_unsupported_label}: < {status_unsupported} >"
|
||
allow_unknown = get_allow_unknown_extensions()
|
||
status_unknown = _('status_on') if allow_unknown else _('status_off')
|
||
raw_unknown_label = _('submenu_display_allow_unknown_ext') if _ else 'Hide unknown ext warn: {status}'
|
||
if '{status}' in raw_unknown_label:
|
||
raw_unknown_label = raw_unknown_label.split('{status}')[0].rstrip(' :')
|
||
unknown_txt = f"{raw_unknown_label}: < {status_unknown} >"
|
||
# Hide premium systems
|
||
hide_premium = get_hide_premium_systems()
|
||
status_hide_premium = _('status_on') if hide_premium else _('status_off')
|
||
hide_premium_label = _('menu_hide_premium_systems') if _ else 'Hide Premium systems'
|
||
hide_premium_txt = f"{hide_premium_label}: < {status_hide_premium} >"
|
||
filter_txt = _("submenu_display_filter_platforms") if _ else "Filter Platforms"
|
||
back_txt = _("menu_back") if _ else "Back"
|
||
options = [layout_txt, font_txt, font_family_txt, unsupported_txt, unknown_txt, hide_premium_txt, filter_txt, back_txt]
|
||
_draw_submenu_generic(screen, _("menu_display"), options, selected_index)
|
||
instruction_keys = [
|
||
"instruction_display_layout",
|
||
"instruction_display_font_size",
|
||
"instruction_display_font_family",
|
||
"instruction_display_show_unsupported",
|
||
"instruction_display_unknown_ext",
|
||
"instruction_display_hide_premium",
|
||
"instruction_display_filter_platforms",
|
||
"instruction_generic_back",
|
||
]
|
||
key = instruction_keys[selected_index] if 0 <= selected_index < len(instruction_keys) else None
|
||
if key:
|
||
button_height = int(config.screen_height * 0.045)
|
||
menu_width = int(config.screen_width * 0.72)
|
||
margin_top_bottom = 26
|
||
menu_height = (len(options)+1) * (button_height + 10) + 2 * margin_top_bottom
|
||
menu_y = (config.screen_height - menu_height) // 2
|
||
title_surface = config.font.render("X", True, THEME_COLORS["text"])
|
||
title_rect_height = title_surface.get_height()
|
||
start_y = menu_y + margin_top_bottom//2 + title_rect_height + 10 + 10
|
||
last_button_bottom = start_y + (len(options)-1) * (button_height + 10) + button_height
|
||
draw_menu_instruction(screen, _(key), last_button_bottom)
|
||
|
||
def draw_pause_games_menu(screen, selected_index):
|
||
from rgsx_settings import get_sources_mode
|
||
mode = get_sources_mode()
|
||
source_label = _("games_source_rgsx") if mode == "rgsx" else _("games_source_custom")
|
||
source_txt = f"{_('menu_games_source_prefix')}: < {source_label} >"
|
||
update_txt = _("menu_redownload_cache")
|
||
history_txt = _("menu_history") if _ else "History"
|
||
back_txt = _("menu_back") if _ else "Back"
|
||
options = [history_txt, source_txt, update_txt, back_txt]
|
||
_draw_submenu_generic(screen, _("menu_games") if _ else "Games", options, selected_index)
|
||
instruction_keys = [
|
||
"instruction_games_history",
|
||
"instruction_games_source_mode",
|
||
"instruction_games_update_cache",
|
||
"instruction_generic_back",
|
||
]
|
||
key = instruction_keys[selected_index] if 0 <= selected_index < len(instruction_keys) else None
|
||
if key:
|
||
button_height = int(config.screen_height * 0.045)
|
||
margin_top_bottom = 26
|
||
menu_height = (len(options)+1) * (button_height + 10) + 2 * margin_top_bottom
|
||
menu_y = (config.screen_height - menu_height) // 2
|
||
title_surface = config.font.render("X", True, THEME_COLORS["text"])
|
||
title_rect_height = title_surface.get_height()
|
||
start_y = menu_y + margin_top_bottom//2 + title_rect_height + 10 + 10
|
||
last_button_bottom = start_y + (len(options)-1) * (button_height + 10) + button_height
|
||
draw_menu_instruction(screen, _(key), last_button_bottom)
|
||
|
||
def draw_pause_settings_menu(screen, selected_index):
|
||
from rgsx_settings import get_symlink_option
|
||
# Music
|
||
if config.music_enabled:
|
||
music_name = config.current_music_name or ""
|
||
music_option = _("menu_music_enabled").format(music_name)
|
||
else:
|
||
music_option = _("menu_music_disabled")
|
||
# Uniformiser en < value > pour les réglages basculables
|
||
if ' : ' in music_option:
|
||
base, val = music_option.split(' : ',1)
|
||
music_option = f"{base} : < {val.strip()} >"
|
||
symlink_option = _("symlink_option_enabled") if get_symlink_option() else _("symlink_option_disabled")
|
||
if ' ' in symlink_option:
|
||
parts = symlink_option.split(' ',1)
|
||
# On garde phrase intacte si elle n'a pas de forme label: valeur ; sinon transformer
|
||
if ' : ' in symlink_option:
|
||
base, val = symlink_option.split(' : ',1)
|
||
symlink_option = f"{base} : < {val.strip()} >"
|
||
api_keys_txt = _("menu_api_keys_status") if _ else "API Keys"
|
||
back_txt = _("menu_back") if _ else "Back"
|
||
options = [music_option, symlink_option, api_keys_txt, back_txt]
|
||
_draw_submenu_generic(screen, _("menu_settings_category") if _ else "Settings", options, selected_index)
|
||
instruction_keys = [
|
||
"instruction_settings_music",
|
||
"instruction_settings_symlink",
|
||
"instruction_settings_api_keys",
|
||
"instruction_generic_back",
|
||
]
|
||
key = instruction_keys[selected_index] if 0 <= selected_index < len(instruction_keys) else None
|
||
if key:
|
||
button_height = int(config.screen_height * 0.045)
|
||
margin_top_bottom = 26
|
||
menu_height = (len(options)+1) * (button_height + 10) + 2 * margin_top_bottom
|
||
menu_y = (config.screen_height - menu_height) // 2
|
||
title_surface = config.font.render("X", True, THEME_COLORS["text"])
|
||
title_rect_height = title_surface.get_height()
|
||
start_y = menu_y + margin_top_bottom//2 + title_rect_height + 10 + 10
|
||
last_button_bottom = start_y + (len(options)-1) * (button_height + 10) + button_height
|
||
draw_menu_instruction(screen, _(key), last_button_bottom)
|
||
|
||
def draw_pause_api_keys_status(screen):
|
||
screen.blit(OVERLAY, (0,0))
|
||
from utils import load_api_keys
|
||
keys = load_api_keys()
|
||
title = _("api_keys_status_title") if _ else "API Keys Status"
|
||
# Préparer données avec masquage partiel des clés (afficher 4 premiers et 2 derniers caractères si longueur > 10)
|
||
def mask_key(value: str|None):
|
||
if not value:
|
||
return "" # rien si absent
|
||
v = value.strip()
|
||
if len(v) <= 10:
|
||
return v # courte, afficher entière
|
||
return f"{v[:4]}…{v[-2:]}" # masque au milieu
|
||
|
||
providers = [
|
||
("1fichier", keys.get('1fichier')),
|
||
("AllDebrid", keys.get('alldebrid')),
|
||
("RealDebrid", keys.get('realdebrid'))
|
||
]
|
||
# Dimensions dynamiques en fonction du contenu
|
||
row_height = config.small_font.get_height() + 14
|
||
header_height = 60
|
||
inner_rows = len(providers)
|
||
menu_width = int(config.screen_width * 0.60)
|
||
menu_height = header_height + inner_rows * row_height + 80
|
||
menu_x = (config.screen_width - menu_width)//2
|
||
menu_y = (config.screen_height - menu_height)//2
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (menu_x, menu_y, menu_width, menu_height), border_radius=22)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (menu_x, menu_y, menu_width, menu_height), 2, border_radius=22)
|
||
|
||
# Titre
|
||
title_surface = config.font.render(title, True, THEME_COLORS["text"])
|
||
title_rect = title_surface.get_rect(center=(config.screen_width//2, menu_y + 36))
|
||
screen.blit(title_surface, title_rect)
|
||
|
||
status_present_txt = _("status_present") if _ else "Present"
|
||
status_missing_txt = _("status_missing") if _ else "Missing"
|
||
# Plus de légende textuelle Présent / Missing (demandé) – seules les pastilles couleur serviront.
|
||
legend_rect = pygame.Rect(0,0,0,0)
|
||
|
||
# Colonnes: Provider | Status badge | (key masked)
|
||
col_provider_x = menu_x + 40
|
||
col_status_x = menu_x + int(menu_width * 0.40)
|
||
col_key_x = menu_x + int(menu_width * 0.58)
|
||
|
||
# Démarrage des lignes sous le titre avec un padding
|
||
y = title_rect.bottom + 24
|
||
badge_font = config.tiny_font if hasattr(config, 'tiny_font') else config.small_font
|
||
for provider, value in providers:
|
||
present = bool(value)
|
||
# Provider name
|
||
prov_surf = config.small_font.render(provider, True, THEME_COLORS["text"])
|
||
screen.blit(prov_surf, (col_provider_x, y))
|
||
|
||
# Pastille circulaire simple (couleur = statut)
|
||
circle_color = (60, 170, 60) if present else (180, 55, 55)
|
||
circle_bg = (30, 70, 30) if present else (70, 25, 25)
|
||
radius = 14
|
||
center_x = col_status_x + radius
|
||
center_y = y + badge_font.get_height()//2
|
||
pygame.draw.circle(screen, circle_bg, (center_x, center_y), radius)
|
||
pygame.draw.circle(screen, circle_color, (center_x, center_y), radius, 2)
|
||
|
||
# Masked key (dim color) or hint
|
||
if present:
|
||
masked = mask_key(value)
|
||
key_color = THEME_COLORS.get("text_dim", (180,180,180))
|
||
key_label = masked
|
||
else:
|
||
key_color = THEME_COLORS.get("text_dim", (150,150,150))
|
||
# Afficher nom de fichier + 'empty'
|
||
filename_display = {
|
||
'1fichier': '1FichierAPI.txt',
|
||
'AllDebrid': 'AllDebridAPI.txt',
|
||
'RealDebrid': 'RealDebridAPI.txt'
|
||
}.get(provider, 'key.txt')
|
||
empty_suffix = _("api_key_empty_suffix") if _ and _("api_key_empty_suffix") != "api_key_empty_suffix" else "empty"
|
||
key_label = f"{filename_display} {empty_suffix}"
|
||
key_surf = config.tiny_font.render(key_label, True, key_color) if hasattr(config, 'tiny_font') else config.small_font.render(key_label, True, key_color)
|
||
screen.blit(key_surf, (col_key_x, y))
|
||
|
||
# Ligne séparatrice (optionnelle)
|
||
sep_y = y + row_height - 8
|
||
if provider != providers[-1][0]:
|
||
pygame.draw.line(screen, THEME_COLORS["border"], (menu_x + 25, sep_y), (menu_x + menu_width - 25, sep_y), 1)
|
||
y += row_height
|
||
|
||
# Indication basique: utiliser config.SAVE_FOLDER (chemin dynamique)
|
||
save_folder_path = getattr(config, 'SAVE_FOLDER', '/saves/ports/rgsx')
|
||
# Utiliser placeholder {path} si traduction fournie
|
||
if _ and _("api_keys_hint_manage") != "api_keys_hint_manage":
|
||
try:
|
||
hint_txt = _("api_keys_hint_manage").format(path=save_folder_path)
|
||
except Exception:
|
||
hint_txt = f"Put your keys in {save_folder_path}"
|
||
else:
|
||
hint_txt = f"Put your keys in {save_folder_path}"
|
||
hint_font = config.tiny_font if hasattr(config, 'tiny_font') else config.small_font
|
||
hint_surf = hint_font.render(hint_txt, True, THEME_COLORS.get("text_dim", THEME_COLORS["text"]))
|
||
# Positionné un peu plus haut pour aérer
|
||
hint_rect = hint_surf.get_rect(center=(config.screen_width//2, menu_y + menu_height - 30))
|
||
screen.blit(hint_surf, hint_rect)
|
||
|
||
def draw_filter_platforms_menu(screen):
|
||
"""Affiche le menu de filtrage des plateformes (afficher/masquer)."""
|
||
from rgsx_settings import load_rgsx_settings
|
||
screen.blit(OVERLAY, (0, 0))
|
||
settings = load_rgsx_settings()
|
||
hidden = set(settings.get("hidden_platforms", [])) if isinstance(settings, dict) else set()
|
||
|
||
# Initialiser la copie de travail si vide ou taille différente
|
||
if not config.filter_platforms_selection or len(config.filter_platforms_selection) != len(config.platform_dicts):
|
||
# Liste alphabétique complète (sans filtrer hidden existant)
|
||
all_names = sorted([d.get("platform_name", "") for d in config.platform_dicts if d.get("platform_name")])
|
||
config.filter_platforms_selection = [(name, name in hidden) for name in all_names]
|
||
config.selected_filter_index = 0
|
||
config.filter_platforms_scroll_offset = 0
|
||
config.filter_platforms_dirty = False
|
||
|
||
title_text = _("filter_platforms_title")
|
||
title_surface = config.title_font.render(title_text, True, THEME_COLORS["text"])
|
||
title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 14))
|
||
# Padding responsive réduit
|
||
hpad = max(36, min(64, int(config.screen_width * 0.06)))
|
||
vpad = max(10, min(20, int(title_surface.get_height() * 0.45)))
|
||
title_rect_inflated = title_rect.inflate(hpad, vpad)
|
||
title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 10)
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12)
|
||
screen.blit(title_surface, title_rect)
|
||
|
||
# Zone liste
|
||
list_width = int(config.screen_width * 0.7)
|
||
list_height = int(config.screen_height * 0.6)
|
||
list_x = (config.screen_width - list_width) // 2
|
||
list_y = title_rect_inflated.bottom + 20
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (list_x, list_y, list_width, list_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (list_x, list_y, list_width, list_height), 2, border_radius=12)
|
||
|
||
line_height = config.small_font.get_height() + 8
|
||
visible_items = list_height // line_height - 1 # laisser un peu d'espace bas
|
||
total_items = len(config.filter_platforms_selection)
|
||
if config.selected_filter_index < 0:
|
||
config.selected_filter_index = 0
|
||
# Ne pas forcer la réduction si on est sur les boutons (indices >= total_items)
|
||
# Laisser controls.py gérer la borne max étendue
|
||
# Ajuster scroll
|
||
if config.selected_filter_index < config.filter_platforms_scroll_offset:
|
||
config.filter_platforms_scroll_offset = config.selected_filter_index
|
||
elif config.selected_filter_index >= config.filter_platforms_scroll_offset + visible_items:
|
||
config.filter_platforms_scroll_offset = config.selected_filter_index - visible_items + 1
|
||
|
||
# Dessiner items
|
||
for i in range(config.filter_platforms_scroll_offset, min(config.filter_platforms_scroll_offset + visible_items, total_items)):
|
||
name, is_hidden = config.filter_platforms_selection[i]
|
||
idx_on_screen = i - config.filter_platforms_scroll_offset
|
||
y_center = list_y + 10 + idx_on_screen * line_height + line_height // 2
|
||
selected = (i == config.selected_filter_index)
|
||
checkbox = "[ ]" if is_hidden else "[X]" # inversé: coché signifie visible
|
||
# Correction: on veut [X] si visible => is_hidden False
|
||
checkbox = "[X]" if not is_hidden else "[ ]"
|
||
display_text = f"{checkbox} {name}"
|
||
color = THEME_COLORS["fond_lignes"] if selected else THEME_COLORS["text"]
|
||
text_surface = config.small_font.render(display_text, True, color)
|
||
text_rect = text_surface.get_rect(midleft=(list_x + 20, y_center))
|
||
if selected:
|
||
glow_surface = pygame.Surface((list_width - 40, line_height), pygame.SRCALPHA)
|
||
pygame.draw.rect(glow_surface, THEME_COLORS["fond_lignes"] + (50,), (0, 0, list_width - 40, line_height), border_radius=8)
|
||
screen.blit(glow_surface, (list_x + 20, y_center - line_height // 2))
|
||
screen.blit(text_surface, text_rect)
|
||
|
||
# Scrollbar
|
||
if total_items > visible_items:
|
||
scroll_height = int((visible_items / total_items) * (list_height - 20))
|
||
scroll_y = int((config.filter_platforms_scroll_offset / max(1, total_items - visible_items)) * (list_height - 20 - scroll_height))
|
||
pygame.draw.rect(screen, THEME_COLORS["fond_lignes"], (list_x + list_width - 25, list_y + 10 + scroll_y, 10, scroll_height), border_radius=4)
|
||
|
||
# Boutons d'action
|
||
btn_width = 220
|
||
btn_height = int(config.screen_height * 0.0463)
|
||
spacing = 30
|
||
buttons_y = list_y + list_height + 20
|
||
center_x = config.screen_width // 2
|
||
actions = [
|
||
("filter_all", -2),
|
||
("filter_none", -3),
|
||
("filter_apply", -4),
|
||
("filter_back", -5)
|
||
]
|
||
# Indice spécial sélection boutons quand selected_filter_index >= total_items
|
||
extra_index_base = total_items
|
||
# Ajuster selected_filter_index max pour inclure boutons
|
||
extended_max = total_items + len(actions) - 1
|
||
if config.selected_filter_index > extended_max:
|
||
config.selected_filter_index = extended_max
|
||
|
||
for idx, (key, offset) in enumerate(actions):
|
||
btn_x = center_x - (len(actions) * (btn_width + spacing) - spacing) // 2 + idx * (btn_width + spacing)
|
||
is_selected = (config.selected_filter_index == total_items + idx)
|
||
label = _(key)
|
||
draw_stylized_button(screen, label, btn_x, buttons_y, btn_width, btn_height, selected=is_selected)
|
||
|
||
# Infos bas
|
||
hidden_count = sum(1 for _, h in config.filter_platforms_selection if h)
|
||
visible_count = total_items - hidden_count
|
||
info_text = _("filter_platforms_info").format(visible_count, hidden_count, total_items)
|
||
info_surface = config.small_font.render(info_text, True, THEME_COLORS["text"])
|
||
info_rect = info_surface.get_rect(center=(config.screen_width // 2, buttons_y + btn_height + 30))
|
||
screen.blit(info_surface, info_rect)
|
||
|
||
if config.filter_platforms_dirty:
|
||
dirty_text = _("filter_unsaved_warning")
|
||
dirty_surface = config.small_font.render(dirty_text, True, THEME_COLORS["warning_text"])
|
||
dirty_rect = dirty_surface.get_rect(center=(config.screen_width // 2, info_rect.bottom + 25))
|
||
screen.blit(dirty_surface, dirty_rect)
|
||
|
||
# Menu aide contrôles
|
||
def draw_controls_help(screen, previous_state):
|
||
"""Affiche la liste des contrôles (aide) avec mise en page adaptative."""
|
||
# Contenu des catégories
|
||
control_categories = {
|
||
_("controls_category_navigation"): [
|
||
f"{get_control_display('up', '↑')} {get_control_display('down', '↓')} {get_control_display('left', '←')} {get_control_display('right', '→')} : {_('controls_navigation')}",
|
||
f"{get_control_display('page_up', 'LB')} {get_control_display('page_down', 'RB')} : {_('controls_pages')}",
|
||
],
|
||
_("controls_category_main_actions"): [
|
||
f"{get_control_display('confirm', 'A')} : {_('controls_confirm_select')}",
|
||
f"{get_control_display('cancel', 'B')} : {_('controls_cancel_back')}",
|
||
f"{get_control_display('start', 'Start')} : {_('controls_action_start')}",
|
||
],
|
||
_("controls_category_downloads"): [
|
||
f"{get_control_display('history', 'Y')} : {_('controls_action_history')}",
|
||
f"{get_control_display('clear_history', 'X')} : {_('controls_action_clear_history')}",
|
||
],
|
||
_("controls_category_search"): [
|
||
f"{get_control_display('filter', 'Select')} : {_('controls_filter_search')}",
|
||
f"{get_control_display('delete', 'Suppr')} : {_('controls_action_delete')}",
|
||
f"{get_control_display('space', 'Espace')} : {_('controls_action_space')}",
|
||
],
|
||
}
|
||
|
||
# États autorisés (même logique qu'avant)
|
||
allowed_states = {
|
||
# États classiques où l'aide était accessible
|
||
"error", "platform", "game", "confirm_exit",
|
||
"extension_warning", "history", "clear_history",
|
||
# Nouveaux états hiérarchiques pause
|
||
"pause_controls_menu", "pause_menu"
|
||
}
|
||
if previous_state not in allowed_states:
|
||
return
|
||
|
||
screen.blit(OVERLAY, (0, 0))
|
||
|
||
# Paramètres d'affichage
|
||
font = config.small_font
|
||
title_font = config.title_font
|
||
section_font = config.font
|
||
line_spacing = max(4, font.get_height() // 6)
|
||
section_spacing = font.get_height() // 2
|
||
title_spacing = font.get_height()
|
||
padding = 24
|
||
inter_col_spacing = 48
|
||
max_panel_width = int(config.screen_width * 0.9)
|
||
max_panel_height = int(config.screen_height * 0.9)
|
||
|
||
# Découpage en 2 colonnes (équilibré)
|
||
categories_list = list(control_categories.items())
|
||
mid = len(categories_list) // 2
|
||
col1_categories = categories_list[:mid]
|
||
col2_categories = categories_list[mid:]
|
||
|
||
# Largeur cible par colonne (avant wrapping)
|
||
target_col_width = (max_panel_width - 2 * padding - inter_col_spacing) // 2
|
||
|
||
def wrap_lines_for_column(cat_pairs):
|
||
wrapped = [] # liste de (is_section_title, surface)
|
||
max_width = 0
|
||
total_height = 0
|
||
for section_title, lines in cat_pairs:
|
||
# Titre section
|
||
sec_surf = section_font.render(section_title, True, THEME_COLORS["fond_lignes"])
|
||
wrapped.append((True, sec_surf))
|
||
total_height += sec_surf.get_height() + line_spacing
|
||
|
||
for raw_line in lines:
|
||
# Wrap par mots
|
||
words = raw_line.split()
|
||
cur = ""
|
||
for word in words:
|
||
test = (cur + " " + word).strip()
|
||
if font.size(test)[0] <= target_col_width:
|
||
cur = test
|
||
else:
|
||
if cur:
|
||
line_surf = font.render(cur, True, THEME_COLORS["text"])
|
||
|
||
|
||
|
||
wrapped.append((False, line_surf))
|
||
total_height += line_surf.get_height() + line_spacing
|
||
max_width = max(max_width, line_surf.get_width())
|
||
cur = word
|
||
if cur:
|
||
line_surf = font.render(cur, True, THEME_COLORS["text"])
|
||
wrapped.append((False, line_surf))
|
||
total_height += line_surf.get_height() + line_spacing
|
||
max_width = max(max_width, line_surf.get_width())
|
||
|
||
total_height += section_spacing # espace après section
|
||
max_width = max(max_width, sec_surf.get_width())
|
||
|
||
if wrapped and not wrapped[-1][0]:
|
||
total_height -= section_spacing # retirer excédent final
|
||
return wrapped, max_width, total_height
|
||
|
||
col1_wrapped, col1_w, col1_h = wrap_lines_for_column(col1_categories)
|
||
col2_wrapped, col2_w, col2_h = wrap_lines_for_column(col2_categories)
|
||
|
||
col_widths_sum = col1_w + col2_w + inter_col_spacing
|
||
content_width = min(max_panel_width - 2 * padding, max(col_widths_sum, col1_w + col2_w + inter_col_spacing))
|
||
panel_width = content_width + 2 * padding
|
||
|
||
title_surf = title_font.render(_("controls_help_title"), True, THEME_COLORS["text"])
|
||
title_height = title_surf.get_height()
|
||
|
||
content_height = max(col1_h, col2_h)
|
||
panel_height = title_height + title_spacing + content_height + 2 * padding
|
||
if panel_height > max_panel_height:
|
||
panel_height = max_panel_height
|
||
enable_clip = True
|
||
else:
|
||
enable_clip = False
|
||
|
||
panel_x = (config.screen_width - panel_width) // 2
|
||
panel_y = (config.screen_height - panel_height) // 2
|
||
|
||
# Fond panel
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (panel_x, panel_y, panel_width, panel_height), border_radius=16)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (panel_x, panel_y, panel_width, panel_height), 2, border_radius=16)
|
||
|
||
# Titre
|
||
title_rect = title_surf.get_rect(center=(panel_x + panel_width // 2, panel_y + padding + title_height // 2))
|
||
screen.blit(title_surf, title_rect)
|
||
|
||
# Zones de colonnes
|
||
col_top = panel_y + padding + title_height + title_spacing
|
||
col1_x = panel_x + padding
|
||
col2_x = panel_x + panel_width - padding - col2_w
|
||
|
||
# Clip si nécessaire
|
||
prev_clip = None
|
||
if enable_clip:
|
||
prev_clip = screen.get_clip()
|
||
clip_rect = pygame.Rect(panel_x + padding, col_top, panel_width - 2 * padding, panel_height - (col_top - panel_y) - padding)
|
||
screen.set_clip(clip_rect)
|
||
|
||
# Dessin colonne 1
|
||
y1 = col_top
|
||
last_section = False
|
||
for is_section, surf in col1_wrapped:
|
||
if is_section:
|
||
y1 += 0
|
||
if y1 + surf.get_height() > panel_y + panel_height - padding:
|
||
break
|
||
screen.blit(surf, (col1_x, y1))
|
||
y1 += surf.get_height() + (section_spacing if is_section else line_spacing)
|
||
|
||
# Dessin colonne 2
|
||
y2 = col_top
|
||
for is_section, surf in col2_wrapped:
|
||
if y2 + surf.get_height() > panel_y + panel_height - padding:
|
||
break
|
||
screen.blit(surf, (col2_x, y2))
|
||
y2 += surf.get_height() + (section_spacing if is_section else line_spacing)
|
||
|
||
if enable_clip and prev_clip is not None:
|
||
screen.set_clip(prev_clip)
|
||
|
||
|
||
# Menu Quitter Appli
|
||
def draw_confirm_dialog(screen):
|
||
"""Affiche la boîte de dialogue de confirmation pour quitter."""
|
||
global OVERLAY
|
||
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, 150))
|
||
logger.debug("OVERLAY recréé dans draw_confirm_dialog")
|
||
|
||
screen.blit(OVERLAY, (0, 0))
|
||
# Dynamic message: warn when downloads are active
|
||
active_downloads = 0
|
||
try:
|
||
active_downloads = len(getattr(config, 'download_tasks', {}) or {})
|
||
except Exception:
|
||
active_downloads = 0
|
||
if active_downloads > 0:
|
||
# Try translated key if it exists; otherwise fallback to generic message
|
||
try:
|
||
warn_tpl = _("confirm_exit_with_downloads") # optional key
|
||
# If untranslated key returns the same string, still format
|
||
message = warn_tpl.format(active_downloads)
|
||
except Exception:
|
||
message = f"Attention: {active_downloads} téléchargement(s) en cours. Quitter quand même ?"
|
||
else:
|
||
message = _("confirm_exit")
|
||
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 = int(config.screen_height * 0.0463)
|
||
margin_top_bottom = 20
|
||
rect_height = text_height + button_height + 2 * margin_top_bottom
|
||
max_text_width = max([config.font.size(line)[0] for line in wrapped_message], default=300)
|
||
rect_width = max_text_width + 150
|
||
rect_x = (config.screen_width - rect_width) // 2
|
||
rect_y = (config.screen_height - rect_height) // 2
|
||
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||
|
||
for i, line in enumerate(wrapped_message):
|
||
text = config.font.render(line, True, THEME_COLORS["text"])
|
||
text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2))
|
||
screen.blit(text, text_rect)
|
||
|
||
button_width = min(160, (rect_width - 60) // 2)
|
||
draw_stylized_button(screen, _("button_yes"), rect_x + rect_width // 2 - button_width - 10, rect_y + text_height + margin_top_bottom, button_width, button_height, selected=config.confirm_selection == 1)
|
||
draw_stylized_button(screen, _("button_no"), rect_x + rect_width // 2 + 10, rect_y + text_height + margin_top_bottom, button_width, button_height, selected=config.confirm_selection == 0)
|
||
|
||
|
||
def draw_reload_games_data_dialog(screen):
|
||
"""Affiche la boîte de dialogue de confirmation pour retélécharger le cache des jeux."""
|
||
global OVERLAY
|
||
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, 150))
|
||
|
||
screen.blit(OVERLAY, (0, 0))
|
||
message = _("confirm_redownload_cache")
|
||
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 = int(config.screen_height * 0.0463)
|
||
margin_top_bottom = 20
|
||
rect_height = text_height + button_height + 2 * margin_top_bottom
|
||
max_text_width = max([config.small_font.size(line)[0] for line in wrapped_message], default=300)
|
||
rect_width = max_text_width + 80
|
||
rect_x = (config.screen_width - rect_width) // 2
|
||
rect_y = (config.screen_height - rect_height) // 2
|
||
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||
|
||
for i, line in enumerate(wrapped_message):
|
||
text = config.small_font.render(line, True, THEME_COLORS["text"])
|
||
text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2))
|
||
screen.blit(text, text_rect)
|
||
|
||
# Calcule une largeur de bouton cohérente avec la boîte et centre les deux boutons
|
||
button_width = min(160, (rect_width - 60) // 2)
|
||
yes_x = rect_x + rect_width // 2 - button_width - 10
|
||
no_x = rect_x + rect_width // 2 + 10
|
||
buttons_y = rect_y + text_height + margin_top_bottom
|
||
draw_stylized_button(screen, _("button_yes"), yes_x, buttons_y, button_width, button_height, selected=config.redownload_confirm_selection == 1)
|
||
draw_stylized_button(screen, _("button_no"), no_x, buttons_y, button_width, button_height, selected=config.redownload_confirm_selection == 0)
|
||
|
||
# Popup avec compte à rebours
|
||
def draw_popup(screen):
|
||
"""Dessine un popup avec un message (adapté en largeur) et un compte à rebours."""
|
||
screen.blit(OVERLAY, (0, 0))
|
||
|
||
# Largeur de base (peut s'élargir un peu si très petit écran)
|
||
popup_width = int(config.screen_width * 0.8)
|
||
max_inner_width = popup_width - 60 # padding horizontal interne pour le texte
|
||
line_height = config.small_font.get_height() + 8
|
||
margin_top_bottom = 24
|
||
|
||
raw_segments = config.popup_message.split('\n') if config.popup_message else []
|
||
wrapped_lines = []
|
||
for seg in raw_segments:
|
||
if seg.strip() == "":
|
||
wrapped_lines.append("")
|
||
else:
|
||
wrapped_lines.extend(wrap_text(seg, config.small_font, max_inner_width))
|
||
if not wrapped_lines:
|
||
wrapped_lines = [""]
|
||
|
||
text_height = len(wrapped_lines) * line_height
|
||
# Ajouter une ligne pour le compte à rebours
|
||
popup_height = text_height + 2 * margin_top_bottom + line_height
|
||
popup_x = (config.screen_width - popup_width) // 2
|
||
popup_y = (config.screen_height - popup_height) // 2
|
||
|
||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (popup_x, popup_y, popup_width, popup_height), border_radius=12)
|
||
pygame.draw.rect(screen, THEME_COLORS["border"], (popup_x, popup_y, popup_width, popup_height), 2, border_radius=12)
|
||
|
||
for i, line in enumerate(wrapped_lines):
|
||
# Alignment centre horizontal global
|
||
text_surface = config.small_font.render(line, True, THEME_COLORS["text"])
|
||
text_rect = text_surface.get_rect(center=(config.screen_width // 2, popup_y + margin_top_bottom + i * line_height + line_height // 2))
|
||
screen.blit(text_surface, text_rect)
|
||
|
||
remaining_time = max(0, config.popup_timer // 1000)
|
||
countdown_text = _("popup_countdown").format(remaining_time, 's' if remaining_time != 1 else '')
|
||
countdown_surface = config.small_font.render(countdown_text, True, THEME_COLORS["text"])
|
||
countdown_rect = countdown_surface.get_rect(center=(config.screen_width // 2, popup_y + margin_top_bottom + len(wrapped_lines) * line_height + line_height // 2))
|
||
screen.blit(countdown_surface, countdown_rect)
|
||
|