2025-07-06 19:47:21 +02:00
|
|
|
|
import pygame
|
|
|
|
|
|
import re
|
|
|
|
|
|
import json
|
|
|
|
|
|
import os
|
|
|
|
|
|
import config
|
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
def create_placeholder(width=400):
|
|
|
|
|
|
"""Crée une image de substitution pour les jeux sans vignette."""
|
|
|
|
|
|
logger.debug(f"Création placeholder: largeur={width}")
|
|
|
|
|
|
if config.font is None:
|
|
|
|
|
|
# Police de secours si config.font n’est pas initialisé
|
|
|
|
|
|
fallback_font = pygame.font.SysFont("arial", 24)
|
|
|
|
|
|
text = fallback_font.render("No Image", True, (255, 255, 255))
|
|
|
|
|
|
else:
|
|
|
|
|
|
text = config.font.render("No Image", True, (255, 255, 255))
|
|
|
|
|
|
|
|
|
|
|
|
height = int(150 * (width / 200))
|
|
|
|
|
|
placeholder = pygame.Surface((width, height))
|
|
|
|
|
|
placeholder.fill((50, 50, 50))
|
|
|
|
|
|
text_rect = text.get_rect(center=(width // 2, height // 2))
|
|
|
|
|
|
placeholder.blit(text, text_rect)
|
|
|
|
|
|
return placeholder
|
|
|
|
|
|
|
|
|
|
|
|
def truncate_text_middle(text, font, max_width):
|
|
|
|
|
|
"""Tronque le texte en insérant '...' au milieu."""
|
|
|
|
|
|
text_width = font.size(text)[0]
|
|
|
|
|
|
if text_width <= max_width:
|
|
|
|
|
|
return text
|
|
|
|
|
|
ellipsis = "..."
|
|
|
|
|
|
ellipsis_width = font.size(ellipsis)[0]
|
|
|
|
|
|
max_text_width = max_width - ellipsis_width
|
|
|
|
|
|
while text_width > max_text_width and len(text) > 0:
|
|
|
|
|
|
text = text[:-1]
|
|
|
|
|
|
text_width = font.size(text)[0]
|
|
|
|
|
|
mid = len(text) // 2
|
|
|
|
|
|
return text[:mid] + ellipsis + text[mid:]
|
|
|
|
|
|
|
|
|
|
|
|
def truncate_text_end(text, font, max_width):
|
|
|
|
|
|
"""Tronque le texte à la fin pour qu'il tienne dans max_width avec la police donnée."""
|
|
|
|
|
|
if not isinstance(text, str):
|
|
|
|
|
|
logger.error(f"Texte non valide: {text}")
|
|
|
|
|
|
return ""
|
|
|
|
|
|
if not isinstance(font, pygame.font.Font):
|
|
|
|
|
|
logger.error("Police non valide dans truncate_text_end")
|
|
|
|
|
|
return text # Retourne le texte brut si la police est invalide
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
if font.size(text)[0] <= max_width:
|
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
truncated = text
|
|
|
|
|
|
while len(truncated) > 0 and font.size(truncated + "...")[0] > max_width:
|
|
|
|
|
|
truncated = truncated[:-1]
|
|
|
|
|
|
return truncated + "..." if len(truncated) < len(text) else text
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"Erreur lors du rendu du texte '{text}': {str(e)}")
|
|
|
|
|
|
return text # Retourne le texte brut en cas d'erreur
|
|
|
|
|
|
|
|
|
|
|
|
def sanitize_filename(name):
|
|
|
|
|
|
"""Sanitise les noms de fichiers en remplaçant les caractères interdits."""
|
|
|
|
|
|
return re.sub(r'[<>:"/\/\\|?*]', '_', name).strip()
|
|
|
|
|
|
|
|
|
|
|
|
def wrap_text(text, font, max_width):
|
|
|
|
|
|
"""Divise le texte en lignes pour respecter la largeur maximale."""
|
|
|
|
|
|
words = text.split(' ')
|
|
|
|
|
|
lines = []
|
|
|
|
|
|
current_line = ''
|
|
|
|
|
|
|
|
|
|
|
|
for word in words:
|
|
|
|
|
|
# Tester si ajouter le mot dépasse la largeur
|
|
|
|
|
|
test_line = current_line + (' ' if current_line else '') + word
|
|
|
|
|
|
test_surface = font.render(test_line, True, (255, 255, 255))
|
|
|
|
|
|
if test_surface.get_width() <= max_width:
|
|
|
|
|
|
current_line = test_line
|
|
|
|
|
|
else:
|
|
|
|
|
|
if current_line:
|
|
|
|
|
|
lines.append(current_line)
|
|
|
|
|
|
current_line = word
|
|
|
|
|
|
|
|
|
|
|
|
if current_line:
|
|
|
|
|
|
lines.append(current_line)
|
|
|
|
|
|
|
|
|
|
|
|
return lines
|
|
|
|
|
|
|
|
|
|
|
|
def load_system_image(platform_dict):
|
|
|
|
|
|
"""Charge une image système depuis le chemin spécifié dans system_image."""
|
|
|
|
|
|
image_path = platform_dict.get("system_image")
|
|
|
|
|
|
platform_name = platform_dict.get("platform", "unknown")
|
|
|
|
|
|
#logger.debug(f"Chargement de l'image système pour {platform_name} depuis {image_path}")
|
|
|
|
|
|
try:
|
|
|
|
|
|
if not os.path.exists(image_path):
|
|
|
|
|
|
logger.error(f"Image introuvable pour {platform_name} à {image_path}")
|
|
|
|
|
|
return None
|
|
|
|
|
|
return pygame.image.load(image_path).convert_alpha()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"Erreur lors du chargement de l'image pour {platform_name} : {str(e)}")
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def load_games(platform_id):
|
|
|
|
|
|
"""Charge les jeux pour une plateforme donnée en utilisant platform_id."""
|
|
|
|
|
|
games_path = f"/userdata/roms/ports/RGSX/games/{platform_id}.json"
|
|
|
|
|
|
#logger.debug(f"Chargement des jeux pour {platform_id} depuis {games_path}")
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(games_path, 'r', encoding='utf-8') as f:
|
|
|
|
|
|
games = json.load(f)
|
|
|
|
|
|
return games
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"Erreur lors du chargement des jeux pour {platform_id} : {str(e)}")
|
2025-07-06 21:02:43 +02:00
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
def load_json_file(path, default=None):
|
|
|
|
|
|
"""Charge un fichier JSON avec gestion d'erreur."""
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(path, "r") as f:
|
|
|
|
|
|
return json.load(f)
|
|
|
|
|
|
except (FileNotFoundError, json.JSONDecodeError) as e:
|
|
|
|
|
|
logging.error(f"Erreur lors de la lecture de {path} : {e}")
|
|
|
|
|
|
return default if default is not None else {}
|