feat: added sound effects support to themes

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
2025-07-16 11:10:31 +05:00
parent f0df1f89be
commit 889ba4af2b
6 changed files with 143 additions and 6 deletions

View File

@ -9,12 +9,13 @@
- [Metadata](#metadata)
- [Screenshots](#screenshots)
- [Fonts and Icons](#fonts-and-icons)
- [Sound Effects](#sound-effects)
---
## 📖 Overview
Themes in `PortProtonQT` allow customizing the UI appearance. Themes are stored under:
Themes in `PortProtonQT` allow customizing the UI appearance and sounds. Themes are stored under:
- `~/.local/share/PortProtonQT/themes`.
@ -34,6 +35,12 @@ Create a `styles.py` in the theme root. It should define variables or functions
**Example:**
```python
# Sound effects mapping
SOUNDS = {
"app_start": "app_start.wav", # Application startup
"app_exit": "app_exit.wav", # Application exit
}
def custom_button_style(color1, color2):
return f"""
QPushButton {{
@ -69,3 +76,19 @@ Folder: `images/screenshots/` — place UI screenshots there.
- Icons: `images/icons/*.svg/.png`
---
## 🔊 Sound Effects (optional)
Folder: `sounds/` — place interface sound effects here.
Supported formats:
- `.wav` - Wave audio files
Available sound events:
- Interface sounds:
- `app_start.wav` - Application startup
- `app_exit.wav` - Application exit
If a sound file is missing in a custom theme, the default sound from the standard theme will be used.
---

View File

@ -9,12 +9,13 @@
- [Метаинформация](#метаинформация)
- [Скриншоты](#скриншоты)
- [Шрифты и иконки](#шрифты-и-иконки)
- [Звуковые эффекты](#звуковые-эффекты)
---
## 📖 Обзор
Темы в `PortProtonQT` позволяют изменить внешний вид интерфейса. Все темы хранятся в папке:
Темы в `PortProtonQT` позволяют изменить внешний вид интерфейса и звуковое оформление. Все темы хранятся в папке:
- `~/.local/share/PortProtonQT/themes`.
@ -32,8 +33,14 @@ mkdir -p ~/.local/share/PortProtonQT/themes/my_custom_theme
Создайте `styles.py` в корне темы. В нём определите переменные и/или функции, возвращающие CSS-оформление.
**Пример функции:**
**Пример:**
```python
# Карта звуковых эффектов
SOUNDS = {
"app_start": "app_start.wav", # Запуск приложения
"app_exit": "app_exit.wav", # Закрытие приложения
}
def custom_button_style(color1, color2):
return f"""
QPushButton {{
@ -69,3 +76,19 @@ description = Описание вашей темы.
- Иконки: `images/icons/*.svg/.png`
---
## 🔊 Звуковые эффекты (опционально)
Папка: `sounds/` — звуковые эффекты интерфейса.
Поддерживаемые форматы:
- `.wav` - Wave аудио файлы
Доступные звуковые события:
- Звуки интерфейса:
- `app_start.wav` - Запуск приложения
- `app_exit.wav` - Закрытие приложения
Если звуковой файл отсутствует в пользовательской теме, будет использован звук из стандартной темы.
---

View File

@ -220,6 +220,13 @@ class MainWindow(QMainWindow):
self.resize(width, height)
else:
self.showNormal()
self._startup_sound = self.theme_manager.get_sound(default_styles.SOUNDS["app_start"])
self._exit_sound = self.theme_manager.get_sound(default_styles.SOUNDS["app_exit"])
if self._startup_sound:
self._startup_sound.play()
@Slot(list)
def on_games_loaded(self, games: list[tuple]):
self.games = games
@ -2253,5 +2260,11 @@ class MainWindow(QMainWindow):
self.checkProcessTimer.deleteLater()
self.checkProcessTimer = None
QApplication.quit()
event.accept()
# Воспроизводим звук закрытия
if self._exit_sound:
self._exit_sound.play()
# Небольшая задержка перед закрытием, чтобы звук успел проиграться
QTimer.singleShot(100, lambda: event.accept())
event.ignore()
else:
event.accept()

View File

@ -3,6 +3,8 @@ import os
from portprotonqt.logger import get_logger
from PySide6.QtSvg import QSvgRenderer
from PySide6.QtGui import QIcon, QColor, QFontDatabase, QPixmap, QPainter
from PySide6.QtCore import QUrl
from PySide6.QtMultimedia import QSoundEffect
from portprotonqt.config_utils import save_theme_to_config, load_theme_metainfo
@ -226,7 +228,7 @@ class ThemeManager:
# Если иконка всё равно не найдена
if not icon_path or not os.path.exists(icon_path):
logger.error(f"Предупреждение: иконка '{icon_name}' не найдена")
logger.warning(f"icon '{icon_name}' not found")
return QIcon() if not as_path else None
if as_path:
@ -284,3 +286,67 @@ class ThemeManager:
break
return image_path
def get_sound(self, sound_name, theme_name=None):
"""
Возвращает QSoundEffect для звука из папки sounds текущей темы.
Если не найден, проверяет стандартную тему.
Поддерживает форматы .wav
:param sound_name: Имя звукового файла (с расширением или без)
:param theme_name: Имя темы (опционально)
:return: QSoundEffect объект или None если звук не найден
"""
sound_path = None
theme_name = theme_name or self.current_theme_name
supported_extensions = ['.wav']
has_extension = any(sound_name.lower().endswith(ext) for ext in supported_extensions)
base_name = sound_name if has_extension else sound_name
# Поиск звука в папке текущей темы
for themes_dir in THEMES_DIRS:
theme_folder = os.path.join(str(themes_dir), str(theme_name))
sounds_folder = os.path.join(theme_folder, "sounds")
# Если передано имя с расширением, проверяем только этот файл
if has_extension:
candidate = os.path.join(sounds_folder, str(base_name))
if os.path.exists(candidate):
sound_path = candidate
break
else:
# Проверяем все поддерживаемые расширения
for ext in supported_extensions:
candidate = os.path.join(sounds_folder, str(base_name) + str(ext))
if os.path.exists(candidate):
sound_path = candidate
break
if sound_path:
break
# Если не нашли используем стандартную тему
if not sound_path:
base_dir = os.path.dirname(os.path.abspath(__file__))
standard_sounds_folder = os.path.join(base_dir, "themes", "standart", "sounds")
# Аналогично проверяем в стандартной теме
if has_extension:
sound_path = os.path.join(standard_sounds_folder, base_name)
if not os.path.exists(sound_path):
sound_path = None
else:
for ext in supported_extensions:
candidate = os.path.join(standard_sounds_folder, base_name + ext)
if os.path.exists(candidate):
sound_path = candidate
break
# Если звук всё равно не найден
if not sound_path or not os.path.exists(sound_path):
logger.warning(f"Sound '{sound_name}' not found")
return None
sound = QSoundEffect()
sound.setSource(QUrl.fromLocalFile(sound_path))
return sound

View File

@ -8,6 +8,12 @@ current_theme_name = read_theme_from_config()
favoriteLabelSize = 48, 48
pixmapsScaledSize = 60, 60
# ЗВУКОВЫЕ ЭФФЕКТЫ
SOUNDS = {
"app_start": "app_start.wav", # Запуск приложения
"app_exit": "app_exit.wav", # Закрытие приложения
}
GAME_CARD_ANIMATION = {
# Ширина обводки карточки в состоянии покоя (без наведения или фокуса).
# Влияет на толщину рамки вокруг карточки, когда она не выделена.

View File

@ -8,6 +8,12 @@ current_theme_name = read_theme_from_config()
favoriteLabelSize = 48, 48
pixmapsScaledSize = 60, 60
# ЗВУКОВЫЕ ЭФФЕКТЫ
SOUNDS = {
"app_start": "app_start.wav", # Запуск приложения
"app_exit": "app_exit.wav", # Закрытие приложения
}
# VARS
font_family = "Play"
font_size_a = "16px"