- Move accessibility settings loading and saving on accessibility.py

- Updated controls to replace old "progress" status button with "clear_history"

- Refactored control mappings and descriptions to align with the new naming conventions.

- Updated language files

- Improved the controls help menu layout for better readability and organization.
This commit is contained in:
skymike03
2025-08-12 00:44:16 +02:00
parent ce3a3d3c05
commit 0ade44babd
12 changed files with 323 additions and 294 deletions

View File

@@ -1,13 +1,14 @@
import os
os.environ["SDL_FBDEV"] = "/dev/fb0"
import pygame # type: ignore
# type: ignore[reportAttributeAccessIssue]
import asyncio
import platform
import logging
import requests
import queue
import datetime
import config
from display import (
init_display, draw_loading_screen, draw_error_screen, draw_platform_grid,
draw_progress_screen, draw_controls, draw_virtual_keyboard, draw_popup_result_download,
@@ -19,14 +20,14 @@ from display import (
from language import handle_language_menu_events, _
from network import test_internet, download_rom, is_1fichier_url, download_from_1fichier, check_for_updates
from controls import handle_controls, validate_menu_state, process_key_repeats, get_emergency_controls
from controls_mapper import load_controls_config, save_controls_config, map_controls, draw_controls_mapping, get_actions
from controls_mapper import load_controls_config, map_controls, draw_controls_mapping, get_actions
from utils import (
detect_non_pc, load_sources, check_extension_before_download, extract_zip_data,
play_random_music, load_accessibility_settings, load_music_config
play_random_music, load_music_config
)
from history import load_history, save_history
import config
from config import OTA_data_ZIP
from accessibility import load_accessibility_settings
# Configuration du logging
try:
@@ -267,7 +268,15 @@ async def main():
if config.menu_state == "confirm_clear_history":
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
if action == "confirm":
config.history.clear()
save_history(config.history)
config.menu_state = "history"
config.needs_redraw = True
logger.debug("Historique effacé")
elif action == "cancel":
config.menu_state = "history"
config.needs_redraw = True
continue
if config.menu_state == "confirm_cancel_download":
@@ -314,7 +323,7 @@ async def main():
logger.debug("Téléchargement annulé, retour à l'état précédent")
continue
if config.menu_state in ["platform", "game", "error", "confirm_exit", "download_progress", "download_result", "history"]:
if config.menu_state in ["platform", "game", "error", "confirm_exit", "download_result", "history"]:
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
if action == "quit":
@@ -456,7 +465,13 @@ async def main():
config.needs_redraw = True
logger.debug(f"Retéléchargement terminé pour {game_name}, succès={success}, message={message}")
break
elif action in ("clear_history", "delete_history") and config.menu_state == "history":
# Ouvrir le dialogue de confirmation
config.previous_menu_state = config.menu_state
config.menu_state = "confirm_clear_history"
config.confirm_selection = 0
config.needs_redraw = True
continue

View File

@@ -1,8 +1,34 @@
import pygame #type:ignore
import config
from utils import save_accessibility_settings
import os
import json
from logging import getLogger
from language import _
logger = getLogger(__name__)
def load_accessibility_settings():
"""Charge les paramètres d'accessibilité depuis accessibility.json."""
accessibility_path = os.path.join(config.SAVE_FOLDER, "accessibility.json")
try:
if os.path.exists(accessibility_path):
with open(accessibility_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
logger.error(f"Erreur lors du chargement de accessibility.json: {str(e)}")
return {"font_scale": 1.0}
def save_accessibility_settings(settings):
"""Sauvegarde les paramètres d'accessibilité dans accessibility.json."""
accessibility_path = os.path.join(config.SAVE_FOLDER, "accessibility.json")
try:
os.makedirs(config.SAVE_FOLDER, exist_ok=True)
with open(accessibility_path, 'w', encoding='utf-8') as f:
json.dump(settings, f, indent=2)
logger.debug(f"Paramètres d'accessibilité sauvegardés: {settings}")
except Exception as e:
logger.error(f"Erreur lors de la sauvegarde de accessibility.json: {str(e)}")
def draw_accessibility_menu(screen):
"""Affiche le menu d'accessibilité avec curseur pour la taille de police."""
from display import OVERLAY, THEME_COLORS, draw_stylized_button

View File

@@ -4,7 +4,7 @@ import sys
import logging
# Version actuelle de l'application
app_version = "1.9.8.6"
app_version = "1.9.8.7"
def get_application_root():
"""Détermine le dossier de l'application de manière portable."""

View File

@@ -26,7 +26,7 @@ key_states = {} # Dictionnaire pour suivre l'état des touches
# Liste des états valides
VALID_STATES = [
"platform", "game", "download_progress", "download_result", "confirm_exit",
"platform", "game", "download_result", "confirm_exit",
"extension_warning", "pause_menu", "controls_help", "history", "controls_mapping",
"redownload_game_cache", "restart_popup", "error", "loading", "confirm_clear_history",
"language_select"
@@ -52,7 +52,7 @@ def load_controls_config(path=CONTROLS_CONFIG_PATH):
"up": {"type": "key", "key": pygame.K_UP},
"down": {"type": "key", "key": pygame.K_DOWN},
"start": {"type": "key", "key": pygame.K_p},
"progress": {"type": "key", "key": pygame.K_x},
"clear_history": {"type": "key", "key": pygame.K_x},
"history": {"type": "key", "key": pygame.K_h},
"page_up": {"type": "key", "key": pygame.K_PAGEUP},
"page_down": {"type": "key", "key": pygame.K_PAGEDOWN},
@@ -62,17 +62,27 @@ def load_controls_config(path=CONTROLS_CONFIG_PATH):
}
try:
with open(path, "r") as f:
config_data = json.load(f)
# Vérifier et compléter les actions manquantes
for action, default_mapping in default_config.items():
if action not in config_data:
logger.warning(f"Action {action} manquante dans {path}, utilisation de la valeur par défaut")
config_data[action] = default_mapping
return config_data
except (FileNotFoundError, json.JSONDecodeError) as e:
logger.error(f"Erreur lors de la lecture de {path} : {e}, utilisation de la configuration par défaut")
return default_config
if os.path.exists(path):
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
if not isinstance(data, dict):
data = {}
else:
data = {}
changed = False
for k, v in default_config.items():
if k not in data:
data[k] = v
changed = True
if changed:
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
logging.getLogger(__name__).debug(f"controls.json complété avec les actions manquantes: {path}")
return data
except Exception as e:
logging.getLogger(__name__).error(f"Erreur load_controls_config: {e}")
return default_config.copy()
# Fonction pour vérifier si un événement correspond à une action
def is_input_matched(event, action_name):
@@ -254,11 +264,6 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_start_time = 0
config.repeat_last_action = current_time
config.needs_redraw = True
elif is_input_matched(event, "progress"):
if config.download_tasks:
config.menu_state = "download_progress"
config.needs_redraw = True
logger.debug("Retour à download_progress depuis platform")
elif is_input_matched(event, "history"):
config.menu_state = "history"
config.needs_redraw = True
@@ -356,20 +361,20 @@ def handle_controls(event, sources, joystick, screen):
config.scroll_offset = 0
config.needs_redraw = True
logger.debug("Sortie du mode recherche")
elif is_input_matched(event, "filter") or is_input_matched(event, "confirm"):
elif is_input_matched(event, "filter"):
config.search_mode = False
config.filter_active = bool(config.search_query)
config.needs_redraw = True
logger.debug(f"Validation du filtre avec manette: query={config.search_query}, filter_active={config.filter_active}")
elif config.search_mode and not config.is_non_pc:
# Gestion de la recherche sur PC (clavier et manette)
if is_input_matched(event, "filter"):
if is_input_matched(event, "confirm"):
config.search_mode = False
config.filter_active = True
config.current_game = 0
config.scroll_offset = 0
config.needs_redraw = True
logger.debug(f"Validation du filtre avec bouton filter sur PC: query={config.search_query}")
logger.debug(f"Validation du filtre avec bouton entree sur PC: query={config.search_query}")
elif is_input_matched(event, "cancel"):
config.search_mode = False
config.search_query = ""
@@ -397,31 +402,7 @@ def handle_controls(event, sources, joystick, screen):
config.scroll_offset = 0
config.needs_redraw = True
logger.debug(f"Suppression caractère: query={config.search_query}, jeux filtrés={len(config.filtered_games)}")
# Gestion de la validation
elif is_input_matched(event, "confirm"):
config.search_mode = False
config.filter_active = True # Conserver le filtre actif
config.current_game = 0
config.scroll_offset = 0
config.needs_redraw = True
logger.debug(f"Validation de la recherche: query={config.search_query}, jeux filtrés={len(config.filtered_games)}")
# Gestion de l'annulation
elif is_input_matched(event, "cancel"):
config.search_mode = False
config.search_query = ""
config.filtered_games = config.games
config.current_game = 0
config.scroll_offset = 0
config.needs_redraw = True
logger.debug("Sortie du mode recherche")
# Gestion de la validation avec le bouton filter
elif is_input_matched(event, "filter"):
config.search_mode = False
config.filter_active = True
config.current_game = 0
config.scroll_offset = 0
config.needs_redraw = True
logger.debug(f"Validation du filtre avec bouton filter: query={config.search_query}, jeux filtrés={len(config.filtered_games)}")
else:
if is_input_matched(event, "up"):
@@ -462,13 +443,7 @@ def handle_controls(event, sources, joystick, screen):
config.scroll_offset = 0
config.selected_key = (0, 0)
config.needs_redraw = True
logger.debug("Entrée en mode recherche")
elif is_input_matched(event, "progress"):
if config.download_tasks:
config.previous_menu_state = config.menu_state
config.menu_state = "download_progress"
config.needs_redraw = True
logger.debug(f"Retour à download_progress depuis {config.previous_menu_state}")
logger.debug("Entrée en mode recherche")
elif is_input_matched(event, "history"):
config.menu_state = "history"
config.needs_redraw = True
@@ -683,7 +658,9 @@ def handle_controls(event, sources, joystick, screen):
config.repeat_last_action = current_time
config.needs_redraw = True
#logger.debug("Page suivante dans l'historique")
elif is_input_matched(event, "progress"):
elif (is_input_matched(event, "clear_history")
or is_input_matched(event, "delete_history")
or is_input_matched(event, "progress")):
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
config.menu_state = "confirm_clear_history"
config.confirm_clear_selection = 0 # 0 pour "Non", 1 pour "Oui"
@@ -782,12 +759,12 @@ def handle_controls(event, sources, joystick, screen):
elif is_input_matched(event, "cancel"):
config.menu_state = "history"
config.needs_redraw = True
# Confirmation vider l'historique"
# Confirmation vider l'historique
elif config.menu_state == "confirm_clear_history":
logger.debug(f"État confirm_clear_history, confirm_clear_selection={config.confirm_clear_selection}, événement={event.type}, valeur={getattr(event, 'value', None)}")
if is_input_matched(event, "confirm"):
logger.debug(f"Action confirm détectée dans confirm_clear_history")
# 0 = Non, 1 = Oui
if config.confirm_clear_selection == 1: # Oui
clear_history()
config.history = []
@@ -799,38 +776,13 @@ def handle_controls(event, sources, joystick, screen):
else: # Non
config.menu_state = "history"
config.needs_redraw = True
logger.debug("Annulation du vidage de l'historique, retour à history")
elif is_input_matched(event, "left"):
#logger.debug(f"Action left détectée dans confirm_clear_history")
config.confirm_clear_selection = 1 # Sélectionner "Non"
elif is_input_matched(event, "left") or is_input_matched(event, "right"):
config.confirm_clear_selection = 1 - config.confirm_clear_selection
config.needs_redraw = True
#logger.debug(f"Changement sélection confirm_clear_history: {config.confirm_clear_selection}")
elif is_input_matched(event, "right"):
#logger.debug(f"Action right détectée dans confirm_clear_history")
config.confirm_clear_selection = 0 # Sélectionner "Oui"
config.needs_redraw = True
#logger.debug(f"Changement sélection confirm_clear_history: {config.confirm_clear_selection}")
elif is_input_matched(event, "cancel"):
#logger.debug(f"Action cancel détectée dans confirm_clear_history")
config.menu_state = "history"
config.needs_redraw = True
logger.debug("Annulation du vidage de l'historique, retour à history")
# Progression téléchargement
elif config.menu_state == "download_progress":
if is_input_matched(event, "cancel"):
for task in config.download_tasks:
task.cancel()
config.download_tasks.clear()
config.download_progress.clear()
config.pending_download = None
config.menu_state = validate_menu_state(config.previous_menu_state)
config.needs_redraw = True
logger.debug(f"Téléchargement annulé, retour à {config.menu_state}")
elif is_input_matched(event, "progress"):
config.menu_state = validate_menu_state(config.previous_menu_state)
config.needs_redraw = True
logger.debug(f"Retour à {config.menu_state} depuis download_progress")
# Résultat téléchargement
elif config.menu_state == "download_result":
@@ -958,7 +910,6 @@ def handle_controls(event, sources, joystick, screen):
if config.redownload_confirm_selection == 1: # Oui
logger.debug("Début du redownload des jeux")
config.download_tasks.clear()
config.download_progress.clear()
config.pending_download = None
if os.path.exists(config.APP_FOLDER + "/sources.json"):
try:
@@ -1151,7 +1102,11 @@ def get_emergency_controls():
"left": {"type": "key", "key": pygame.K_LEFT},
"right": {"type": "key", "key": pygame.K_RIGHT},
"start": {"type": "key", "key": pygame.K_p},
# Ajouter aussi les contrôles manette de base si disponible
"confirm_joy": {"type": "button", "button": 0}, # A/Croix
"cancel_joy": {"type": "button", "button": 1}, # B/Rond
"history": {"type": "key", "key": pygame.K_h},
"clear_history": {"type": "key", "key": pygame.K_x},
"page_up": {"type": "key", "key": pygame.K_PAGEUP},
"page_down": {"type": "key", "key": pygame.K_PAGEDOWN},
# manette basique
"confirm_joy": {"type": "button", "button": 0},
"cancel_joy": {"type": "button", "button": 1},
}

View File

@@ -28,7 +28,7 @@ ACTION_DEFS = [
{"name": "page_up"},
{"name": "page_down"},
{"name": "history"},
{"name": "delete_history"},
{"name": "clear_history"},
{"name": "delete"},
{"name": "space"},
]
@@ -60,14 +60,14 @@ SDL_TO_PYGAME_KEY = {
# Noms lisibles pour les touches clavier
KEY_NAMES = {
pygame.K_RETURN: "Ente",
pygame.K_RETURN: "Enter",
pygame.K_ESCAPE: "Échap",
pygame.K_SPACE: "Espace",
pygame.K_UP: "Flèche Haut",
pygame.K_DOWN: "Flèche Bas",
pygame.K_LEFT: "Flèche Gauche",
pygame.K_RIGHT: "Flèche Droite",
pygame.K_BACKSPACE: "Retour Arrière",
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",
@@ -116,23 +116,23 @@ KEY_NAMES = {
pygame.K_7: "7",
pygame.K_8: "8",
pygame.K_9: "9",
pygame.K_KP0: "Pavé 0",
pygame.K_KP1: "Pavé 1",
pygame.K_KP2: "Pavé 2",
pygame.K_KP3: "Pavé 3",
pygame.K_KP4: "Pavé 4",
pygame.K_KP5: "Pavé 5",
pygame.K_KP6: "Pavé 6",
pygame.K_KP7: "Pavé 7",
pygame.K_KP8: "Pavé 8",
pygame.K_KP9: "Pavé 9",
pygame.K_KP_PERIOD: "Pavé .",
pygame.K_KP_DIVIDE: "Pavé /",
pygame.K_KP_MULTIPLY: "Pavé *",
pygame.K_KP_MINUS: "Pavé -",
pygame.K_KP_PLUS: "Pavé +",
pygame.K_KP_ENTER: "Pavé Ente",
pygame.K_KP_EQUALS: "Pavé =",
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",
@@ -152,9 +152,9 @@ KEY_NAMES = {
pygame.K_DELETE: "Suppr",
pygame.K_HOME: "Début",
pygame.K_END: "Fin",
pygame.K_PAGEUP: "Page Haut",
pygame.K_PAGEDOWN: "Page Bas",
pygame.K_PRINT: "Impr Écran",
pygame.K_PAGEUP: "Page+",
pygame.K_PAGEDOWN: "Page-",
pygame.K_PRINT: "PrintScreen",
pygame.K_SYSREQ: "SysReq",
pygame.K_BREAK: "Pause",
pygame.K_PAUSE: "Pause",
@@ -286,17 +286,36 @@ HOLD_DURATION = 1000
JOYHAT_DEBOUNCE = 200 # Délai anti-rebond pour JOYHATMOTION (ms)
def load_controls_config():
def load_controls_config(path=CONTROLS_CONFIG_PATH):
"""Charge la configuration des contrôles depuis controls.json"""
try:
if os.path.exists(CONTROLS_CONFIG_PATH):
with open(CONTROLS_CONFIG_PATH, "r") as f:
config_data = json.load(f)
logger.debug(f"Configuration des contrôles chargée : {config_data}")
return config_data
if os.path.exists(path):
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
if not isinstance(data, dict):
data = {}
else:
logger.debug("Aucun fichier controls.json trouvé")
return {}
data = {}
changed = False
# Normaliser les alias vers laction canonique "clear_history"
# Votre controls.json a "delete_history": mappez-le vers "clear_history"
if "delete_history" in data and "clear_history" not in data:
data["clear_history"] = data["delete_history"]
changed = True
# Ancien alias éventuel
if "progress" in data and "clear_history" not in data:
data["clear_history"] = data["progress"]
changed = True
# Compléter avec des valeurs par défaut si nécessaire (facultatif selon votre implémentation)
# ...existing code de complétion si présent...
if changed:
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
return data
except Exception as e:
logger.error(f"Erreur lors du chargement de controls.json : {e}")
return {}

View File

@@ -248,14 +248,14 @@ def get_control_display(action, default):
if control_type == 'key':
key_code = control_config.get('key')
key_names = {
pygame.K_RETURN: "Ente",
pygame.K_RETURN: "Enter",
pygame.K_ESCAPE: "Échap",
pygame.K_SPACE: "Espace",
pygame.K_UP: "Flèche Haut",
pygame.K_DOWN: "Flèche Bas",
pygame.K_LEFT: "Flèche Gauche",
pygame.K_RIGHT: "Flèche Droite",
pygame.K_BACKSPACE: "Retour Arrière",
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",
@@ -304,23 +304,23 @@ def get_control_display(action, default):
pygame.K_7: "7",
pygame.K_8: "8",
pygame.K_9: "9",
pygame.K_KP0: "Pavé 0",
pygame.K_KP1: "Pavé 1",
pygame.K_KP2: "Pavé 2",
pygame.K_KP3: "Pavé 3",
pygame.K_KP4: "Pavé 4",
pygame.K_KP5: "Pavé 5",
pygame.K_KP6: "Pavé 6",
pygame.K_KP7: "Pavé 7",
pygame.K_KP8: "Pavé 8",
pygame.K_KP9: "Pavé 9",
pygame.K_KP_PERIOD: "Pavé .",
pygame.K_KP_DIVIDE: "Pavé /",
pygame.K_KP_MULTIPLY: "Pavé *",
pygame.K_KP_MINUS: "Pavé -",
pygame.K_KP_PLUS: "Pavé +",
pygame.K_KP_ENTER: "Pavé Ente",
pygame.K_KP_EQUALS: "Pavé =",
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",
@@ -340,9 +340,9 @@ def get_control_display(action, default):
pygame.K_DELETE: "Suppr",
pygame.K_HOME: "Début",
pygame.K_END: "Fin",
pygame.K_PAGEUP: "Page Haut",
pygame.K_PAGEDOWN: "Page Bas",
pygame.K_PRINT: "Impr Écran",
pygame.K_PAGEUP: "Page+",
pygame.K_PAGEDOWN: "Page-",
pygame.K_PRINT: "Printscreen",
pygame.K_SYSREQ: "SysReq",
pygame.K_BREAK: "Pause",
pygame.K_PAUSE: "Pause",
@@ -1249,121 +1249,161 @@ def draw_pause_menu(screen, selected_option):
# Menu aide contrôles
def draw_controls_help(screen, previous_state):
"""Affiche la liste des contrôles avec un style moderne."""
start_text = _("controls_action_start")
history_text = _("controls_action_history")
delete_text = _("controls_action_delete")
space_text = _("controls_action_space")
nav_text = _("controls_navigation")
pages_text = _("controls_pages")
confirm_select_text = _("controls_confirm_select")
cancel_back_text = _("controls_cancel_back")
clear_history_text = _("controls_action_delete_history")
filter_search_text = _("controls_filter_search")
"""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', '')} : {nav_text}",
f"{get_control_display('page_up', 'LB')} {get_control_display('page_down', 'RB')} : {pages_text}"
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')} : {confirm_select_text}",
f"{get_control_display('cancel', 'B')} : {cancel_back_text}",
f"{get_control_display('start', 'Start')} : {start_text}"
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')} : {history_text}",
f"{get_control_display('progress', 'X')} : {clear_history_text}"
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')}",
],
_("controls_category_search"): [
f"{get_control_display('filter', 'Select')} : {filter_search_text}",
f"{get_control_display('delete', 'Suppr')} : {delete_text}",
f"{get_control_display('space', 'Espace')} : {space_text}"
]
}
state_controls = {
"error": control_categories,
"platform": control_categories,
"game": control_categories,
"download_progress": control_categories,
"download_result": control_categories,
"confirm_exit": control_categories,
"extension_warning": control_categories,
"history": control_categories
}
control_columns = state_controls.get(previous_state, {})
if not control_columns:
# États autorisés (même logique qu'avant)
allowed_states = {
"error", "platform", "game", "download_result", "confirm_exit",
"extension_warning", "history", "clear_history"
}
if previous_state not in allowed_states:
return
screen.blit(OVERLAY, (0, 0))
# Organisation en 2x2
categories = list(control_columns.keys())
col1 = [categories[0], categories[2]] # Navigation, Historique/Téléchargements
col2 = [categories[1], categories[3]] # Actions principales, Recherche / Filtre
# Calculer la largeur nécessaire
max_text_width = 0
for category, controls in control_columns.items():
for control in controls:
text_width = config.small_font.size(control)[0]
max_text_width = max(max_text_width, text_width)
col_width = max_text_width + 40
popup_width = col_width * 2 + 100 # Plus d'espace entre colonnes
popup_height = 320
popup_x = (config.screen_width - popup_width) // 2
popup_y = (config.screen_height - popup_height) // 2
# 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)
# Fond principal
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)
# 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_text = _("controls_help_title")
title_surface = config.title_font.render(title_text, True, THEME_COLORS["text"])
title_rect = title_surface.get_rect(center=(config.screen_width // 2, popup_y + 25))
screen.blit(title_surface, title_rect)
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)
# Affichage en colonnes
start_y = popup_y + 60
# Colonne 1
current_y = start_y
for category in col1:
controls = control_columns[category]
# Titre
cat_surface = config.font.render(category, True, THEME_COLORS["fond_lignes"])
cat_rect = cat_surface.get_rect(x=popup_x + 20, y=current_y)
screen.blit(cat_surface, cat_rect)
current_y += 30 # Plus d'espace après titre
# Contrôles
for control in controls:
ctrl_surface = config.small_font.render(f"{control}", True, THEME_COLORS["text"])
ctrl_rect = ctrl_surface.get_rect(x=popup_x + 30, y=current_y)
screen.blit(ctrl_surface, ctrl_rect)
current_y += 20
current_y += 20 # Plus d'espace entre sections
# Colonne 2
current_y = start_y
for category in col2:
controls = control_columns[category]
# Titre
cat_surface = config.font.render(category, True, THEME_COLORS["fond_lignes"])
cat_rect = cat_surface.get_rect(x=popup_x + col_width + 40, y=current_y) # Plus d'espace entre colonnes
screen.blit(cat_surface, cat_rect)
current_y += 30 # Plus d'espace après titre
# Contrôles
for control in controls:
ctrl_surface = config.small_font.render(f"{control}", True, THEME_COLORS["text"])
ctrl_rect = ctrl_surface.get_rect(x=popup_x + col_width + 50, y=current_y) # Plus d'espace entre colonnes
screen.blit(ctrl_surface, ctrl_rect)
current_y += 20
current_y += 20 # Plus d'espace entre sections
# Menu Quitter Appli
def draw_confirm_dialog(screen):

View File

@@ -28,7 +28,7 @@ def parse_es_input_config():
"right": "right",
"pageup": "page_up",
"pagedown": "page_down",
"y": "progress",
"y": "clear_history",
"x": "history",
"select": "filter",
"leftshoulder": "delete",
@@ -129,7 +129,7 @@ def parse_es_input_config():
controls_config["start"] = {"type": "key", "key": pygame.K_p}
controls_config["filter"] = {"type": "key", "key": pygame.K_f}
controls_config["history"] = {"type": "key", "key": pygame.K_h}
controls_config["progress"] = {"type": "key", "key": pygame.K_x}
controls_config["clear_history"] = {"type": "key", "key": pygame.K_x}
controls_config["page_up"] = {"type": "key", "key": pygame.K_PAGEUP}
controls_config["page_down"] = {"type": "key", "key": pygame.K_PAGEDOWN}
@@ -143,7 +143,7 @@ def parse_es_input_config():
"right": {"type": "key", "key": pygame.K_RIGHT},
"page_up": {"type": "key", "key": pygame.K_PAGEUP},
"page_down": {"type": "key", "key": pygame.K_PAGEDOWN},
"progress": {"type": "key", "key": pygame.K_x},
"clear_history": {"type": "key", "key": pygame.K_x},
"history": {"type": "key", "key": pygame.K_h},
"filter": {"type": "key", "key": pygame.K_f},
"delete": {"type": "key", "key": pygame.K_DELETE},

View File

@@ -104,7 +104,7 @@
"controls_action_right": "Rechts",
"controls_action_page_up": "Vorherige Seite",
"controls_action_page_down": "Nächste Seite",
"controls_action_delete_history": "Verlauf leeren",
"controls_action_clear_history": "Verlauf leeren",
"controls_action_history": "Verlauf",
"controls_action_filter": "Filtern",
"controls_action_delete": "Löschen",
@@ -119,7 +119,7 @@
"controls_desc_right": "Nach rechts navigieren",
"controls_desc_page_up": "Vorherige Seite/Schnelles Scrollen nach oben (z.B.: BildAuf, LB)",
"controls_desc_page_down": "Nächste Seite/Schnelles Scrollen nach unten (z.B.: BildAb, RB)",
"controls_desc_delete_history": "Verlauf löschen (z.B.: X)",
"controls_desc_clear_history": "Verlauf löschen (z.B.: X)",
"controls_desc_history": "Verlauf öffnen (z.B.: H, Y)",
"controls_desc_filter": "Filter öffnen (z.B.: F, Select)",
"controls_desc_delete": "Zeichen löschen (z.B.: LT, Entf)",
@@ -131,7 +131,6 @@
"action_quit": "Beenden",
"action_select": "Auswählen",
"action_history": "Verlauf",
"action_progress": "Fortschritt",
"action_download": "Herunterladen",
"action_filter": "Filtern",
"action_cancel": "Abbrechen",

View File

@@ -95,7 +95,7 @@
"controls_action_right": "Right",
"controls_action_page_up": "Previous Page",
"controls_action_page_down": "Next Page",
"controls_action_delete_history": "Clear History",
"controls_action_clear_history": "Clear History",
"controls_action_history": "History",
"controls_action_filter": "Filter",
"controls_action_delete": "Delete",
@@ -110,7 +110,7 @@
"controls_desc_right": "Navigate right",
"controls_desc_page_up": "Previous page/Fast scroll up (e.g. PageUp, LB)",
"controls_desc_page_down": "Next page/Fast scroll down (e.g. PageDown, RB)",
"controls_desc_delete_history": "Clear History (e.g. X)",
"controls_desc_clear_history": "Clear History (e.g. X)",
"controls_desc_history": "Open history (e.g. H, Y)",
"controls_desc_filter": "Open filter (e.g. F, Select)",
"controls_desc_delete": "Delete character (e.g. LT, Delete)",
@@ -122,7 +122,6 @@
"action_quit": "Quit",
"action_select": "Select",
"action_history": "History",
"action_progress": "Progress",
"action_download": "Download",
"action_filter": "Filter",
"action_cancel": "Cancel",

View File

@@ -106,7 +106,7 @@
"controls_action_right": "Derecha",
"controls_action_page_up": "Página anterior",
"controls_action_page_down": "Página siguiente",
"controls_action_delete_history": "Vaciar Historial",
"controls_action_clear_history": "Vaciar Historial",
"controls_action_history": "Historial",
"controls_action_filter": "Filtrar",
"controls_action_delete": "Eliminar",
@@ -121,7 +121,7 @@
"controls_desc_right": "Navegar a derecha",
"controls_desc_page_up": "Página anterior/Desplazamiento rápido arriba (ej: RePág, LB)",
"controls_desc_page_down": "Página siguiente/Desplazamiento rápido abajo (ej: AvPág, RB)",
"controls_desc_delete_history": "Borrar Historial (ej: X)",
"controls_desc_clear_history": "Borrar Historial (ej: X)",
"controls_desc_history": "Abrir historial (ej: H, Y)",
"controls_desc_filter": "Abrir filtro (ej: F, Select)",
"controls_desc_delete": "Eliminar carácter (ej: LT, Supr)",
@@ -133,7 +133,6 @@
"action_quit": "Salir",
"action_select": "Seleccionar",
"action_history": "Historial",
"action_progress": "Progreso",
"action_download": "Descargar",
"action_filter": "Filtrar",
"action_cancel": "Cancelar",

View File

@@ -101,7 +101,7 @@
"controls_action_right": "Droite",
"controls_action_page_up": "Page Précédente",
"controls_action_page_down": "Page Suivante",
"controls_action_delete_history": "Vider Historique",
"controls_action_clear_history": "Vider Historique",
"controls_action_history": "Historique",
"controls_action_filter": "Filtrer",
"controls_action_delete": "Supprimer",
@@ -117,7 +117,7 @@
"controls_desc_page_up": "Défilement Rapide - (ex: PageUp, LB)",
"controls_desc_page_down": "Défilement Rapide + (ex: PageDown, RB)",
"controls_desc_history": "Ouvrir l'historique (ex: H, Y)",
"controls_desc_delete_history": "Effacer Historique (ex: X)",
"controls_desc_clear_history": "Effacer Historique (ex: X)",
"controls_desc_filter": "Mode Filtre : Ouvrir/Valider (ex: F, Select)",
"controls_desc_delete": "Mode Filtre : Supprimer caractère (ex: LT, Suppr)",
"controls_desc_space": "Mode Filtre : Ajouter espace (ex: RT, Espace)",
@@ -128,7 +128,6 @@
"action_quit": "Quitter",
"action_select": "Sélectionner",
"action_history": "Historique",
"action_progress": "Progression",
"action_download": "Télécharger",
"action_filter": "Filtrer",
"action_cancel": "Annuler",

View File

@@ -12,28 +12,6 @@ import zipfile
import time
import random
from config import JSON_EXTENSIONS, SAVE_FOLDER
def load_accessibility_settings():
"""Charge les paramètres d'accessibilité depuis accessibility.json."""
accessibility_path = os.path.join(SAVE_FOLDER, "accessibility.json")
try:
if os.path.exists(accessibility_path):
with open(accessibility_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
logger.error(f"Erreur lors du chargement de accessibility.json: {str(e)}")
return {"font_scale": 1.0}
def save_accessibility_settings(settings):
"""Sauvegarde les paramètres d'accessibilité dans accessibility.json."""
accessibility_path = os.path.join(SAVE_FOLDER, "accessibility.json")
try:
os.makedirs(SAVE_FOLDER, exist_ok=True)
with open(accessibility_path, 'w', encoding='utf-8') as f:
json.dump(settings, f, indent=2)
logger.debug(f"Paramètres d'accessibilité sauvegardés: {settings}")
except Exception as e:
logger.error(f"Erreur lors de la sauvegarde de accessibility.json: {str(e)}")
from history import save_history
from language import _ # Import de la fonction de traduction
from datetime import datetime
@@ -59,7 +37,7 @@ def detect_non_pc():
except (subprocess.SubprocessError, FileNotFoundError):
logger.debug(f"batocera-es-swissknife non disponible, utilisation de platform.machine(): {arch}")
is_non_pc = arch not in ["x86_64", "amd64"]
is_non_pc = arch not in ["x86_64", "amd64", "AMD64"]
logger.debug(f"Système détecté: {platform.system()}, architecture: {arch}, is_non_pc={is_non_pc}")
return is_non_pc