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) - [Metadata](#metadata)
- [Screenshots](#screenshots) - [Screenshots](#screenshots)
- [Fonts and Icons](#fonts-and-icons) - [Fonts and Icons](#fonts-and-icons)
- [Sound Effects](#sound-effects)
--- ---
## 📖 Overview ## 📖 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`. - `~/.local/share/PortProtonQT/themes`.
@ -34,6 +35,12 @@ Create a `styles.py` in the theme root. It should define variables or functions
**Example:** **Example:**
```python ```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): def custom_button_style(color1, color2):
return f""" return f"""
QPushButton {{ QPushButton {{
@ -69,3 +76,19 @@ Folder: `images/screenshots/` — place UI screenshots there.
- Icons: `images/icons/*.svg/.png` - 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`. - `~/.local/share/PortProtonQT/themes`.
@ -32,8 +33,14 @@ mkdir -p ~/.local/share/PortProtonQT/themes/my_custom_theme
Создайте `styles.py` в корне темы. В нём определите переменные и/или функции, возвращающие CSS-оформление. Создайте `styles.py` в корне темы. В нём определите переменные и/или функции, возвращающие CSS-оформление.
**Пример функции:** **Пример:**
```python ```python
# Карта звуковых эффектов
SOUNDS = {
"app_start": "app_start.wav", # Запуск приложения
"app_exit": "app_exit.wav", # Закрытие приложения
}
def custom_button_style(color1, color2): def custom_button_style(color1, color2):
return f""" return f"""
QPushButton {{ QPushButton {{
@ -69,3 +76,19 @@ description = Описание вашей темы.
- Иконки: `images/icons/*.svg/.png` - Иконки: `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) self.resize(width, height)
else: else:
self.showNormal() 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) @Slot(list)
def on_games_loaded(self, games: list[tuple]): def on_games_loaded(self, games: list[tuple]):
self.games = games self.games = games
@ -2253,5 +2260,11 @@ class MainWindow(QMainWindow):
self.checkProcessTimer.deleteLater() self.checkProcessTimer.deleteLater()
self.checkProcessTimer = None self.checkProcessTimer = None
QApplication.quit() # Воспроизводим звук закрытия
if self._exit_sound:
self._exit_sound.play()
# Небольшая задержка перед закрытием, чтобы звук успел проиграться
QTimer.singleShot(100, lambda: event.accept())
event.ignore()
else:
event.accept() event.accept()

View File

@ -3,6 +3,8 @@ import os
from portprotonqt.logger import get_logger from portprotonqt.logger import get_logger
from PySide6.QtSvg import QSvgRenderer from PySide6.QtSvg import QSvgRenderer
from PySide6.QtGui import QIcon, QColor, QFontDatabase, QPixmap, QPainter 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 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): 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 return QIcon() if not as_path else None
if as_path: if as_path:
@ -284,3 +286,67 @@ class ThemeManager:
break break
return image_path 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 favoriteLabelSize = 48, 48
pixmapsScaledSize = 60, 60 pixmapsScaledSize = 60, 60
# ЗВУКОВЫЕ ЭФФЕКТЫ
SOUNDS = {
"app_start": "app_start.wav", # Запуск приложения
"app_exit": "app_exit.wav", # Закрытие приложения
}
GAME_CARD_ANIMATION = { GAME_CARD_ANIMATION = {
# Ширина обводки карточки в состоянии покоя (без наведения или фокуса). # Ширина обводки карточки в состоянии покоя (без наведения или фокуса).
# Влияет на толщину рамки вокруг карточки, когда она не выделена. # Влияет на толщину рамки вокруг карточки, когда она не выделена.

View File

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