100 lines
3.4 KiB
Python
100 lines
3.4 KiB
Python
"""Сервис загрузки раскладок кнопок из .mapping файлов (TOML-формат).
|
||
|
||
Папка input_mappings/ лежит рядом с исполняемым файлом (или рядом с main.py
|
||
при разработке). Каждый .mapping файл описывает раскладку кнопок для
|
||
конкретного игрового проекта и содержит:
|
||
- [[buttons]] — список кнопок с полями:
|
||
- name (str) — подпись кнопки
|
||
- input_index (int) — номер цифрового входа (0..25)
|
||
- x (int) — столбец на холсте (0..20)
|
||
- y (int) — строка на холсте (-4..4, +Y = вверх)
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
import tomllib
|
||
from dataclasses import dataclass, field
|
||
from pathlib import Path
|
||
|
||
_MAX_X = 20
|
||
_MIN_Y = -4
|
||
_MAX_Y = 4
|
||
|
||
|
||
@dataclass
|
||
class MappingButton:
|
||
"""Одна кнопка в раскладке."""
|
||
name: str
|
||
input_index: int
|
||
x: int
|
||
y: int
|
||
|
||
|
||
@dataclass
|
||
class InputMapping:
|
||
"""Раскладка кнопок для одного игрового проекта."""
|
||
project_name: str
|
||
buttons: list[MappingButton] = field(default_factory=list)
|
||
|
||
|
||
def _get_input_mappings_dir() -> Path:
|
||
"""Определить путь к папке input_mappings/.
|
||
|
||
При запуске из .exe (PyInstaller) — рядом с exe-файлом.
|
||
При запуске из исходников — рядом с корнем проекта.
|
||
"""
|
||
if getattr(sys, "frozen", False):
|
||
base = Path(sys.executable).parent
|
||
else:
|
||
base = Path(__file__).resolve().parent.parent.parent
|
||
return base / "input_mappings"
|
||
|
||
|
||
def load_input_mapping(path: Path) -> InputMapping | None:
|
||
"""Загрузить один .mapping файл. Возвращает None при ошибке."""
|
||
try:
|
||
data = tomllib.loads(path.read_text(encoding="utf-8"))
|
||
except Exception:
|
||
return None
|
||
|
||
buttons: list[MappingButton] = []
|
||
for entry in data.get("buttons", []):
|
||
name = entry.get("name", "")
|
||
input_index = entry.get("input_index")
|
||
x = entry.get("x", 0)
|
||
y = entry.get("y", 0)
|
||
if input_index is None:
|
||
continue
|
||
# Ограничиваем координаты
|
||
x = max(0, min(int(x), _MAX_X))
|
||
y = max(_MIN_Y, min(int(y), _MAX_Y))
|
||
buttons.append(MappingButton(
|
||
name=str(name),
|
||
input_index=int(input_index),
|
||
x=x,
|
||
y=y,
|
||
))
|
||
|
||
project_name = path.stem # имя файла без расширения
|
||
return InputMapping(project_name=project_name, buttons=buttons)
|
||
|
||
|
||
def load_all_input_mappings() -> list[InputMapping]:
|
||
"""Загрузить все .mapping файлы из папки input_mappings/."""
|
||
mappings_dir = _get_input_mappings_dir()
|
||
if not mappings_dir.is_dir():
|
||
return []
|
||
|
||
mappings: list[InputMapping] = []
|
||
for mapping_file in sorted(mappings_dir.glob("*.mapping")):
|
||
mapping = load_input_mapping(mapping_file)
|
||
if mapping is not None:
|
||
mappings.append(mapping)
|
||
return mappings
|
||
|
||
|
||
def get_input_mappings_dir() -> Path:
|
||
"""Публичный доступ к пути папки (для UI «Открыть папку»)."""
|
||
return _get_input_mappings_dir()
|