v2.2.2.6
- updated controls mapping and reading simplified - update translations - move some files to reorganize folders - add some icons to controls
@@ -16,7 +16,6 @@ import datetime
|
||||
import subprocess
|
||||
import sys
|
||||
import config
|
||||
import shutil
|
||||
|
||||
from display import (
|
||||
init_display, draw_loading_screen, draw_error_screen, draw_platform_grid,
|
||||
@@ -64,7 +63,6 @@ try: # pragma: no cover
|
||||
logger.debug("API key files ensured at startup")
|
||||
except Exception as _e:
|
||||
logger.warning(f"Cannot prepare API key files early: {_e}")
|
||||
|
||||
# Mise à jour de la gamelist Windows avant toute initialisation graphique (évite les conflits avec ES)
|
||||
def _run_windows_gamelist_update():
|
||||
try:
|
||||
@@ -198,6 +196,15 @@ for i in range(count):
|
||||
joystick_names.append(j.get_name())
|
||||
except Exception as e:
|
||||
logger.debug(f"Impossible de lire le nom du joystick {i}: {e}")
|
||||
|
||||
# Enregistrer le nom du premier joystick détecté pour l'auto-préréglage
|
||||
try:
|
||||
if joystick_names:
|
||||
config.controller_device_name = joystick_names[0]
|
||||
else:
|
||||
config.controller_device_name = ""
|
||||
except Exception:
|
||||
pass
|
||||
normalized_names = [n.lower() for n in joystick_names]
|
||||
if not joystick_names:
|
||||
joystick_names = ["Clavier"]
|
||||
@@ -205,73 +212,12 @@ if not joystick_names:
|
||||
logger.debug("Aucun joystick détecté, utilisation du clavier par défaut.")
|
||||
config.joystick = False
|
||||
config.keyboard = True
|
||||
# Si aucune marque spécifique détectée mais un joystick est présent, marquer comme générique
|
||||
if not any([config.xbox_controller, config.playstation_controller, config.nintendo_controller,
|
||||
config.eightbitdo_controller, config.steam_controller, config.trimui_controller,
|
||||
config.logitech_controller]):
|
||||
config.generic_controller = True
|
||||
logger.debug("Aucun contrôleur spécifique détecté, utilisation du profil générique")
|
||||
else:
|
||||
# Des joysticks sont présents, activer le mode joystick et tenter la détection spécifique
|
||||
# Des joysticks sont présents: activer le mode joystick et mémoriser le nom pour l'auto-préréglage
|
||||
config.joystick = True
|
||||
config.keyboard = False
|
||||
print(f"Joysticks détectés: YES")
|
||||
logger.debug(f"Joysticks détectés: YES")
|
||||
for idx, name in enumerate(joystick_names):
|
||||
lname = name.lower()
|
||||
# Détection Anbernic RG35XX
|
||||
if ("rg35xx" in lname):
|
||||
config.anbernic_rg35xx_controller = True
|
||||
logger.debug(f"Anbernic Controller detected : {name}")
|
||||
print(f"Controller detected : {name}")
|
||||
break
|
||||
# Détection spécifique Elite AVANT la détection générique Xbox
|
||||
elif ("microsoft xbox controller" in lname):
|
||||
config.xbox_elite_controller = True
|
||||
logger.debug(f"Controller detected: {name}")
|
||||
print(f"Controller detected: {name}")
|
||||
break
|
||||
elif ("xbox" in lname) or ("x-box" in lname) or ("xinput" in lname) or ("microsoft x-box" in lname) or ("x-box 360" in lname) or ("360" in lname):
|
||||
config.xbox_controller = True
|
||||
logger.debug(f"Xbox Controller detected : {name}")
|
||||
print(f"Controller detected : {name}")
|
||||
break
|
||||
elif "playstation" in lname or "ps3" in lname or "sony" in lname:
|
||||
config.playstation_controller = True
|
||||
logger.debug(f"Playstation Controller detected : {name}")
|
||||
print(f"Controller detected : {name}")
|
||||
break
|
||||
elif "nintendo" in lname:
|
||||
config.nintendo_controller = True
|
||||
logger.debug(f"Nintendo Controller detected : {name}")
|
||||
print(f"Controller detected : {name}")
|
||||
break
|
||||
elif "trimui" in lname:
|
||||
config.trimui_controller = True
|
||||
logger.debug(f"Trimui Controller detected : {name}")
|
||||
print(f"Controller detected : {name}")
|
||||
break
|
||||
elif "logitech" in lname:
|
||||
config.logitech_controller = True
|
||||
logger.debug(f"Logitech Controller detected : {name}")
|
||||
print(f"Controller detected : {name}")
|
||||
break
|
||||
elif "8bitdo" in lname or "8-bitdo" in lname:
|
||||
config.eightbitdo_controller = True
|
||||
logger.debug(f"8bitdoController detected : {name}")
|
||||
print(f"Controller detected : {name}")
|
||||
break
|
||||
elif "steam" in lname:
|
||||
config.steam_controller = True
|
||||
logger.debug(f"Steam Controller detected : {name}")
|
||||
print(f"Controller detected : {name}")
|
||||
else:
|
||||
# Si aucune marque spécifique détectée mais un joystick est présent, marquer comme générique
|
||||
config.generic_controller = True
|
||||
logger.debug(f"Generic Controller detected : {name}")
|
||||
print(f"Generic Controller detected : {name}")
|
||||
# Note: virtual keyboard display now depends on controller presence (config.joystick)
|
||||
logger.debug(f"Flags contrôleur: xbox={config.xbox_controller}, ps={config.playstation_controller}, nintendo={config.nintendo_controller}, eightbitdo={config.eightbitdo_controller}, steam={config.steam_controller}, trimui={config.trimui_controller}, logitech={config.logitech_controller}, generic={config.generic_controller}")
|
||||
print("Joystick détecté:", ", ".join(joystick_names))
|
||||
logger.debug(f"Joysticks détectés: {joystick_names}")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
{
|
||||
"confirm": {
|
||||
"type": "button",
|
||||
"button": 1,
|
||||
"display": "B"
|
||||
},
|
||||
"cancel": {
|
||||
"type": "button",
|
||||
"button": 0,
|
||||
"display": "A"
|
||||
},
|
||||
"up": {
|
||||
"type": "hat",
|
||||
"value": [0, 1],
|
||||
"display": "↑"
|
||||
},
|
||||
"down": {
|
||||
"type": "hat",
|
||||
"value": [0, -1],
|
||||
"display": "↓"
|
||||
},
|
||||
"left": {
|
||||
"type": "hat",
|
||||
"value": [-1, 0],
|
||||
"display": "←"
|
||||
},
|
||||
"right": {
|
||||
"type": "hat",
|
||||
"value": [1, 0],
|
||||
"display": "→"
|
||||
},
|
||||
"start": {
|
||||
"type": "button",
|
||||
"button": 7,
|
||||
"display": "Start"
|
||||
},
|
||||
"filter": {
|
||||
"type": "button",
|
||||
"button": 6,
|
||||
"display": "Select"
|
||||
},
|
||||
"page_up": {
|
||||
"type": "axis",
|
||||
"axis": 4,
|
||||
"direction": 1,
|
||||
"display": "ZR"
|
||||
},
|
||||
"page_down": {
|
||||
"type": "axis",
|
||||
"axis": 5,
|
||||
"direction": -1,
|
||||
"display": "ZL"
|
||||
},
|
||||
"history": {
|
||||
"type": "button",
|
||||
"button": 3,
|
||||
"display": "Y"
|
||||
},
|
||||
"clear_history": {
|
||||
"type": "button",
|
||||
"button": 2,
|
||||
"display": "X"
|
||||
},
|
||||
"delete": {
|
||||
"type": "button",
|
||||
"button": 4,
|
||||
"display": "L"
|
||||
},
|
||||
"space": {
|
||||
"type": "button",
|
||||
"button": 5,
|
||||
"display": "R"
|
||||
}
|
||||
}
|
||||
75
ports/RGSX/assets/controls/ps5_dualsense.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"device": "DualSense Wireless Controller",
|
||||
"up": {
|
||||
"type": "button",
|
||||
"button": 11,
|
||||
"display": "\u2191"
|
||||
},
|
||||
"down": {
|
||||
"type": "button",
|
||||
"button": 12,
|
||||
"display": "\u2193"
|
||||
},
|
||||
"left": {
|
||||
"type": "button",
|
||||
"button": 13,
|
||||
"display": "\u2190"
|
||||
},
|
||||
"right": {
|
||||
"type": "button",
|
||||
"button": 14,
|
||||
"display": "\u2192"
|
||||
},
|
||||
"confirm": {
|
||||
"type": "button",
|
||||
"button": 0,
|
||||
"display": "A"
|
||||
},
|
||||
"cancel": {
|
||||
"type": "button",
|
||||
"button": 1,
|
||||
"display": "B"
|
||||
},
|
||||
"history": {
|
||||
"type": "button",
|
||||
"button": 3,
|
||||
"display": "Y"
|
||||
},
|
||||
"clear_history": {
|
||||
"type": "button",
|
||||
"button": 2,
|
||||
"display": "X"
|
||||
},
|
||||
"start": {
|
||||
"type": "button",
|
||||
"button": 6,
|
||||
"display": "Start"
|
||||
},
|
||||
"filter": {
|
||||
"type": "button",
|
||||
"button": 4,
|
||||
"display": "Select"
|
||||
},
|
||||
"delete": {
|
||||
"type": "button",
|
||||
"button": 9,
|
||||
"display": "LB"
|
||||
},
|
||||
"space": {
|
||||
"type": "button",
|
||||
"button": 10,
|
||||
"display": "RB"
|
||||
},
|
||||
"page_up": {
|
||||
"type": "axis",
|
||||
"axis": 4,
|
||||
"direction": 1,
|
||||
"display": "LT"
|
||||
},
|
||||
"page_down": {
|
||||
"type": "axis",
|
||||
"axis": 5,
|
||||
"direction": 1,
|
||||
"display": "RT"
|
||||
}
|
||||
}
|
||||
20
ports/RGSX/assets/controls/retroid_pocket_flip_2.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"device": "Retroid Pocket Controller",
|
||||
"confirm": { "type": "button", "button": 1, "display": "A" },
|
||||
"cancel": { "type": "button", "button": 2, "display": "B" },
|
||||
"clear_history": { "type": "button", "button": 4, "display": "X" },
|
||||
"history": { "type": "button", "button": 3, "display": "Y" },
|
||||
"start": { "type": "button", "button": 8, "display": "Start" },
|
||||
"filter": { "type": "button", "button": 7, "display": "Select" },
|
||||
|
||||
"up": { "type": "button", "button": 12, "display": "↑" },
|
||||
"down": { "type": "button", "button": 13, "display": "↓" },
|
||||
"left": { "type": "button", "button": 14, "display": "←" },
|
||||
"right": { "type": "button", "button": 15, "display": "→" },
|
||||
|
||||
"delete": { "type": "button", "button": 5, "display": "LB" },
|
||||
"space": { "type": "button", "button": 6, "display": "RB" },
|
||||
|
||||
"page_up": { "type": "axis", "axis": 2, "direction": -1, "display": "LT" },
|
||||
"page_down": { "type": "axis", "axis": 5, "direction": -1, "display": "RT" }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"device": "RG34XX-SP Controller",
|
||||
"confirm": {
|
||||
"type": "button",
|
||||
"button": 3,
|
||||
20
ports/RGSX/assets/controls/rgb10_ogs.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"device": "GO-Super Gamepad",
|
||||
"confirm": { "type": "button", "button": 0, "display": "A" },
|
||||
"cancel": { "type": "button", "button": 1, "display": "B" },
|
||||
"clear_history": { "type": "button", "button": 3, "display": "X" },
|
||||
"history": { "type": "button", "button": 2, "display": "Y" },
|
||||
"start": { "type": "button", "button": 13, "display": "Start" },
|
||||
"filter": { "type": "button", "button": 12, "display": "Select" },
|
||||
|
||||
"up": { "type": "button", "button": 8, "display": "↑" },
|
||||
"down": { "type": "button", "button": 9, "display": "↓" },
|
||||
"left": { "type": "button", "button": 10, "display": "←" },
|
||||
"right": { "type": "button", "button": 11, "display": "→" },
|
||||
|
||||
"delete": { "type": "button", "button": 6, "display": "LB" },
|
||||
"space": { "type": "button", "button": 7, "display": "RB" },
|
||||
|
||||
"page_up": { "type": "button", "button": 4, "display": "LT" },
|
||||
"page_down": { "type": "button", "button": 5, "display": "RT" }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"device": "Steam Deck",
|
||||
"confirm": {
|
||||
"type": "button",
|
||||
"button": 3,
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"device": "TRIMUI Smart Pro Controller",
|
||||
"confirm": {
|
||||
"type": "button",
|
||||
"button": 0,
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"device": "XBOX 360 For Windows (Controller)",
|
||||
"confirm": {
|
||||
"type": "button",
|
||||
"button": 0,
|
||||
@@ -1,4 +1,37 @@
|
||||
{
|
||||
"device": "Xbox 360 Controller",
|
||||
"up": {
|
||||
"type": "hat",
|
||||
"value": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"display": "\u2191"
|
||||
},
|
||||
"down": {
|
||||
"type": "hat",
|
||||
"value": [
|
||||
0,
|
||||
-1
|
||||
],
|
||||
"display": "\u2193"
|
||||
},
|
||||
"left": {
|
||||
"type": "hat",
|
||||
"value": [
|
||||
-1,
|
||||
0
|
||||
],
|
||||
"display": "\u2190"
|
||||
},
|
||||
"right": {
|
||||
"type": "hat",
|
||||
"value": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"display": "\u2192"
|
||||
},
|
||||
"confirm": {
|
||||
"type": "button",
|
||||
"button": 0,
|
||||
@@ -9,48 +42,6 @@
|
||||
"button": 1,
|
||||
"display": "B"
|
||||
},
|
||||
"up": {
|
||||
"type": "hat",
|
||||
"value": [0, 1],
|
||||
"display": "↑"
|
||||
},
|
||||
"down": {
|
||||
"type": "hat",
|
||||
"value": [0, -1],
|
||||
"display": "↓"
|
||||
},
|
||||
"left": {
|
||||
"type": "hat",
|
||||
"value": [-1, 0],
|
||||
"display": "←"
|
||||
},
|
||||
"right": {
|
||||
"type": "hat",
|
||||
"value": [1, 0],
|
||||
"display": "→"
|
||||
},
|
||||
"start": {
|
||||
"type": "button",
|
||||
"button": 7,
|
||||
"display": "Start"
|
||||
},
|
||||
"filter": {
|
||||
"type": "button",
|
||||
"button": 6,
|
||||
"display": "Select"
|
||||
},
|
||||
"page_up": {
|
||||
"type": "axis",
|
||||
"axis": 4,
|
||||
"direction": 1,
|
||||
"display": "RT"
|
||||
},
|
||||
"page_down": {
|
||||
"type": "axis",
|
||||
"axis": 5,
|
||||
"direction": -1,
|
||||
"display": "LT"
|
||||
},
|
||||
"history": {
|
||||
"type": "button",
|
||||
"button": 3,
|
||||
@@ -61,6 +52,16 @@
|
||||
"button": 2,
|
||||
"display": "X"
|
||||
},
|
||||
"start": {
|
||||
"type": "button",
|
||||
"button": 7,
|
||||
"display": "Start"
|
||||
},
|
||||
"filter": {
|
||||
"type": "button",
|
||||
"button": 6,
|
||||
"display": "Select"
|
||||
},
|
||||
"delete": {
|
||||
"type": "button",
|
||||
"button": 4,
|
||||
@@ -70,5 +71,17 @@
|
||||
"type": "button",
|
||||
"button": 5,
|
||||
"display": "RB"
|
||||
},
|
||||
"page_up": {
|
||||
"type": "axis",
|
||||
"axis": 4,
|
||||
"direction": -1,
|
||||
"display": "LT"
|
||||
},
|
||||
"page_down": {
|
||||
"type": "axis",
|
||||
"axis": 5,
|
||||
"direction": -1,
|
||||
"display": "RT"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"device": "Microsoft Xbox Controller",
|
||||
"confirm": { "type": "button", "button": 1, "display": "A" },
|
||||
"cancel": { "type": "button", "button": 2, "display": "B" },
|
||||
"clear_history": { "type": "button", "button": 3, "display": "X" },
|
||||
@@ -12,8 +13,5 @@
|
||||
"left": { "type": "hat", "value": [-1, 0], "display": "\u2190" },
|
||||
"right": { "type": "hat", "value": [1, 0], "display": "\u2192" },
|
||||
"page_up": { "type": "axis", "axis": 5, "direction": -1, "display": "RT" },
|
||||
"page_down": { "type": "axis", "axis": 2, "direction": -1, "display": "LT" },
|
||||
"meta": {
|
||||
"notes": "Mapping spécifique Xbox Elite basé sur log fourni. Triggers décalés: LEFT_TRIGGER=AXIS2 -, RIGHT_TRIGGER=AXIS5 -. Les boutons semblent décalés de +1 vs profil 360 standard."
|
||||
}
|
||||
"page_down": { "type": "axis", "axis": 2, "direction": -1, "display": "LT" }
|
||||
}
|
||||
@@ -1,4 +1,25 @@
|
||||
{
|
||||
"device": "ZEROPLUS Controller",
|
||||
"up": {
|
||||
"type": "button",
|
||||
"button": 11,
|
||||
"display": "\u2191"
|
||||
},
|
||||
"down": {
|
||||
"type": "button",
|
||||
"button": 12,
|
||||
"display": "\u2193"
|
||||
},
|
||||
"left": {
|
||||
"type": "button",
|
||||
"button": 13,
|
||||
"display": "\u2190"
|
||||
},
|
||||
"right": {
|
||||
"type": "button",
|
||||
"button": 14,
|
||||
"display": "\u2192"
|
||||
},
|
||||
"confirm": {
|
||||
"type": "button",
|
||||
"button": 0,
|
||||
@@ -9,48 +30,6 @@
|
||||
"button": 1,
|
||||
"display": "B"
|
||||
},
|
||||
"up": {
|
||||
"type": "hat",
|
||||
"value": [0, 1],
|
||||
"display": "↑"
|
||||
},
|
||||
"down": {
|
||||
"type": "hat",
|
||||
"value": [0, -1],
|
||||
"display": "↓"
|
||||
},
|
||||
"left": {
|
||||
"type": "hat",
|
||||
"value": [-1, 0],
|
||||
"display": "←"
|
||||
},
|
||||
"right": {
|
||||
"type": "hat",
|
||||
"value": [1, 0],
|
||||
"display": "→"
|
||||
},
|
||||
"start": {
|
||||
"type": "button",
|
||||
"button": 7,
|
||||
"display": "Start"
|
||||
},
|
||||
"filter": {
|
||||
"type": "button",
|
||||
"button": 6,
|
||||
"display": "Select"
|
||||
},
|
||||
"page_up": {
|
||||
"type": "axis",
|
||||
"axis": 4,
|
||||
"direction": 1,
|
||||
"display": "RT"
|
||||
},
|
||||
"page_down": {
|
||||
"type": "axis",
|
||||
"axis": 5,
|
||||
"direction": -1,
|
||||
"display": "LT"
|
||||
},
|
||||
"history": {
|
||||
"type": "button",
|
||||
"button": 3,
|
||||
@@ -61,14 +40,36 @@
|
||||
"button": 2,
|
||||
"display": "X"
|
||||
},
|
||||
"delete": {
|
||||
"start": {
|
||||
"type": "button",
|
||||
"button": 6,
|
||||
"display": "Start"
|
||||
},
|
||||
"filter": {
|
||||
"type": "button",
|
||||
"button": 4,
|
||||
"display": "Select"
|
||||
},
|
||||
"delete": {
|
||||
"type": "button",
|
||||
"button": 9,
|
||||
"display": "LB"
|
||||
},
|
||||
"space": {
|
||||
"type": "button",
|
||||
"button": 5,
|
||||
"button": 10,
|
||||
"display": "RB"
|
||||
},
|
||||
"page_up": {
|
||||
"type": "axis",
|
||||
"axis": 4,
|
||||
"direction": 1,
|
||||
"display": "LT"
|
||||
},
|
||||
"page_down": {
|
||||
"type": "axis",
|
||||
"axis": 5,
|
||||
"direction": 1,
|
||||
"display": "RT"
|
||||
}
|
||||
}
|
||||
}
|
||||
5
ports/RGSX/assets/images/button_l.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="outline" d="M15,19 L49,19 A13,13 90 0,1 62,32 A13,13 90 0,1 49,45 L15,45 A13,13 90 0,1 2,32 A13,13 90 0,1 15,19 Z" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<path id="button_l" d="m15 19a13 13 0 0 0-13 13 13 13 0 0 0 13 13h34a13 13 0 0 0 13-13 13 13 0 0 0-13-13h-34zm12.804688 4.433594h4.101562v13.921875h4.289062v3.210937h-8.390624v-17.132812z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 544 B |
6
ports/RGSX/assets/images/button_lt.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="outline" d="M62,19 L62,32 A13,13 90 0,1 49,45 L15,45 A13,13 90 0,1 2,32 L2,19" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<line id="outline2" x1="1" y1="19" x2="63" y2="19" stroke="#fff" stroke-width="2"/>
|
||||
<path id="button_lt" d="m2 19v13a13 13 0 0 0 13 13h34a13 13 0 0 0 13-13v-13h-60zm19.939453 4.433594h4.101563v13.921875h4.289062v3.210937h-8.390625v-17.132812zm9.667969 0h10.453125v3.222656h-3.1875v13.910156h-4.078125v-13.910156h-3.1875v-3.222656z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 652 B |
5
ports/RGSX/assets/images/button_r.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="outline" d="M15,19 L49,19 A13,13 90 0,1 62,32 A13,13 90 0,1 49,45 L15,45 A13,13 90 0,1 2,32 A13,13 90 0,1 15,19 Z" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<path id="button_r" d="m15 19a13 13 0 0 0-13 13 13 13 0 0 0 13 13h34a13 13 0 0 0 13-13 13 13 0 0 0-13-13h-34zm12.330078 4.433594h4.546875c1.882813 0 3.273438 0.425781 4.171875 1.277344 0.898438 0.851562 1.347656 2.15625 1.347656 3.914062 0 2.039062-0.699218 3.519531-2.097656 4.441406l3.28125 7.5h-4.324219l-2.625-6.46875h-0.210937v6.46875h-4.089844v-17.132812zm4.089844 3.175781v4.324219h0.304687c0.539063 0 0.929688-0.183594 1.171875-0.550782 0.25-0.367187 0.375-0.921874 0.375-1.664062 0-0.75-0.128906-1.289062-0.386718-1.617188-0.25-0.328124-0.644532-0.492187-1.183594-0.492187h-0.28125z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 948 B |
6
ports/RGSX/assets/images/button_rt.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="outline" d="M62,19 L62,32 A13,13 90 0,1 49,45 L15,45 A13,13 90 0,1 2,32 L2,19" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<line id="outline2" x1="1" y1="19" x2="63" y2="19" stroke="#fff" stroke-width="2"/>
|
||||
<path id="button_rt" d="m2 19v13a13 13 0 0 0 13 13h34a13 13 0 0 0 13-13v-13h-60zm19.119141 4.433594h4.546875c1.882812 0 3.273437 0.425781 4.171875 1.277344 0.898437 0.851562 1.347656 2.15625 1.347656 3.914062 0 2.039062-0.699219 3.519531-2.097656 4.441406l3.28125 7.5h-4.324219l-2.625-6.46875h-0.210938v6.46875h-4.089843v-17.132812zm11.308593 0h10.453125v3.222656h-3.1875v13.910156h-4.078125v-13.910156h-3.1875v-3.222656zm-7.21875 3.175781v4.324219h0.304688c0.539062 0 0.929687-0.183594 1.171875-0.550782 0.25-0.367187 0.375-0.921874 0.375-1.664062 0-0.75-0.128906-1.289062-0.386719-1.617188-0.25-0.328124-0.644531-0.492187-1.183594-0.492187h-0.28125z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
12
ports/RGSX/assets/images/button_select.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="outline" d="m2 42a7 7 0 0 1 7-7h46a7 7 0 0 1 7 7 7 7 0 0 1-7 7h-46a7 7 0 0 1-7-7z" fill="#fff" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<g id ="label_select" fill="#fff">
|
||||
<path d="m7.5908203 26.665543q0.5175781 0 0.7910156-0.341797 0.2734375-0.351563 0.2734375-0.84961 0-0.507812-0.1464843-0.859375-0.1464844-0.361328-0.4296875-0.664062-0.2832032-0.302735-0.5371094-0.498047-0.2441406-0.205078-0.6152344-0.46875-1.2792969-0.898438-1.8945312-1.894531-0.6054688-0.996094-0.6054688-2.353516 0-1.875 1.1230469-2.96875 1.1328125-1.103516 2.9199219-1.103516t3.5253904 1.044922l-1.044922 2.509766q-0.05859-0.0293-0.302734-0.15625-0.244141-0.136719-0.3125-0.166016-0.06836-0.03906-0.283203-0.136718-0.2148439-0.107422-0.3027346-0.146485l-0.2636718-0.09766q-0.4296875-0.166016-0.8300782-0.166016-0.390625 0-0.625 0.361328-0.2246093 0.351563-0.2246093 0.859375 0 0.498047 0.1269531 0.830079 0.1269531 0.332031 0.390625 0.615234 0.4492187 0.46875 1.1035156 0.898437 1.2988282 0.878907 1.9531252 1.855469 0.664062 0.966797 0.664062 2.333985 0 2.041015-1.09375 3.134765-1.0937497 1.083985-3.1152341 1.083985-2.0117187 0-3.515625-0.84961v-3.203125q2.1289063 1.396485 3.2714844 1.396485z"/>
|
||||
<path d="m21.125977 17.505386h-3.66211v2.88086h3.398438v2.65625h-3.398438v3.417968h3.66211v2.675782h-7.080079v-14.277344h7.080079z"/>
|
||||
<path d="m26.838867 26.460464h3.574219v2.675782h-6.992188v-14.277344h3.417969z"/>
|
||||
<path d="m39.602539 17.505386h-3.662109v2.88086h3.398437v2.65625h-3.398437v3.417968h3.662109v2.675782h-7.080078v-14.277344h7.080078z"/>
|
||||
<path d="m46.643555 29.321793q-1.181641 0-2.109375-0.400391-0.917969-0.410156-1.503907-1.083984-0.585937-0.673829-0.966796-1.63086-0.69336-1.738281-0.69336-4.189453 0-1.933594 0.488281-3.564453 0.488282-1.650391 1.660157-2.714844 1.210937-1.074219 3.164062-1.074219 0.859375 0 1.650391 0.253907 0.800781 0.253906 1.728515 0.849609l-0.976562 2.412109q-0.07813-0.07813-0.380859-0.253906-0.302735-0.185547-0.556641-0.292969-0.673828-0.302734-1.259766-0.302734-0.576172 0-0.947265 0.341797-0.371094 0.332031-0.576172 0.820312-0.205078 0.488282-0.322266 1.171875-0.205078 1.103516-0.205078 2.373047 0 4.609375 2.099609 4.609375 0.917969 0 2.72461-1.035156v2.880859q-1.279297 0.830079-3.017578 0.830079z"/>
|
||||
<path d="m59.680664 17.544449h-2.65625v11.591797h-3.398437v-11.591797h-2.65625v-2.685547h8.710937z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
11
ports/RGSX/assets/images/button_start.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="outline" d="m2 42a7 7 0 0 1 7-7h46a7 7 0 0 1 7 7 7 7 0 0 1-7 7h-46a7 7 0 0 1-7-7z" fill="#fff" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<g id="label_start" fill="#fff">
|
||||
<path d="m11.02832 26.665543q0.517578 0 0.791016-0.341797 0.273437-0.351563 0.273437-0.84961 0-0.507812-0.146484-0.859375-0.146484-0.361328-0.429687-0.664062-0.283204-0.302735-0.53711-0.498047-0.24414-0.205078-0.615234-0.46875-1.2792971-0.898438-1.8945314-1.894531-0.6054688-0.996094-0.6054688-2.353516 0-1.875 1.1230469-2.96875 1.1328123-1.103516 2.9199223-1.103516 1.787109 0 3.52539 1.044922l-1.044922 2.509766q-0.05859-0.0293-0.302734-0.15625-0.244141-0.136719-0.3125-0.166016-0.06836-0.03906-0.283203-0.136718-0.214844-0.107422-0.302735-0.146485l-0.263671-0.09766q-0.429688-0.166016-0.830079-0.166016-0.390625 0-0.625 0.361328-0.224609 0.351563-0.224609 0.859375 0 0.498047 0.126953 0.830079 0.126953 0.332031 0.390625 0.615234 0.449219 0.46875 1.103516 0.898437 1.298828 0.878907 1.953125 1.855469 0.664062 0.966797 0.664062 2.333985 0 2.041015-1.09375 3.134765-1.09375 1.083985-3.115234 1.083985-2.0117188 0-3.5156251-0.84961v-3.203125q2.1289063 1.396485 3.2714841 1.396485z"/>
|
||||
<path d="m25.149414 17.544449h-2.65625v11.591797h-3.398437v-11.591797h-2.65625v-2.685547h8.710937z"/>
|
||||
<path d="m36.760742 29.136246h-3.4375l-0.791015-3.496094h-2.88086l-0.791015 3.496094h-3.427735l3.544922-14.335938h4.228516zm-4.814453-6.201172-0.849609-3.759766q-0.15625 0.751953-0.830078 3.759766z"/>
|
||||
<path d="m38.108398 14.858902h3.789063q2.353516 0 3.476562 1.064453 1.123047 1.064453 1.123047 3.261719 0 2.548828-1.748047 3.701172l2.734375 6.25h-3.603515l-2.1875-5.390625h-0.175781v5.390625h-3.408204zm3.408204 2.646484v3.603516h0.253906q0.673828 0 0.976562-0.458984 0.3125-0.458985 0.3125-1.386719 0-0.9375-0.322265-1.347656-0.3125-0.410157-0.986328-0.410157z"/>
|
||||
<path d="m56.243164 17.544449h-2.65625v11.591797h-3.398437v-11.591797h-2.65625v-2.685547h8.710937z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
63
ports/RGSX/assets/images/buttons_east.svg
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg7"
|
||||
viewBox="0 0 64 64"
|
||||
version="1.1"
|
||||
height="64"
|
||||
width="64">
|
||||
<metadata
|
||||
id="metadata13">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs11" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="32"
|
||||
cx="50"
|
||||
id="outline_east" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="50"
|
||||
cx="32"
|
||||
id="outline_south" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="14"
|
||||
cx="32"
|
||||
id="outline_north" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="32"
|
||||
cx="14"
|
||||
id="outline_west" />
|
||||
<path
|
||||
style="fill:#ffffff"
|
||||
d="m 50,22 c -5.522847,0 -10,4.477153 -10,10 0,5.522847 4.477153,10 10,10 5.522847,0 10,-4.477153 10,-10 0,-5.522847 -4.477153,-10 -10,-10 z"
|
||||
id="button_east" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
63
ports/RGSX/assets/images/buttons_north.svg
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg7"
|
||||
viewBox="0 0 64 64"
|
||||
version="1.1"
|
||||
height="64"
|
||||
width="64">
|
||||
<metadata
|
||||
id="metadata13">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs11" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="32"
|
||||
cx="50"
|
||||
id="outline_east" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="50"
|
||||
cx="32"
|
||||
id="outline_south" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="14"
|
||||
cx="32"
|
||||
id="outline_north" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="32"
|
||||
cx="14"
|
||||
id="outline_west" />
|
||||
<path
|
||||
style="fill:#ffffff"
|
||||
d="M 32,4 C 26.477153,4 22,8.4771525 22,14 22,19.522847 26.477153,24 32,24 37.522847,24 42,19.522847 42,14 42,8.4771525 37.522847,4 32,4 Z"
|
||||
id="button_north" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
63
ports/RGSX/assets/images/buttons_south.svg
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg7"
|
||||
viewBox="0 0 64 64"
|
||||
version="1.1"
|
||||
height="64"
|
||||
width="64">
|
||||
<metadata
|
||||
id="metadata13">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs11" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="32"
|
||||
cx="50"
|
||||
id="outline_east" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="50"
|
||||
cx="32"
|
||||
id="outline_south" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="14"
|
||||
cx="32"
|
||||
id="outline_north" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="32"
|
||||
cx="14"
|
||||
id="outline_west" />
|
||||
<path
|
||||
style="fill:#ffffff"
|
||||
d="m 32,40 c -5.522847,0 -10,4.477153 -10,10 0,5.522847 4.477153,10 10,10 5.522847,0 10,-4.477153 10,-10 0,-5.522847 -4.477153,-10 -10,-10 z"
|
||||
id="button_south" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
63
ports/RGSX/assets/images/buttons_west.svg
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg7"
|
||||
viewBox="0 0 64 64"
|
||||
version="1.1"
|
||||
height="64"
|
||||
width="64">
|
||||
<metadata
|
||||
id="metadata13">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs11" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="32"
|
||||
cx="50"
|
||||
id="outline_east" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="50"
|
||||
cx="32"
|
||||
id="outline_south" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="14"
|
||||
cx="32"
|
||||
id="outline_north" />
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
r="10"
|
||||
cy="32"
|
||||
cx="14"
|
||||
id="outline_west" />
|
||||
<path
|
||||
style="fill:#ffffff"
|
||||
d="M 14,22 C 8.4771525,22 4,26.477153 4,32 4,37.522847 8.4771525,42 14,42 19.522847,42 24,37.522847 24,32 24,26.477153 19.522847,22 14,22 Z"
|
||||
id="button_west" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
12
ports/RGSX/assets/images/dpad_down.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="outline_up" d="M22,23.5 L22,6 A4,4 90 0,1 26,2 L38,2 A4,4 90 0,1 42,6 L42,23.5" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<path id="outline_right" d="M40.5,22 L58,22 A4,4 90 0,1 62,26 L62,38 A4,4 90 0,1 58,42 L40.5,42" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<path id="outline_down" d="M22,40.5 L22,58 A4,4 90 0,0 26,62 L38,62 A4,4 90 0,0 42,58 L42,40.5" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<path id="outline_left" d="M23.5,22 L6,22 A4,4 90 0,0 2,26 L2,38 A4,4 90 0,0 6,42 L23.5,42" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<polygon id="dpad_up" points="27,14 37,14 32,6.2" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<polygon id="dpad_right" points="50,27 50,37 57.8,32" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<polygon id="dpad_down" points="27,50 37,50 32,57.8" fill="#fff" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<polygon id="dpad_left" points="14,27 14,37 6.2,32" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<circle id="dpad_thumb" cx="32" cy="32" r="5" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
12
ports/RGSX/assets/images/dpad_left.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="outline_up" d="M22,23.5 L22,6 A4,4 90 0,1 26,2 L38,2 A4,4 90 0,1 42,6 L42,23.5" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<path id="outline_right" d="M40.5,22 L58,22 A4,4 90 0,1 62,26 L62,38 A4,4 90 0,1 58,42 L40.5,42" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<path id="outline_down" d="M22,40.5 L22,58 A4,4 90 0,0 26,62 L38,62 A4,4 90 0,0 42,58 L42,40.5" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<path id="outline_left" d="M23.5,22 L6,22 A4,4 90 0,0 2,26 L2,38 A4,4 90 0,0 6,42 L23.5,42" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<polygon id="dpad_up" points="27,14 37,14 32,6.2" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<polygon id="dpad_right" points="50,27 50,37 57.8,32" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<polygon id="dpad_down" points="27,50 37,50 32,57.8" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<polygon id="dpad_left" points="14,27 14,37 6.2,32" fill="#fff" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<circle id="dpad_thumb" cx="32" cy="32" r="5" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
12
ports/RGSX/assets/images/dpad_right.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="outline_up" d="M22,23.5 L22,6 A4,4 90 0,1 26,2 L38,2 A4,4 90 0,1 42,6 L42,23.5" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<path id="outline_right" d="M40.5,22 L58,22 A4,4 90 0,1 62,26 L62,38 A4,4 90 0,1 58,42 L40.5,42" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<path id="outline_down" d="M22,40.5 L22,58 A4,4 90 0,0 26,62 L38,62 A4,4 90 0,0 42,58 L42,40.5" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<path id="outline_left" d="M23.5,22 L6,22 A4,4 90 0,0 2,26 L2,38 A4,4 90 0,0 6,42 L23.5,42" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<polygon id="dpad_up" points="27,14 37,14 32,6.2" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<polygon id="dpad_right" points="50,27 50,37 57.8,32" fill="#fff" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<polygon id="dpad_down" points="27,50 37,50 32,57.8" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<polygon id="dpad_left" points="14,27 14,37 6.2,32" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<circle id="dpad_thumb" cx="32" cy="32" r="5" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
12
ports/RGSX/assets/images/dpad_up.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="outline_up" d="M22,23.5 L22,6 A4,4 90 0,1 26,2 L38,2 A4,4 90 0,1 42,6 L42,23.5" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<path id="outline_right" d="M40.5,22 L58,22 A4,4 90 0,1 62,26 L62,38 A4,4 90 0,1 58,42 L40.5,42" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<path id="outline_down" d="M22,40.5 L22,58 A4,4 90 0,0 26,62 L38,62 A4,4 90 0,0 42,58 L42,40.5" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<path id="outline_left" d="M23.5,22 L6,22 A4,4 90 0,0 2,26 L2,38 A4,4 90 0,0 6,42 L23.5,42" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="3"/>
|
||||
<polygon id="dpad_up" points="27,14 37,14 32,6.2" fill="#fff" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<polygon id="dpad_right" points="50,27 50,37 57.8,32" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<polygon id="dpad_down" points="27,50 37,50 32,57.8" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<polygon id="dpad_left" points="14,27 14,37 6.2,32" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
<circle id="dpad_thumb" cx="32" cy="32" r="5" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -13,7 +13,7 @@ except Exception:
|
||||
pygame = None # type: ignore
|
||||
|
||||
# Version actuelle de l'application
|
||||
app_version = "2.2.2.5"
|
||||
app_version = "2.2.2.6"
|
||||
|
||||
|
||||
def get_application_root():
|
||||
@@ -78,9 +78,9 @@ OTA_UPDATE_ZIP = os.path.join(OTA_SERVER_URL, "RGSX.zip")
|
||||
OTA_data_ZIP = os.path.join(OTA_SERVER_URL, "games.zip")
|
||||
|
||||
#CHEMINS DES EXECUTABLES
|
||||
UNRAR_EXE = os.path.join(APP_FOLDER,"assets", "unrar.exe")
|
||||
XDVDFS_EXE = os.path.join(APP_FOLDER,"assets", "xdvdfs.exe")
|
||||
XDVDFS_LINUX = os.path.join(APP_FOLDER,"assets", "xdvdfs")
|
||||
UNRAR_EXE = os.path.join(APP_FOLDER,"assets","progs","unrar.exe")
|
||||
XDVDFS_EXE = os.path.join(APP_FOLDER,"assets", "progs", "xdvdfs.exe")
|
||||
XDVDFS_LINUX = os.path.join(APP_FOLDER,"assets", "progs", "xdvdfs")
|
||||
|
||||
if not HEADLESS:
|
||||
# Print des chemins pour debug
|
||||
@@ -191,6 +191,7 @@ trimui_controller = False
|
||||
generic_controller = False
|
||||
xbox_elite_controller = False # Flag spécifique manette Xbox Elite
|
||||
anbernic_rg35xx_controller = False # Flag spécifique Anbernic RG3xxx
|
||||
controller_device_name = "" # Nom exact du joystick détecté (pour auto-préréglages)
|
||||
|
||||
# --- Filtre plateformes (UI) ---
|
||||
selected_filter_index = 0 # index dans la liste visible triée
|
||||
@@ -253,7 +254,7 @@ def init_font():
|
||||
search_size = 48
|
||||
small_size = 28
|
||||
if fam == "pixel":
|
||||
path = os.path.join(APP_FOLDER, "assets", "Pixel-UniCode.ttf")
|
||||
path = os.path.join(APP_FOLDER, "assets", "fonts", "Pixel-UniCode.ttf")
|
||||
f = pygame.font.Font(path, int(base_size * font_scale))
|
||||
t = pygame.font.Font(path, int(title_size * font_scale))
|
||||
s = pygame.font.Font(path, int(search_size * font_scale))
|
||||
|
||||
@@ -2,6 +2,7 @@ import pygame # type: ignore
|
||||
import shutil
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
import config
|
||||
from config import REPEAT_DELAY, REPEAT_INTERVAL, REPEAT_ACTION_DEBOUNCE
|
||||
@@ -96,44 +97,65 @@ def load_controls_config(path=CONTROLS_CONFIG_PATH):
|
||||
|
||||
# 2) Préréglages sans copie si aucun fichier utilisateur
|
||||
try:
|
||||
candidates = []
|
||||
# Si aucun contrôleur détecté, privilégier le préréglage clavier
|
||||
if not getattr(config, 'joystick', False) or getattr(config, 'keyboard', False):
|
||||
candidates.append('keyboard.json')
|
||||
# Déterminer les préréglages disponibles selon les flags détectés au démarrage
|
||||
if getattr(config, 'steam_controller', False):
|
||||
candidates.append('steam_controller.json')
|
||||
if getattr(config, 'trimui_controller', False):
|
||||
candidates.append('trimui_controller.json')
|
||||
if getattr(config, 'xbox_elite_controller', False):
|
||||
candidates.append('xbox_elite_controller.json')
|
||||
elif getattr(config, 'xbox_controller', False):
|
||||
candidates.append('xbox_controller.json')
|
||||
if getattr(config, 'nintendo_controller', False):
|
||||
candidates.append('nintendo_controller.json')
|
||||
if getattr(config, 'eightbitdo_controller', False):
|
||||
candidates.append('8bitdo_controller.json')
|
||||
if getattr(config, 'anbernic_rg35xx_controller', False):
|
||||
candidates.append('anbernic_rg34xx_sp_controller.json')
|
||||
# Fallbacks génériques
|
||||
if 'generic_controller.json' not in candidates:
|
||||
candidates.append('generic_controller.json')
|
||||
if 'xbox_controller.json' not in candidates:
|
||||
candidates.append('xbox_controller.json')
|
||||
# --- Auto-match par nom de périphérique détecté ---
|
||||
def _sanitize(s: str) -> str:
|
||||
s = (s or "").strip().lower()
|
||||
s = re.sub(r"[^a-z0-9]+", "_", s)
|
||||
s = re.sub(r"_+", "_", s).strip("_")
|
||||
return s
|
||||
|
||||
for fname in candidates:
|
||||
src = os.path.join(config.PRECONF_CONTROLS_PATH, fname)
|
||||
def _extract_device_from_comment(val: str) -> str:
|
||||
try:
|
||||
if not isinstance(val, str):
|
||||
return ""
|
||||
# Expect formats like "# Device: NAME" or just NAME
|
||||
if "Device:" in val:
|
||||
part = val.split("Device:", 1)[1]
|
||||
return part.strip().lstrip('#').strip()
|
||||
return val.strip().lstrip('#').strip()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
device_name = getattr(config, 'controller_device_name', '') or ''
|
||||
if getattr(config, 'joystick', False) and device_name:
|
||||
target_norm = _sanitize(device_name)
|
||||
try:
|
||||
for fname in os.listdir(config.PRECONF_CONTROLS_PATH):
|
||||
if not fname.lower().endswith('.json'):
|
||||
continue
|
||||
src = os.path.join(config.PRECONF_CONTROLS_PATH, fname)
|
||||
try:
|
||||
with open(src, 'r', encoding='utf-8') as f:
|
||||
preset = json.load(f)
|
||||
except Exception:
|
||||
continue
|
||||
# Match by explicit device field
|
||||
dev_field = preset.get('device') if isinstance(preset, dict) else None
|
||||
if isinstance(dev_field, str) and _sanitize(dev_field) == target_norm:
|
||||
logging.getLogger(__name__).info(f"Chargement préréglage (device) depuis le fichier: {fname}")
|
||||
print(f"Chargement préréglage (device) depuis le fichier: {fname}")
|
||||
return preset
|
||||
except Exception as e:
|
||||
logging.getLogger(__name__).warning(f"Échec scan préréglages par device: {e}")
|
||||
|
||||
# Fallback préréglage explicite clavier si pas de joystick
|
||||
if not getattr(config, 'joystick', False) or getattr(config, 'keyboard', False):
|
||||
src = os.path.join(config.PRECONF_CONTROLS_PATH, 'keyboard.json')
|
||||
if os.path.exists(src):
|
||||
with open(src, "r", encoding="utf-8") as f:
|
||||
with open(src, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
if isinstance(data, dict) and data:
|
||||
logging.getLogger(__name__).info(f"Chargement des contrôles préréglés: {fname}")
|
||||
logging.getLogger(__name__).info("Chargement des contrôles préréglés: keyboard.json")
|
||||
return data
|
||||
except Exception as e:
|
||||
logging.getLogger(__name__).warning(f"Échec du chargement des contrôles préréglés: {e}")
|
||||
|
||||
# 3) Fallback clavier par défaut
|
||||
logging.getLogger(__name__).info("Aucun fichier utilisateur ou préréglage trouvé, utilisation des contrôles par défaut")
|
||||
# 3) Fallback: si joystick présent mais aucun préréglage trouvé, retourner {} pour déclencher le remap
|
||||
if getattr(config, 'joystick', False):
|
||||
logging.getLogger(__name__).info("Aucun préréglage trouvé pour le joystick connecté, ouverture du remap")
|
||||
return {}
|
||||
# Sinon, fallback clavier par défaut
|
||||
logging.getLogger(__name__).info("Aucun fichier utilisateur ou préréglage trouvé, utilisation des contrôles clavier par défaut")
|
||||
return default_config.copy()
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import pygame # type: ignore
|
||||
import json
|
||||
import os
|
||||
import io
|
||||
import logging
|
||||
import config
|
||||
import language
|
||||
from config import CONTROLS_CONFIG_PATH
|
||||
from display import draw_gradient
|
||||
import xml.etree.ElementTree as ET
|
||||
from collections import OrderedDict
|
||||
from typing import Optional, Tuple
|
||||
|
||||
# Optional: SVG to PNG conversion (if installed)
|
||||
try:
|
||||
import cairosvg # type: ignore
|
||||
except Exception: # pragma: no cover - optional dependency
|
||||
cairosvg = None # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -282,9 +291,77 @@ MOUSE_BUTTON_NAMES = {
|
||||
|
||||
# Durée de maintien pour valider une entrée (en millisecondes)
|
||||
HOLD_DURATION = 1000
|
||||
INPUT_ACCEPT_COOLDOWN = 350 # ms to ignore inputs right after accepting one (avoid axis release bounce)
|
||||
|
||||
JOYHAT_DEBOUNCE = 200 # Délai anti-rebond pour JOYHATMOTION (ms)
|
||||
|
||||
# ---- Icônes: helpers pour charger et afficher des SVG ----
|
||||
_ICON_CACHE: dict[Tuple[str, int], Optional[pygame.Surface]] = {}
|
||||
|
||||
def _images_base_dir() -> str:
|
||||
return os.path.join(os.path.dirname(__file__), "assets", "images")
|
||||
|
||||
def _action_icon_filename(action_name: str) -> Optional[str]:
|
||||
# Map actions to icon filenames present in assets/images
|
||||
mapping = {
|
||||
"up": "dpad_up.svg",
|
||||
"down": "dpad_down.svg",
|
||||
"left": "dpad_left.svg",
|
||||
"right": "dpad_right.svg",
|
||||
"confirm": "buttons_south.svg", # A (south)
|
||||
"cancel": "buttons_east.svg", # B (east)
|
||||
"clear_history": "buttons_west.svg", # X (west)
|
||||
"history": "buttons_north.svg", # Y (north)
|
||||
"start": "button_start.svg",
|
||||
"filter": "button_select.svg",
|
||||
"delete": "button_l.svg", # LB
|
||||
"space": "button_r.svg", # RB
|
||||
"page_up": "button_lt.svg",
|
||||
"page_down": "button_rt.svg",
|
||||
}
|
||||
return mapping.get(action_name)
|
||||
|
||||
def _load_svg_icon_surface(svg_path: str, size: int) -> Optional[pygame.Surface]:
|
||||
# Try to load SVG via cairosvg; fallback: let pygame try to load (only if supported)
|
||||
try:
|
||||
if cairosvg is not None:
|
||||
with open(svg_path, "rb") as f:
|
||||
svg_bytes = f.read()
|
||||
png_bytes = cairosvg.svg2png(bytestring=svg_bytes, output_width=size, output_height=size)
|
||||
return pygame.image.load(io.BytesIO(png_bytes), "icon.png").convert_alpha()
|
||||
else:
|
||||
# Some pygame builds may support SVG; try directly.
|
||||
surf = pygame.image.load(svg_path)
|
||||
# Scale to requested size while keeping aspect ratio
|
||||
w, h = surf.get_size()
|
||||
if w != size or h != size:
|
||||
# uniform scale to fit in size x size
|
||||
scale = min(size / max(w, 1), size / max(h, 1))
|
||||
new_w = max(1, int(w * scale))
|
||||
new_h = max(1, int(h * scale))
|
||||
surf = pygame.transform.smoothscale(surf, (new_w, new_h))
|
||||
return surf.convert_alpha()
|
||||
except Exception as e:
|
||||
logger.debug(f"Icon load failed for {svg_path}: {e}")
|
||||
return None
|
||||
|
||||
def get_action_icon_surface(action_name: str, size: int) -> Optional[pygame.Surface]:
|
||||
key = (action_name, size)
|
||||
if key in _ICON_CACHE:
|
||||
return _ICON_CACHE[key]
|
||||
filename = _action_icon_filename(action_name)
|
||||
if not filename:
|
||||
_ICON_CACHE[key] = None
|
||||
return None
|
||||
full_path = os.path.join(_images_base_dir(), filename)
|
||||
if not os.path.exists(full_path):
|
||||
logger.debug(f"Icon file not found: {full_path}")
|
||||
_ICON_CACHE[key] = None
|
||||
return None
|
||||
surf = _load_svg_icon_surface(full_path, size)
|
||||
_ICON_CACHE[key] = surf
|
||||
return surf
|
||||
|
||||
def load_controls_config(path=CONTROLS_CONFIG_PATH):
|
||||
"""Charge la configuration des contrôles depuis controls.json"""
|
||||
try:
|
||||
@@ -347,9 +424,64 @@ def get_readable_input_name(event):
|
||||
return "Inconnu"
|
||||
|
||||
|
||||
def get_preferred_display_for_action(action_name: str, input_type: str, input_value):
|
||||
"""Retourne un libellé display standardisé pour correspondre à controller_debug.
|
||||
|
||||
Règles:
|
||||
- Pour les actions manette, on force un libellé stable (A/B/X/Y, LB/RB, LT/RT, Start/Select, flèches).
|
||||
- Pour le clavier, on conserve le nom lisible de la touche.
|
||||
- Pour le D-Pad et axes directionnels, on affiche des flèches.
|
||||
"""
|
||||
# Clavier: garder la touche lisible
|
||||
if input_type == "key":
|
||||
try:
|
||||
key_value = int(input_value)
|
||||
except Exception:
|
||||
key_value = input_value
|
||||
key_value = SDL_TO_PYGAME_KEY.get(key_value, key_value)
|
||||
return KEY_NAMES.get(key_value, pygame.key.name(key_value) or f"Touche {key_value}")
|
||||
|
||||
# Mapping stable par action (manette)
|
||||
action_display = {
|
||||
"confirm": "A",
|
||||
"cancel": "B",
|
||||
"clear_history": "X",
|
||||
"history": "Y",
|
||||
"start": "Start",
|
||||
"filter": "Select",
|
||||
"delete": "LB",
|
||||
"space": "RB",
|
||||
"page_up": "LT",
|
||||
"page_down": "RT",
|
||||
"up": "↑",
|
||||
"down": "↓",
|
||||
"left": "←",
|
||||
"right": "→",
|
||||
}
|
||||
|
||||
# Directions: flèches (peu importe hat/axis/button)
|
||||
if action_name in ("up", "down", "left", "right"):
|
||||
return action_display[action_name]
|
||||
|
||||
# Autres actions: renvoyer le libellé normalisé ci-dessus
|
||||
if action_name in action_display:
|
||||
return action_display[action_name]
|
||||
|
||||
# Fallback: si on ne sait pas, retourner chaîne vide (appelant fera un secours)
|
||||
return ""
|
||||
|
||||
|
||||
def map_controls(screen):
|
||||
"""Interface de mappage des contrôles avec maintien de 3 secondes"""
|
||||
controls_config = load_controls_config()
|
||||
# Construire un objet ordonné pour forcer l'ordre des clés dans le JSON final
|
||||
# Placer "device" en premier si disponible
|
||||
controls_config = OrderedDict()
|
||||
try:
|
||||
device_name = getattr(config, "controller_device_name", "") or ""
|
||||
if device_name:
|
||||
controls_config["device"] = device_name
|
||||
except Exception:
|
||||
pass
|
||||
current_action_index = 0
|
||||
current_input = None
|
||||
input_held_time = 0
|
||||
@@ -357,6 +489,7 @@ def map_controls(screen):
|
||||
last_frame_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
last_joyhat_time = 0
|
||||
next_input_allowed_time = 0 # timestamp until which new inputs are ignored after accept
|
||||
|
||||
# État des entrées maintenues
|
||||
held_keys = set()
|
||||
@@ -424,6 +557,9 @@ def map_controls(screen):
|
||||
|
||||
# Détection des nouvelles entrées
|
||||
if event.type in (pygame.KEYDOWN, pygame.JOYBUTTONDOWN, pygame.JOYAXISMOTION, pygame.JOYHATMOTION, pygame.MOUSEBUTTONDOWN):
|
||||
# Ignorer les événements pendant un court délai après validation d'un mapping
|
||||
if current_time < next_input_allowed_time:
|
||||
continue
|
||||
if event.type == pygame.JOYHATMOTION:
|
||||
if (current_time - last_joyhat_time) < JOYHAT_DEBOUNCE:
|
||||
continue
|
||||
@@ -485,36 +621,41 @@ def map_controls(screen):
|
||||
|
||||
# Sauvegarder avec la structure attendue par controls.py
|
||||
if current_input["type"] == "key":
|
||||
disp = get_preferred_display_for_action(action_name, "key", current_input["value"]) or last_input_name
|
||||
controls_config[action_name] = {
|
||||
"type": "key",
|
||||
"key": current_input["value"],
|
||||
"display": last_input_name
|
||||
"display": disp
|
||||
}
|
||||
elif current_input["type"] == "button":
|
||||
disp = get_preferred_display_for_action(action_name, "button", current_input["value"]) or last_input_name
|
||||
controls_config[action_name] = {
|
||||
"type": "button",
|
||||
"button": current_input["value"],
|
||||
"display": last_input_name
|
||||
"display": disp
|
||||
}
|
||||
elif current_input["type"] == "axis":
|
||||
axis, direction = current_input["value"]
|
||||
disp = get_preferred_display_for_action(action_name, "axis", (axis, direction)) or last_input_name
|
||||
controls_config[action_name] = {
|
||||
"type": "axis",
|
||||
"axis": axis,
|
||||
"direction": direction,
|
||||
"display": last_input_name
|
||||
"display": disp
|
||||
}
|
||||
elif current_input["type"] == "hat":
|
||||
disp = get_preferred_display_for_action(action_name, "hat", current_input["value"]) or last_input_name
|
||||
controls_config[action_name] = {
|
||||
"type": "hat",
|
||||
"value": current_input["value"],
|
||||
"display": last_input_name
|
||||
"display": disp
|
||||
}
|
||||
elif current_input["type"] == "mouse":
|
||||
disp = get_preferred_display_for_action(action_name, "mouse", current_input["value"]) or last_input_name
|
||||
controls_config[action_name] = {
|
||||
"type": "mouse",
|
||||
"button": current_input["value"],
|
||||
"display": last_input_name
|
||||
"display": disp
|
||||
}
|
||||
|
||||
logger.debug(f"Contrôle mappé: {action_name} -> {controls_config[action_name]}")
|
||||
@@ -523,6 +664,8 @@ def map_controls(screen):
|
||||
input_held_time = 0
|
||||
last_input_name = None
|
||||
config.needs_redraw = True
|
||||
# Activer un court délai pour ignorer les rebonds (ex: relâchement d'un axe)
|
||||
next_input_allowed_time = pygame.time.get_ticks() + INPUT_ACCEPT_COOLDOWN
|
||||
|
||||
# Réinitialiser les entrées maintenues
|
||||
held_keys.clear()
|
||||
@@ -542,7 +685,7 @@ def map_controls(screen):
|
||||
|
||||
|
||||
def draw_controls_mapping(screen, action, last_input, waiting_for_input, hold_progress):
|
||||
#Affiche l'interface de mappage des contrôles avec une barre de progression pour le maintien
|
||||
# Affiche l'interface de mappage des contrôles avec une barre de progression pour le maintien
|
||||
draw_gradient(screen, (28, 37, 38), (47, 59, 61))
|
||||
|
||||
# Paramètres de l'interface
|
||||
@@ -552,6 +695,8 @@ def draw_controls_mapping(screen, action, last_input, waiting_for_input, hold_pr
|
||||
border_radius = 24
|
||||
border_width = 4
|
||||
shadow_offset = 8
|
||||
bar_height = 25
|
||||
min_bar_inner_width = 200 # largeur minimale utile de la barre
|
||||
|
||||
# Titre principal (traduction)
|
||||
title_text = language.get_text("controls_mapping_title", "Configuration des contrôles")
|
||||
@@ -559,6 +704,10 @@ def draw_controls_mapping(screen, action, last_input, waiting_for_input, hold_pr
|
||||
title_rect = title_surface.get_rect(center=(config.screen_width // 2, 80))
|
||||
screen.blit(title_surface, title_rect)
|
||||
|
||||
# Icône de l'action courante (facultatif si dépendances disponibles)
|
||||
icon_size = 72 # px
|
||||
icon_surface = get_action_icon_surface(action.get('name', ''), icon_size)
|
||||
|
||||
# Instructions (traduction)
|
||||
instruction_text = language.get_text("controls_mapping_instruction", "Maintenez pendant 3s pour configurer :")
|
||||
description_text = action.get('description', '')
|
||||
@@ -574,11 +723,22 @@ def draw_controls_mapping(screen, action, last_input, waiting_for_input, hold_pr
|
||||
input_surface = config.small_font.render(input_text, True, (0, 255, 0) if last_input else (255, 255, 255))
|
||||
input_width, input_height = input_surface.get_size()
|
||||
|
||||
# Dimensions de la popup
|
||||
text_width = max(instruction_width, description_width, input_width)
|
||||
text_height = instruction_height + description_height + input_height + 2 * padding_between
|
||||
popup_width = text_width + 2 * padding_horizontal
|
||||
popup_height = text_height + 40 + 2 * padding_vertical # +40 pour la barre de progression
|
||||
# Dimensions de la popup (s'adapte au contenu et inclut la barre)
|
||||
icon_width, icon_height = (icon_surface.get_size() if icon_surface else (0, 0))
|
||||
inner_text_width = max(instruction_width, description_width, input_width, min_bar_inner_width, icon_width)
|
||||
inner_text_height = 0
|
||||
if icon_surface:
|
||||
inner_text_height += icon_height + padding_between
|
||||
inner_text_height += instruction_height + description_height + input_height + 2 * padding_between
|
||||
inner_width = inner_text_width
|
||||
inner_height = inner_text_height + padding_between + bar_height
|
||||
|
||||
popup_width = inner_width + 2 * padding_horizontal
|
||||
# Eviter de dépasser l'écran (marge de 20px de chaque côté)
|
||||
popup_width = min(popup_width, config.screen_width - 40)
|
||||
popup_height = inner_height + 2 * padding_vertical
|
||||
popup_height = min(popup_height, config.screen_height - 40)
|
||||
|
||||
popup_x = (config.screen_width - popup_width) // 2
|
||||
popup_y = (config.screen_height - popup_height) // 2
|
||||
|
||||
@@ -597,31 +757,40 @@ def draw_controls_mapping(screen, action, last_input, waiting_for_input, hold_pr
|
||||
# Bordure blanche
|
||||
pygame.draw.rect(screen, (255, 255, 255), popup_rect, border_width, border_radius=border_radius)
|
||||
|
||||
# Afficher les textes
|
||||
# Afficher les textes (centrés dans la popup)
|
||||
center_x = popup_x + popup_width // 2
|
||||
start_y = popup_y + padding_vertical
|
||||
instruction_rect = instruction_surface.get_rect(center=(config.screen_width // 2, start_y + instruction_height // 2))
|
||||
# Icône en premier (si dispo)
|
||||
if icon_surface:
|
||||
icon_rect = icon_surface.get_rect(center=(center_x, start_y + icon_height // 2))
|
||||
screen.blit(icon_surface, icon_rect)
|
||||
start_y += icon_height + padding_between
|
||||
instruction_rect = instruction_surface.get_rect(center=(center_x, start_y + instruction_height // 2))
|
||||
screen.blit(instruction_surface, instruction_rect)
|
||||
start_y += instruction_height + padding_between
|
||||
description_rect = description_surface.get_rect(center=(config.screen_width // 2, start_y + description_height // 2))
|
||||
|
||||
description_rect = description_surface.get_rect(center=(center_x, start_y + description_height // 2))
|
||||
screen.blit(description_surface, description_rect)
|
||||
start_y += description_height + padding_between
|
||||
input_rect = input_surface.get_rect(center=(config.screen_width // 2, start_y + input_height // 2))
|
||||
|
||||
input_rect = input_surface.get_rect(center=(center_x, start_y + input_height // 2))
|
||||
screen.blit(input_surface, input_rect)
|
||||
start_y += input_height + padding_between
|
||||
|
||||
# Barre de progression pour le maintien
|
||||
bar_width = 300
|
||||
bar_height = 25
|
||||
bar_x = (config.screen_width - bar_width) // 2
|
||||
bar_y = start_y + 20
|
||||
pygame.draw.rect(screen, (50, 50, 50), (bar_x, bar_y, bar_width, bar_height))
|
||||
progress_width = bar_width * hold_progress
|
||||
pygame.draw.rect(screen, (0, 255, 0), (bar_x, bar_y, progress_width, bar_height))
|
||||
# Barre de progression pour le maintien (adaptée à la largeur intérieure de la popup)
|
||||
bar_x = popup_x + padding_horizontal
|
||||
bar_y = start_y
|
||||
bar_width = popup_width - 2 * padding_horizontal
|
||||
|
||||
pygame.draw.rect(screen, (50, 50, 50), (bar_x, bar_y, bar_width, bar_height))
|
||||
progress_width = int(bar_width * max(0.0, min(1.0, hold_progress)))
|
||||
if progress_width > 0:
|
||||
pygame.draw.rect(screen, (0, 255, 0), (bar_x, bar_y, progress_width, bar_height))
|
||||
pygame.draw.rect(screen, (255, 255, 255), (bar_x, bar_y, bar_width, bar_height), 2)
|
||||
|
||||
# Afficher le pourcentage de progression
|
||||
|
||||
# Pourcentage de progression (affiché au centre de la barre)
|
||||
if hold_progress > 0:
|
||||
progress_text = f"{int(hold_progress * 100)}%"
|
||||
progress_surface = config.small_font.render(progress_text, True, (255, 255, 255))
|
||||
progress_rect = progress_surface.get_rect(center=(config.screen_width // 2, bar_y + bar_height + 30))
|
||||
progress_rect = progress_surface.get_rect(center=(bar_x + bar_width // 2, bar_y + bar_height // 2))
|
||||
screen.blit(progress_surface, progress_rect)
|
||||
@@ -1,4 +1,8 @@
|
||||
|
||||
|
||||
import pygame # type: ignore
|
||||
import os
|
||||
import io
|
||||
import config
|
||||
from utils import truncate_text_middle, wrap_text, load_system_image, truncate_text_end
|
||||
import logging
|
||||
@@ -10,6 +14,145 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
OVERLAY = None # Initialisé dans init_display()
|
||||
|
||||
# --- Helpers: SVG icons for controls (local cache, optional cairosvg) ---
|
||||
_HELP_ICON_CACHE = {}
|
||||
|
||||
def _images_base_dir():
|
||||
try:
|
||||
base_dir = os.path.join(os.path.dirname(__file__), "assets", "images")
|
||||
except Exception:
|
||||
base_dir = "assets/images"
|
||||
return base_dir
|
||||
|
||||
def _action_icon_filename(action_name: str):
|
||||
mapping = {
|
||||
"up": "dpad_up.svg",
|
||||
"down": "dpad_down.svg",
|
||||
"left": "dpad_left.svg",
|
||||
"right": "dpad_right.svg",
|
||||
"confirm": "buttons_south.svg",
|
||||
"cancel": "buttons_east.svg",
|
||||
"clear_history": "buttons_west.svg",
|
||||
"history": "buttons_north.svg",
|
||||
"start": "button_start.svg",
|
||||
"filter": "button_select.svg",
|
||||
"delete": "button_l.svg",
|
||||
"space": "button_r.svg",
|
||||
"page_up": "button_lt.svg",
|
||||
"page_down": "button_rt.svg",
|
||||
}
|
||||
return mapping.get(action_name)
|
||||
|
||||
def _load_svg_icon_surface(svg_path: str, size: int):
|
||||
try:
|
||||
# Prefer cairosvg if available for crisp rasterization
|
||||
try:
|
||||
import cairosvg # type: ignore
|
||||
except Exception:
|
||||
cairosvg = None # type: ignore
|
||||
if cairosvg is not None:
|
||||
with open(svg_path, "rb") as f:
|
||||
svg_bytes = f.read()
|
||||
png_bytes = cairosvg.svg2png(bytestring=svg_bytes, output_width=size, output_height=size)
|
||||
return pygame.image.load(io.BytesIO(png_bytes), "icon.png").convert_alpha()
|
||||
# Fallback: try direct load (works if SDL_image has SVG support)
|
||||
surf = pygame.image.load(svg_path)
|
||||
w, h = surf.get_size()
|
||||
if w != size or h != size:
|
||||
scale = min(size / max(w, 1), size / max(h, 1))
|
||||
new_w = max(1, int(w * scale))
|
||||
new_h = max(1, int(h * scale))
|
||||
surf = pygame.transform.smoothscale(surf, (new_w, new_h))
|
||||
return surf.convert_alpha()
|
||||
except Exception as e:
|
||||
try:
|
||||
logger.debug(f"Help icon load failed for {svg_path}: {e}")
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
def get_help_icon_surface(action_name: str, size: int):
|
||||
key = (action_name, size)
|
||||
if key in _HELP_ICON_CACHE:
|
||||
return _HELP_ICON_CACHE[key]
|
||||
filename = _action_icon_filename(action_name)
|
||||
if not filename:
|
||||
_HELP_ICON_CACHE[key] = None
|
||||
return None
|
||||
full_path = os.path.join(_images_base_dir(), filename)
|
||||
if not os.path.exists(full_path):
|
||||
_HELP_ICON_CACHE[key] = None
|
||||
return None
|
||||
surf = _load_svg_icon_surface(full_path, size)
|
||||
_HELP_ICON_CACHE[key] = surf
|
||||
return surf
|
||||
|
||||
def _render_icons_line(actions, text, target_col_width, font, text_color, icon_size=28, icon_gap=8, icon_text_gap=12):
|
||||
"""Compose une ligne avec une rangée d'icônes (actions) et un texte à droite.
|
||||
Renvoie un pygame.Surface prêt à être blité, limité à target_col_width.
|
||||
"""
|
||||
# Charger icônes (ignorer celles manquantes)
|
||||
icon_surfs = []
|
||||
for a in actions:
|
||||
surf = get_help_icon_surface(a, icon_size)
|
||||
if surf is not None:
|
||||
icon_surfs.append(surf)
|
||||
# Si aucune icône, rendre simplement le texte (le layout appelant ajoutera les espacements)
|
||||
if not icon_surfs:
|
||||
try:
|
||||
lines = wrap_text(text, font, target_col_width)
|
||||
except Exception:
|
||||
lines = [text]
|
||||
line_surfs = [font.render(l, True, text_color) for l in lines]
|
||||
width = max((s.get_width() for s in line_surfs), default=1)
|
||||
height = sum(s.get_height() for s in line_surfs) + max(0, (len(line_surfs) - 1)) * 4
|
||||
surf = pygame.Surface((width, height), pygame.SRCALPHA)
|
||||
y = 0
|
||||
for s in line_surfs:
|
||||
surf.blit(s, (0, y))
|
||||
y += s.get_height() + 4
|
||||
return surf
|
||||
|
||||
# Calcul largeur totale des icônes
|
||||
icons_width = sum(s.get_width() for s in icon_surfs) + (len(icon_surfs) - 1) * icon_gap
|
||||
if icons_width + icon_text_gap > target_col_width:
|
||||
scale = (target_col_width - icon_text_gap) / max(1, icons_width)
|
||||
scale = max(0.6, min(1.0, scale))
|
||||
new_icon_surfs = []
|
||||
for s in icon_surfs:
|
||||
new_size = (max(1, int(s.get_width() * scale)), max(1, int(s.get_height() * scale)))
|
||||
new_icon_surfs.append(pygame.transform.smoothscale(s, new_size))
|
||||
icon_surfs = new_icon_surfs
|
||||
icons_width = sum(s.get_width() for s in icon_surfs) + (len(icon_surfs) - 1) * icon_gap
|
||||
|
||||
text_area_width = max(60, target_col_width - icons_width - icon_text_gap)
|
||||
try:
|
||||
lines = wrap_text(text, font, text_area_width)
|
||||
except Exception:
|
||||
lines = [text]
|
||||
line_surfs = [font.render(l, True, text_color) for l in lines]
|
||||
text_block_width = max((s.get_width() for s in line_surfs), default=1)
|
||||
text_block_height = sum(s.get_height() for s in line_surfs) + max(0, (len(line_surfs) - 1)) * 4
|
||||
|
||||
total_width = min(target_col_width, icons_width + icon_text_gap + text_block_width)
|
||||
total_height = max(max((s.get_height() for s in icon_surfs), default=0), text_block_height)
|
||||
surf = pygame.Surface((total_width, total_height), pygame.SRCALPHA)
|
||||
|
||||
x = 0
|
||||
icon_y_center = total_height // 2
|
||||
for idx, s in enumerate(icon_surfs):
|
||||
r = s.get_rect()
|
||||
y = icon_y_center - r.height // 2
|
||||
surf.blit(s, (x, y))
|
||||
x += r.width + (icon_gap if idx < len(icon_surfs) - 1 else 0)
|
||||
|
||||
text_x = x + icon_text_gap
|
||||
y = (total_height - text_block_height) // 2
|
||||
for ls in line_surfs:
|
||||
surf.blit(ls, (text_x, y))
|
||||
y += ls.get_height() + 4
|
||||
return surf
|
||||
|
||||
# Couleurs modernes pour le thème
|
||||
THEME_COLORS = {
|
||||
# Fond des lignes sélectionnées
|
||||
@@ -1270,6 +1413,24 @@ def draw_controls(screen, menu_state, current_music_name=None, music_popup_start
|
||||
start_button = get_control_display('start', 'START')
|
||||
start_text = _("controls_action_start")
|
||||
control_text = f"RGSX v{config.app_version} - {start_button} : {start_text}"
|
||||
|
||||
# Afficher le nom du joystick s'il est détecté
|
||||
try:
|
||||
device_name = getattr(config, 'controller_device_name', '') or ''
|
||||
if device_name:
|
||||
# Utilise la clé i18n si disponible, sinon fallback
|
||||
try:
|
||||
joy_label = _("footer_joystick")
|
||||
except Exception:
|
||||
joy_label = "Joystick: {0}"
|
||||
# Formater si le placeholder {0} est présent
|
||||
if isinstance(joy_label, str) and "{0}" in joy_label:
|
||||
joy_text = joy_label.format(device_name)
|
||||
else:
|
||||
joy_text = f"{joy_label} {device_name}" if joy_label else f"Joystick: {device_name}"
|
||||
control_text += f" | {joy_text}"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Ajouter le nom de la musique si disponible
|
||||
if config.current_music_name and config.music_popup_start_time > 0:
|
||||
@@ -1995,25 +2156,25 @@ def draw_filter_platforms_menu(screen):
|
||||
# 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
|
||||
# Contenu des catégories (avec icônes si disponibles)
|
||||
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')}",
|
||||
("icons", ["up", "down", "left", "right"], f"{get_control_display('up', '↑')} {get_control_display('down', '↓')} {get_control_display('left', '←')} {get_control_display('right', '→')} : {_('controls_navigation')}"),
|
||||
("icons", ["page_up", "page_down"], 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')}",
|
||||
("icons", ["confirm"], f"{get_control_display('confirm', 'A')} : {_('controls_confirm_select')}"),
|
||||
("icons", ["cancel"], f"{get_control_display('cancel', 'B')} : {_('controls_cancel_back')}"),
|
||||
("icons", ["start"], 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')}",
|
||||
("icons", ["history"], f"{get_control_display('history', 'Y')} : {_('controls_action_history')}"),
|
||||
("icons", ["clear_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')}",
|
||||
("icons", ["filter"], f"{get_control_display('filter', 'Select')} : {_('controls_filter_search')}"),
|
||||
("icons", ["delete"], f"{get_control_display('delete', 'Suppr')} : {_('controls_action_delete')}"),
|
||||
("icons", ["space"], f"{get_control_display('space', 'Espace')} : {_('controls_action_space')}"),
|
||||
],
|
||||
}
|
||||
|
||||
@@ -2062,28 +2223,40 @@ def draw_controls_help(screen, previous_state):
|
||||
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())
|
||||
# Deux formats possibles:
|
||||
# - tuple ("icons", [actions], text)
|
||||
# - chaîne texte simple
|
||||
line_surface = None
|
||||
if isinstance(raw_line, tuple) and len(raw_line) >= 3 and raw_line[0] == "icons":
|
||||
_, actions, text = raw_line
|
||||
try:
|
||||
line_surface = _render_icons_line(actions, text, target_col_width, font, THEME_COLORS["text"])
|
||||
except Exception:
|
||||
line_surface = None
|
||||
if line_surface is None:
|
||||
# Fallback: traitement texte comme avant
|
||||
words = str(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())
|
||||
else:
|
||||
wrapped.append((False, line_surface))
|
||||
total_height += line_surface.get_height() + line_spacing
|
||||
max_width = max(max_width, line_surface.get_width())
|
||||
|
||||
total_height += section_spacing # espace après section
|
||||
max_width = max(max_width, sec_surf.get_width())
|
||||
|
||||
@@ -180,16 +180,21 @@
|
||||
,"instruction_settings_api_keys": "Gefundene Premium-API-Schlüssel ansehen"
|
||||
,"controls_desc_confirm": "Bestätigen (z.B. A/Kreuz)"
|
||||
,"controls_desc_cancel": "Abbrechen/Zurück (z.B. B/Kreis)"
|
||||
,"controls_desc_up": "Nach oben navigieren"
|
||||
,"controls_desc_down": "Nach unten navigieren"
|
||||
,"controls_desc_left": "Nach links navigieren"
|
||||
,"controls_desc_right": "Nach rechts navigieren"
|
||||
,"controls_desc_page_up": "Schnell nach oben (z.B. LB/L1)"
|
||||
,"controls_desc_page_down": "Schnell nach unten (z.B. RB/R1)"
|
||||
,"controls_desc_up": "UP ↑"
|
||||
,"controls_desc_down": "DOWN ↓"
|
||||
,"controls_desc_left": "LEFT ←"
|
||||
,"controls_desc_right": "RIGHT →"
|
||||
,"controls_desc_page_up": "Schnell nach oben (z.B. LT/L2)"
|
||||
,"controls_desc_page_down": "Schnell nach unten (z.B. RT/R2)"
|
||||
,"controls_desc_history": "Verlauf öffnen (z.B. Y/Dreieck)"
|
||||
,"controls_desc_clear_history": "Downloads: Mehrfachauswahl / Verlauf: Leeren (z.B. X/Quadrat)"
|
||||
,"controls_desc_filter": "Filtermodus: Öffnen/Bestätigen (z.B. Select)"
|
||||
,"controls_desc_delete": "Filtermodus: Zeichen löschen (z.B. LT/L2)"
|
||||
,"controls_desc_space": "Filtermodus: Leerzeichen hinzufügen (z.B. RT/R2)"
|
||||
,"controls_desc_delete": "Filtermodus: Zeichen löschen (z.B. LB/L1)"
|
||||
,"controls_desc_space": "Filtermodus: Leerzeichen hinzufügen (z.B. RB/R1)"
|
||||
,"controls_desc_start": "Pausenmenü öffnen (z.B. Start)"
|
||||
,"controls_mapping_title": "Steuerungszuordnung"
|
||||
,"controls_mapping_instruction": "Zum Bestätigen gedrückt halten:"
|
||||
,"controls_mapping_waiting": "Warte auf eine Taste oder einen Button..."
|
||||
,"controls_mapping_press": "Drücke eine Taste oder einen Button"
|
||||
,"footer_joystick": "Joystick: {0}"
|
||||
}
|
||||
@@ -180,16 +180,21 @@
|
||||
,"instruction_settings_api_keys": "See detected premium provider API keys"
|
||||
,"controls_desc_confirm": "Confirm (e.g. A/Cross)"
|
||||
,"controls_desc_cancel": "Cancel/Back (e.g. B/Circle)"
|
||||
,"controls_desc_up": "Navigate up"
|
||||
,"controls_desc_down": "Navigate down"
|
||||
,"controls_desc_left": "Navigate left"
|
||||
,"controls_desc_right": "Navigate right"
|
||||
,"controls_desc_page_up": "Fast scroll up (e.g. LB/L1)"
|
||||
,"controls_desc_page_down": "Fast scroll down (e.g. RB/R1)"
|
||||
,"controls_desc_up": "UP ↑"
|
||||
,"controls_desc_down": "DOWN ↓"
|
||||
,"controls_desc_left": "LEFT ←"
|
||||
,"controls_desc_right": "RIGHT →"
|
||||
,"controls_desc_page_up": "Fast scroll up (e.g. LT/L2)"
|
||||
,"controls_desc_page_down": "Fast scroll down (e.g. RT/R2)"
|
||||
,"controls_desc_history": "Open history (e.g. Y/Triangle)"
|
||||
,"controls_desc_clear_history": "Downloads: Multi-select / History: Clear (e.g. X/Square)"
|
||||
,"controls_desc_filter": "Filter mode: Open/Confirm (e.g. Select)"
|
||||
,"controls_desc_delete": "Filter mode: Delete character (e.g. LT/L2)"
|
||||
,"controls_desc_space": "Filter mode: Add space (e.g. RT/R2)"
|
||||
,"controls_desc_delete": "Filter mode: Delete character (e.g. LB/L1)"
|
||||
,"controls_desc_space": "Filter mode: Add space (e.g. RB/R1)"
|
||||
,"controls_desc_start": "Open pause menu (e.g. Start)"
|
||||
,"controls_mapping_title": "Controls mapping"
|
||||
,"controls_mapping_instruction": "Hold to confirm the mapping:"
|
||||
,"controls_mapping_waiting": "Waiting for a key or button..."
|
||||
,"controls_mapping_press": "Press a key or a button"
|
||||
,"footer_joystick": "Joystick: {0}"
|
||||
}
|
||||
@@ -180,16 +180,21 @@
|
||||
,"instruction_settings_api_keys": "Ver claves API premium detectadas"
|
||||
,"controls_desc_confirm": "Confirmar (ej. A/Cruz)"
|
||||
,"controls_desc_cancel": "Cancelar/Volver (ej. B/Círculo)"
|
||||
,"controls_desc_up": "Navegar hacia arriba"
|
||||
,"controls_desc_down": "Navegar hacia abajo"
|
||||
,"controls_desc_left": "Navegar a la izquierda"
|
||||
,"controls_desc_right": "Navegar a la derecha"
|
||||
,"controls_desc_page_up": "Desplazamiento rápido - (ej. LB/L1)"
|
||||
,"controls_desc_page_down": "Desplazamiento rápido + (ej. RB/R1)"
|
||||
,"controls_desc_up": "UP ↑"
|
||||
,"controls_desc_down": "DOWN ↓"
|
||||
,"controls_desc_left": "LEFT ←"
|
||||
,"controls_desc_right": "RIGHT →"
|
||||
,"controls_desc_page_up": "Desplazamiento rápido - (ej. LT/L2)"
|
||||
,"controls_desc_page_down": "Desplazamiento rápido + (ej. RT/R2)"
|
||||
,"controls_desc_history": "Abrir historial (ej. Y/Triángulo)"
|
||||
,"controls_desc_clear_history": "Descargas: Selección múltiple / Historial: Limpiar (ej. X/Cuadrado)"
|
||||
,"controls_desc_filter": "Modo filtro: Abrir/Confirmar (ej. Select)"
|
||||
,"controls_desc_delete": "Modo filtro: Eliminar carácter (ej. LT/L2)"
|
||||
,"controls_desc_space": "Modo filtro: Añadir espacio (ej. RT/R2)"
|
||||
,"controls_desc_delete": "Modo filtro: Eliminar carácter (ej. LB/L1)"
|
||||
,"controls_desc_space": "Modo filtro: Añadir espacio (ej. RB/R1)"
|
||||
,"controls_desc_start": "Abrir menú pausa (ej. Start)"
|
||||
,"controls_mapping_title": "Asignación de controles"
|
||||
,"controls_mapping_instruction": "Mantén para confirmar la asignación:"
|
||||
,"controls_mapping_waiting": "Esperando una tecla o botón..."
|
||||
,"controls_mapping_press": "Pulsa una tecla o un botón"
|
||||
,"footer_joystick": "Joystick: {0}"
|
||||
}
|
||||
@@ -180,16 +180,21 @@
|
||||
,"instruction_settings_api_keys": "Voir les clés API détectées des services premium"
|
||||
,"controls_desc_confirm": "Valider (ex: A/Croix)"
|
||||
,"controls_desc_cancel": "Annuler/Retour (ex: B/Rond)"
|
||||
,"controls_desc_up": "Naviguer vers le haut"
|
||||
,"controls_desc_down": "Naviguer vers le bas"
|
||||
,"controls_desc_left": "Naviguer à gauche"
|
||||
,"controls_desc_right": "Naviguer à droite"
|
||||
,"controls_desc_page_up": "Défilement Rapide - (ex: LB/L1)"
|
||||
,"controls_desc_page_down": "Défilement Rapide + (ex: RB/R1)"
|
||||
,"controls_desc_up": "UP ↑"
|
||||
,"controls_desc_down": "DOWN ↓"
|
||||
,"controls_desc_left": "LEFT ←"
|
||||
,"controls_desc_right": "RIGHT →"
|
||||
,"controls_desc_page_up": "Défilement Rapide - (ex: LT/L2)"
|
||||
,"controls_desc_page_down": "Défilement Rapide + (ex: RT/R2)"
|
||||
,"controls_desc_history": "Ouvrir l'historique (ex: Y/Triangle)"
|
||||
,"controls_desc_clear_history": "Téléchargements : Sélection multiple / Historique : Vider (ex: X/Carré)"
|
||||
,"controls_desc_filter": "Mode Filtre : Ouvrir/Valider (ex: Select)"
|
||||
,"controls_desc_delete": "Mode Filtre : Supprimer caractère (ex: LT/L2)"
|
||||
,"controls_desc_space": "Mode Filtre : Ajouter espace (ex: RT/R2)"
|
||||
,"controls_desc_delete": "Mode Filtre : Supprimer caractère (ex: LB/L1)"
|
||||
,"controls_desc_space": "Mode Filtre : Ajouter espace (ex: RB/R1)"
|
||||
,"controls_desc_start": "Ouvrir le menu pause (ex: Start)"
|
||||
,"controls_mapping_title": "Configuration des contrôles"
|
||||
,"controls_mapping_instruction": "Maintenez pour confirmer l'association :"
|
||||
,"controls_mapping_waiting": "En attente d'une touche ou d'un bouton..."
|
||||
,"controls_mapping_press": "Appuyez sur une touche ou un bouton"
|
||||
,"footer_joystick": "Joystick : {0}"
|
||||
}
|
||||
@@ -180,16 +180,21 @@
|
||||
,"instruction_settings_api_keys": "Mostrare chiavi API premium rilevate"
|
||||
,"controls_desc_confirm": "Confermare (es. A/Croce)"
|
||||
,"controls_desc_cancel": "Annullare/Indietro (es. B/Cerchio)"
|
||||
,"controls_desc_up": "Navigare verso l'alto"
|
||||
,"controls_desc_down": "Navigare verso il basso"
|
||||
,"controls_desc_left": "Navigare a sinistra"
|
||||
,"controls_desc_right": "Navigare a destra"
|
||||
,"controls_desc_page_up": "Scorrimento rapido su (es. LB/L1)"
|
||||
,"controls_desc_page_down": "Scorrimento rapido giù (es. RB/R1)"
|
||||
,"controls_desc_up": "UP ↑"
|
||||
,"controls_desc_down": "DOWN ↓"
|
||||
,"controls_desc_left": "LEFT ←"
|
||||
,"controls_desc_right": "RIGHT →"
|
||||
,"controls_desc_page_up": "Scorrimento rapido su (es. LT/L2)"
|
||||
,"controls_desc_page_down": "Scorrimento rapido giù (es. RT/R2)"
|
||||
,"controls_desc_history": "Aprire cronologia (es. Y/Triangolo)"
|
||||
,"controls_desc_clear_history": "Download: Selezione multipla / Cronologia: Svuotare (es. X/Quadrato)"
|
||||
,"controls_desc_filter": "Modalità filtro: Aprire/Confermare (es. Select)"
|
||||
,"controls_desc_delete": "Modalità filtro: Eliminare carattere (es. LT/L2)"
|
||||
,"controls_desc_space": "Modalità filtro: Aggiungere spazio (es. RT/R2)"
|
||||
,"controls_desc_delete": "Modalità filtro: Eliminare carattere (es. LB/L1)"
|
||||
,"controls_desc_space": "Modalità filtro: Aggiungere spazio (es. RB/R1)"
|
||||
,"controls_desc_start": "Aprire menu pausa (es. Start)"
|
||||
,"controls_mapping_title": "Mappatura controlli"
|
||||
,"controls_mapping_instruction": "Tieni premuto per confermare l'associazione:"
|
||||
,"controls_mapping_waiting": "In attesa di un tasto o pulsante..."
|
||||
,"controls_mapping_press": "Premi un tasto o un pulsante"
|
||||
,"footer_joystick": "Joystick: {0}"
|
||||
}
|
||||
@@ -180,16 +180,21 @@
|
||||
,"instruction_settings_api_keys": "Ver chaves API premium detectadas"
|
||||
,"controls_desc_confirm": "Confirmar (ex. A/Cruz)"
|
||||
,"controls_desc_cancel": "Cancelar/Voltar (ex. B/Círculo)"
|
||||
,"controls_desc_up": "Navegar para cima"
|
||||
,"controls_desc_down": "Navegar para baixo"
|
||||
,"controls_desc_left": "Navegar para esquerda"
|
||||
,"controls_desc_right": "Navegar para direita"
|
||||
,"controls_desc_page_up": "Rolagem rápida para cima (ex. LB/L1)"
|
||||
,"controls_desc_page_down": "Rolagem rápida para baixo (ex. RB/R1)"
|
||||
,"controls_desc_up": "UP ↑"
|
||||
,"controls_desc_down": "DOWN ↓"
|
||||
,"controls_desc_left": "LEFT ←"
|
||||
,"controls_desc_right": "RIGHT →"
|
||||
,"controls_desc_page_up": "Rolagem rápida para cima (ex. LT/L2)"
|
||||
,"controls_desc_page_down": "Rolagem rápida para baixo (ex. RT/R2)"
|
||||
,"controls_desc_history": "Abrir histórico (ex. Y/Triângulo)"
|
||||
,"controls_desc_clear_history": "Downloads: Seleção múltipla / Histórico: Limpar (ex. X/Quadrado)"
|
||||
,"controls_desc_filter": "Modo filtro: Abrir/Confirmar (ex. Select)"
|
||||
,"controls_desc_delete": "Modo filtro: Deletar caractere (ex. LT/L2)"
|
||||
,"controls_desc_space": "Modo filtro: Adicionar espaço (ex. RT/R2)"
|
||||
,"controls_desc_delete": "Modo filtro: Deletar caractere (ex. LB/L1)"
|
||||
,"controls_desc_space": "Modo filtro: Adicionar espaço (ex. RB/R1)"
|
||||
,"controls_desc_start": "Abrir menu pausa (ex. Start)"
|
||||
,"controls_mapping_title": "Mapeamento de controles"
|
||||
,"controls_mapping_instruction": "Mantenha para confirmar o mapeamento:"
|
||||
,"controls_mapping_waiting": "Aguardando uma tecla ou botão..."
|
||||
,"controls_mapping_press": "Pressione uma tecla ou um botão"
|
||||
,"footer_joystick": "Joystick: {0}"
|
||||
}
|
||||
@@ -2,8 +2,10 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import re
|
||||
import traceback
|
||||
from typing import Any, Dict, Tuple, List
|
||||
from typing import Any, Dict, Tuple, List, Optional
|
||||
|
||||
try:
|
||||
import pygame # type: ignore
|
||||
@@ -234,6 +236,116 @@ def write_log(path: str, mapping: Dict[str, Tuple[str, Any]], device_name: str)
|
||||
log(f"Saved mapping to: {path}")
|
||||
|
||||
|
||||
# --- JSON preset generation ---
|
||||
def sanitize_device_name(name: str) -> str:
|
||||
s = name.strip().lower()
|
||||
# Replace non-alphanumeric with underscore
|
||||
s = re.sub(r"[^a-z0-9]+", "_", s)
|
||||
s = re.sub(r"_+", "_", s).strip("_")
|
||||
return s or "controller"
|
||||
|
||||
|
||||
def to_json_binding(kind: str, data: Any, display: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
||||
if kind == "button" and isinstance(data, int):
|
||||
return {"type": "button", "button": data, **({"display": display} if display else {})}
|
||||
if kind == "hat" and isinstance(data, (tuple, list)) and len(data) == 2:
|
||||
val = list(data)
|
||||
return {"type": "hat", "value": val, **({"display": display} if display else {})}
|
||||
if kind == "axis" and isinstance(data, dict):
|
||||
axis = data.get("axis")
|
||||
direction = data.get("direction")
|
||||
if isinstance(axis, int) and direction in (-1, 1):
|
||||
return {"type": "axis", "axis": axis, "direction": int(direction), **({"display": display} if display else {})}
|
||||
return None
|
||||
|
||||
|
||||
def build_controls_json(mapping: Dict[str, Tuple[str, Any]]) -> Dict[str, Any]:
|
||||
# Map logical prompts to action keys and preferred display labels
|
||||
prompt_map = {
|
||||
"SOUTH_BUTTON - CONFIRM": ("confirm", "A"),
|
||||
"EAST_BUTTON - CANCEL": ("cancel", "B"),
|
||||
"WEST_BUTTON - CLEAR HISTORY / SELECT GAMES": ("clear_history", "X"),
|
||||
"NORTH_BUTTON - HISTORY": ("history", "Y"),
|
||||
"START - PAUSE": ("start", "Start"),
|
||||
"SELECT - FILTER": ("filter", "Select"),
|
||||
"DPAD_UP - MOVE UP": ("up", "↑"),
|
||||
"DPAD_DOWN - MOVE DOWN": ("down", "↓"),
|
||||
"DPAD_LEFT - MOVE LEFT": ("left", "←"),
|
||||
"DPAD_RIGHT - MOVE RIGHT": ("right", "→"),
|
||||
"LEFT_BUMPER - LB/L1 - Delete last char": ("delete", "LB"),
|
||||
"RIGHT_BUMPER - RB/R1 - Add space": ("space", "RB"),
|
||||
# Triggers per prompts: LEFT=page_up, RIGHT=page_down
|
||||
"LEFT_TRIGGER - LT/L2 - Page +": ("page_up", "LT"),
|
||||
"RIGHT_TRIGGER - RT/R2 - Page -": ("page_down", "RT"),
|
||||
# Left stick directions (fallbacks for arrows)
|
||||
"JOYSTICK_LEFT_UP - MOVE UP": ("up", "J↑"),
|
||||
"JOYSTICK_LEFT_DOWN - MOVE DOWN": ("down", "J↓"),
|
||||
"JOYSTICK_LEFT_LEFT - MOVE LEFT": ("left", "J←"),
|
||||
"JOYSTICK_LEFT_RIGHT - MOVE RIGHT": ("right", "J→"),
|
||||
}
|
||||
|
||||
result: Dict[str, Any] = {}
|
||||
|
||||
# First pass: take direct DPAD/face/meta/bumper/trigger bindings
|
||||
for prompt, (action, disp) in prompt_map.items():
|
||||
if prompt not in mapping:
|
||||
continue
|
||||
kind, data = mapping[prompt]
|
||||
if kind in ("ignored", "skipped"):
|
||||
continue
|
||||
# Prefer DPAD over JOYSTICK for directions: handle fallback later
|
||||
if action in ("up", "down", "left", "right"):
|
||||
if prompt.startswith("DPAD_"):
|
||||
b = to_json_binding(kind, data, disp)
|
||||
if b:
|
||||
result[action] = b
|
||||
# Joystick handled as fallback if DPAD missing
|
||||
else:
|
||||
b = to_json_binding(kind, data, disp)
|
||||
if b:
|
||||
result[action] = b
|
||||
|
||||
# Second pass: fallback to joystick directions if arrows missing
|
||||
fallbacks = [
|
||||
("JOYSTICK_LEFT_UP - MOVE UP", "up", "J↑"),
|
||||
("JOYSTICK_LEFT_DOWN - MOVE DOWN", "down", "J↓"),
|
||||
("JOYSTICK_LEFT_LEFT - MOVE LEFT", "left", "J←"),
|
||||
("JOYSTICK_LEFT_RIGHT - MOVE RIGHT", "right", "J→"),
|
||||
]
|
||||
for prompt, action, disp in fallbacks:
|
||||
if action in result:
|
||||
continue
|
||||
if prompt in mapping:
|
||||
kind, data = mapping[prompt]
|
||||
if kind in ("ignored", "skipped"):
|
||||
continue
|
||||
b = to_json_binding(kind, data, disp)
|
||||
if b:
|
||||
result[action] = b
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def write_controls_json(device_name: str, controls: Dict[str, Any]) -> str:
|
||||
"""Write the generated controls preset JSON in the same folder as this script.
|
||||
|
||||
Also embeds a JSON-safe comment with the device name under the _comment key.
|
||||
"""
|
||||
# Same folder as the launched script
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
fname = f"{sanitize_device_name(device_name)}_controller.json"
|
||||
out_path = os.path.join(base_dir, fname)
|
||||
# Include the detected device name for auto-preset matching
|
||||
payload = {"device": device_name}
|
||||
payload.update(controls)
|
||||
try:
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
json.dump(payload, f, ensure_ascii=False, indent=4)
|
||||
return out_path
|
||||
except Exception:
|
||||
return out_path
|
||||
|
||||
|
||||
def main() -> None:
|
||||
init_screen()
|
||||
js = init_joystick()
|
||||
@@ -253,6 +365,13 @@ def main() -> None:
|
||||
|
||||
log_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "controller_mapping.log")
|
||||
write_log(log_path, mapping, js.get_name())
|
||||
# Build and write ready-to-use JSON controls preset
|
||||
controls = build_controls_json(mapping)
|
||||
if controls:
|
||||
out_json = write_controls_json(js.get_name(), controls)
|
||||
log(f"Saved JSON preset to: {out_json}")
|
||||
else:
|
||||
log("No usable inputs captured to build a JSON preset.")
|
||||
log("Done. Press Q or close the window to exit.")
|
||||
|
||||
|
||||
|
||||