11 Commits
egs ... main

Author SHA1 Message Date
e9e0bea854 feat: stay overlay on top
All checks were successful
Code and build check / Check code (push) Successful in 1m30s
Code and build check / Build with uv (push) Successful in 50s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-19 19:31:33 +05:00
f7d9f5c150 chore(readme): update todo
All checks were successful
Code and build check / Check code (push) Successful in 1m48s
Code and build check / Build with uv (push) Successful in 55s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-19 18:36:45 +05:00
bcb5987d31 chore(changelog): update
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-19 18:32:50 +05:00
b1aa987e4e fix: ensure application quits on window close
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-19 18:30:24 +05:00
f4c8b70bd0 feat: add --session CLI argument for start gamescope
All checks were successful
Code and build check / Check code (push) Successful in 1m39s
Code and build check / Build with uv (push) Successful in 51s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-18 22:48:24 +05:00
ff960df77c feat: transfer focus to hovered GameCard with mutual exclusivity
All checks were successful
Code and build check / Check code (push) Successful in 1m50s
Code and build check / Build with uv (push) Successful in 54s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-17 23:11:25 +05:00
a57f509295 chore(changelog): update
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-17 23:01:38 +05:00
32bbe89911 fix: enforce mutual exclusivity of hovered and focused states in GameCard
All checks were successful
Code and build check / Check code (push) Successful in 1m40s
Code and build check / Build with uv (push) Successful in 54s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-17 22:58:57 +05:00
593db00166 fix(themes): typo in GAME_CARD_ANIMATION
All checks were successful
Code and build check / Check code (push) Successful in 1m44s
Code and build check / Build with uv (push) Successful in 58s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-17 14:39:21 +05:00
79a78c785b chore(changelog): update
Some checks failed
Code and build check / Check code (push) Failing after 1m43s
Code and build check / Build with uv (push) Successful in 57s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-17 14:26:19 +05:00
0b92d058a9 feat: move GameCard animation properties to styles
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-17 14:24:06 +05:00
9 changed files with 261 additions and 54 deletions

View File

@ -6,12 +6,16 @@
## [Unreleased] ## [Unreleased]
### Added ### Added
- Аргумент `--session` для запуска приложения в gamescope с GAMESCOPE_CMD
### Changed ### Changed
- Удалены сборки для Fedora 40 - Удалены сборки для Fedora 40
- Перенесены параметры анимации GameCard в `styles.py` с подробной документацией для поддержки кастомизации тем.
- Статус выделения и наведения на карточки теперь взаимоисключают друг друга
### Fixed ### Fixed
- Дублирование обводки выделения карточек при быстром перемешении мыши - Дублирование обводки выделения карточек при быстром перемешении мыши
- Завершение приложения при закритие окна
--- ---

View File

@ -4,7 +4,6 @@
<p align="center">Удобный графический интерфейс для управления и запуска игр из PortProton, Steam и Epic Games Store. Оно объединяет библиотеки игр в единый центр для лёгкой навигации и организации. Лёгкая структура и кроссплатформенная поддержка обеспечивают цельный игровой опыт без необходимости использования нескольких лаунчеров. Интеграция с PortProton упрощает запуск Windows-игр на Linux с минимальной настройкой.</p> <p align="center">Удобный графический интерфейс для управления и запуска игр из PortProton, Steam и Epic Games Store. Оно объединяет библиотеки игр в единый центр для лёгкой навигации и организации. Лёгкая структура и кроссплатформенная поддержка обеспечивают цельный игровой опыт без необходимости использования нескольких лаунчеров. Интеграция с PortProton упрощает запуск Windows-игр на Linux с минимальной настройкой.</p>
</div> </div>
## В планах ## В планах
- [X] Адаптировать структуру проекта для поддержки инструментов сборки - [X] Адаптировать структуру проекта для поддержки инструментов сборки
@ -15,7 +14,8 @@
- [X] Вынести все константы, такие как уровень закругления карточек, в темы (частично выполнено) - [X] Вынести все константы, такие как уровень закругления карточек, в темы (частично выполнено)
- [X] Добавить метаданные для тем (скриншоты, описание, домашняя страница и автор) - [X] Добавить метаданные для тем (скриншоты, описание, домашняя страница и автор)
- [ ] Продумать систему вкладок вместо текущей - [ ] Продумать систему вкладок вместо текущей
- [X] [Добавить сессию Gamescope, аналогичную той, что используется в SteamOS](https://git.linux-gaming.ru/Boria138/gamescope-session-portprotonqt) - [ ] [Добавить сессию Gamescope, аналогичную той, что используется в SteamOS](https://git.linux-gaming.ru/Boria138/gamescope-session-portprotonqt)
- [ ] Разобраться почему теряется часть стилей в Gamescope
- [ ] Разработать адаптивный дизайн (за эталон берётся Steam Deck с разрешением 1280×800) - [ ] Разработать адаптивный дизайн (за эталон берётся Steam Deck с разрешением 1280×800)
- [ ] Переделать скриншоты для соответствия [гайдлайнам Flathub](https://docs.flathub.org/docs/for-app-authors/metainfo-guidelines/quality-guidelines#screenshots) - [ ] Переделать скриншоты для соответствия [гайдлайнам Flathub](https://docs.flathub.org/docs/for-app-authors/metainfo-guidelines/quality-guidelines#screenshots)
- [X] Получать описания и названия игр из базы данных Steam - [X] Получать описания и названия игр из базы данных Steam
@ -41,7 +41,10 @@
- [X] Добавить парсинг ярлыков из Steam - [X] Добавить парсинг ярлыков из Steam
- [X] Добавить парсинг ярлыков из EGS (скрыто для переработки) - [X] Добавить парсинг ярлыков из EGS (скрыто для переработки)
- [ ] Избавиться от бинарника legendary - [ ] Избавиться от бинарника legendary
- [ ] Добавить запуск и скачивание игр из EGS - [X] Добавить запуск игр из EGS
- [ ] Добавить скачивание игр из EGS
- [ ] Добавить поддержку запуска сторонних игр из EGS
- [ ] Добавить поддержку запуска игр с EOS
- [ ] Добавить авторизацию в EGS через WebView вместо ручного ввода - [ ] Добавить авторизацию в EGS через WebView вместо ручного ввода
- [X] Получать описания для игр из EGS через их [API](https://store-content.ak.epicgames.com/api) - [X] Получать описания для игр из EGS через их [API](https://store-content.ak.epicgames.com/api)
- [X] Получать slug через GraphQL [запрос](https://launcher.store.epicgames.com/graphql) - [X] Получать slug через GraphQL [запрос](https://launcher.store.epicgames.com/graphql)

View File

@ -1,4 +1,6 @@
import sys import sys
import os
import subprocess
from PySide6.QtCore import QLocale, QTranslator, QLibraryInfo from PySide6.QtCore import QLocale, QTranslator, QLibraryInfo
from PySide6.QtWidgets import QApplication from PySide6.QtWidgets import QApplication
from PySide6.QtGui import QIcon from PySide6.QtGui import QIcon
@ -33,6 +35,13 @@ def main():
window = MainWindow() window = MainWindow()
if args.session:
gamescope_cmd = os.getenv("GAMESCOPE_CMD", "gamescope -f --xwayland-count 2")
cmd = f"{gamescope_cmd} -- portprotonqt"
logger.info(f"Executing: {cmd}")
subprocess.Popen(cmd, shell=True)
sys.exit(0)
if args.fullscreen: if args.fullscreen:
logger.info("Launching in fullscreen mode due to --fullscreen flag") logger.info("Launching in fullscreen mode due to --fullscreen flag")
save_fullscreen_config(True) save_fullscreen_config(True)

View File

@ -13,4 +13,9 @@ def parse_args():
action="store_true", action="store_true",
help="Запустить приложение в полноэкранном режиме и сохранить эту настройку" help="Запустить приложение в полноэкранном режиме и сохранить эту настройку"
) )
parser.add_argument(
"--session",
action="store_true",
help="Запустить приложение с использованием gamescope"
)
return parser.parse_args() return parser.parse_args()

View File

@ -26,6 +26,7 @@ class GameCard(QFrame):
removeFromSteamRequested = Signal(str, str) # name, exec_line removeFromSteamRequested = Signal(str, str) # name, exec_line
openGameFolderRequested = Signal(str, str) # name, exec_line openGameFolderRequested = Signal(str, str) # name, exec_line
hoverChanged = Signal(str, bool) hoverChanged = Signal(str, bool)
focusChanged = Signal(str, bool)
def __init__(self, name, description, cover_path, appid, controller_support, exec_line, def __init__(self, name, description, cover_path, appid, controller_support, exec_line,
last_launch, formatted_playtime, protondb_tier, anticheat_status, last_launch_ts, playtime_seconds, game_source, last_launch, formatted_playtime, protondb_tier, anticheat_status, last_launch_ts, playtime_seconds, game_source,
@ -67,14 +68,14 @@ class GameCard(QFrame):
self.setStyleSheet(self.theme.GAME_CARD_WINDOW_STYLE) self.setStyleSheet(self.theme.GAME_CARD_WINDOW_STYLE)
# Параметры анимации обводки # Параметры анимации обводки
self._borderWidth = 2 self._borderWidth = self.theme.GAME_CARD_ANIMATION["default_border_width"]
self._gradientAngle = 0.0 self._gradientAngle = self.theme.GAME_CARD_ANIMATION["gradient_start_angle"]
self._hovered = False self._hovered = False
self._focused = False self._focused = False
# Анимации # Анимации
self.thickness_anim = QPropertyAnimation(self, QByteArray(b"borderWidth")) self.thickness_anim = QPropertyAnimation(self, QByteArray(b"borderWidth"))
self.thickness_anim.setDuration(300) self.thickness_anim.setDuration(self.theme.GAME_CARD_ANIMATION["thickness_anim_duration"])
self.gradient_anim = None self.gradient_anim = None
self.pulse_anim = None self.pulse_anim = None
@ -448,10 +449,8 @@ class GameCard(QFrame):
if self._hovered or self._focused: if self._hovered or self._focused:
center = self.rect().center() center = self.rect().center()
gradient = QConicalGradient(center, self._gradientAngle) gradient = QConicalGradient(center, self._gradientAngle)
gradient.setColorAt(0, QColor("#00fff5")) for stop in self.theme.GAME_CARD_ANIMATION["gradient_colors"]:
gradient.setColorAt(0.33, QColor("#FF5733")) gradient.setColorAt(stop["position"], QColor(stop["color"]))
gradient.setColorAt(0.66, QColor("#9B59B6"))
gradient.setColorAt(1, QColor("#00fff5"))
pen.setBrush(QBrush(gradient)) pen.setBrush(QBrush(gradient))
else: else:
pen.setColor(QColor(0, 0, 0, 0)) pen.setColor(QColor(0, 0, 0, 0))
@ -468,23 +467,25 @@ class GameCard(QFrame):
if self.pulse_anim: if self.pulse_anim:
self.pulse_anim.stop() self.pulse_anim.stop()
self.pulse_anim = QPropertyAnimation(self, QByteArray(b"borderWidth")) self.pulse_anim = QPropertyAnimation(self, QByteArray(b"borderWidth"))
self.pulse_anim.setDuration(800) self.pulse_anim.setDuration(self.theme.GAME_CARD_ANIMATION["pulse_anim_duration"])
self.pulse_anim.setLoopCount(0) self.pulse_anim.setLoopCount(0)
self.pulse_anim.setKeyValueAt(0, 8) self.pulse_anim.setKeyValueAt(0, self.theme.GAME_CARD_ANIMATION["pulse_min_border_width"])
self.pulse_anim.setKeyValueAt(0.5, 10) self.pulse_anim.setKeyValueAt(0.5, self.theme.GAME_CARD_ANIMATION["pulse_max_border_width"])
self.pulse_anim.setKeyValueAt(1, 8) self.pulse_anim.setKeyValueAt(1, self.theme.GAME_CARD_ANIMATION["pulse_min_border_width"])
self.pulse_anim.start() self.pulse_anim.start()
def enterEvent(self, event): def enterEvent(self, event):
self._hovered = True self._hovered = True
self.hoverChanged.emit(self.name, True) self.hoverChanged.emit(self.name, True)
self.setFocus(Qt.FocusReason.MouseFocusReason)
self.thickness_anim.stop() self.thickness_anim.stop()
if self._isPulseAnimationConnected: if self._isPulseAnimationConnected:
self.thickness_anim.finished.disconnect(self.startPulseAnimation) self.thickness_anim.finished.disconnect(self.startPulseAnimation)
self._isPulseAnimationConnected = False self._isPulseAnimationConnected = False
self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type.OutBack)) self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION["thickness_easing_curve"]]))
self.thickness_anim.setStartValue(self._borderWidth) self.thickness_anim.setStartValue(self._borderWidth)
self.thickness_anim.setEndValue(8) self.thickness_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["hover_border_width"])
self.thickness_anim.finished.connect(self.startPulseAnimation) self.thickness_anim.finished.connect(self.startPulseAnimation)
self._isPulseAnimationConnected = True self._isPulseAnimationConnected = True
self.thickness_anim.start() self.thickness_anim.start()
@ -492,9 +493,9 @@ class GameCard(QFrame):
if self.gradient_anim: if self.gradient_anim:
self.gradient_anim.stop() self.gradient_anim.stop()
self.gradient_anim = QPropertyAnimation(self, QByteArray(b"gradientAngle")) self.gradient_anim = QPropertyAnimation(self, QByteArray(b"gradientAngle"))
self.gradient_anim.setDuration(3000) self.gradient_anim.setDuration(self.theme.GAME_CARD_ANIMATION["gradient_anim_duration"])
self.gradient_anim.setStartValue(360) self.gradient_anim.setStartValue(self.theme.GAME_CARD_ANIMATION["gradient_start_angle"])
self.gradient_anim.setEndValue(0) self.gradient_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["gradient_end_angle"])
self.gradient_anim.setLoopCount(-1) self.gradient_anim.setLoopCount(-1)
self.gradient_anim.start() self.gradient_anim.start()
@ -515,19 +516,24 @@ class GameCard(QFrame):
if self._isPulseAnimationConnected: if self._isPulseAnimationConnected:
self.thickness_anim.finished.disconnect(self.startPulseAnimation) self.thickness_anim.finished.disconnect(self.startPulseAnimation)
self._isPulseAnimationConnected = False self._isPulseAnimationConnected = False
self.setBorderWidth(2) self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION["thickness_easing_curve_out"]]))
self.update() self.thickness_anim.setStartValue(self._borderWidth)
self.thickness_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["default_border_width"])
self.thickness_anim.start()
super().leaveEvent(event) super().leaveEvent(event)
def focusInEvent(self, event): def focusInEvent(self, event):
if not self._hovered:
self._focused = True self._focused = True
self.focusChanged.emit(self.name, True)
self.thickness_anim.stop() self.thickness_anim.stop()
if self._isPulseAnimationConnected: if self._isPulseAnimationConnected:
self.thickness_anim.finished.disconnect(self.startPulseAnimation) self.thickness_anim.finished.disconnect(self.startPulseAnimation)
self._isPulseAnimationConnected = False self._isPulseAnimationConnected = False
self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type.OutBack)) self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION["thickness_easing_curve"]]))
self.thickness_anim.setStartValue(self._borderWidth) self.thickness_anim.setStartValue(self._borderWidth)
self.thickness_anim.setEndValue(12) self.thickness_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["focus_border_width"])
self.thickness_anim.finished.connect(self.startPulseAnimation) self.thickness_anim.finished.connect(self.startPulseAnimation)
self._isPulseAnimationConnected = True self._isPulseAnimationConnected = True
self.thickness_anim.start() self.thickness_anim.start()
@ -535,9 +541,9 @@ class GameCard(QFrame):
if self.gradient_anim: if self.gradient_anim:
self.gradient_anim.stop() self.gradient_anim.stop()
self.gradient_anim = QPropertyAnimation(self, QByteArray(b"gradientAngle")) self.gradient_anim = QPropertyAnimation(self, QByteArray(b"gradientAngle"))
self.gradient_anim.setDuration(3000) self.gradient_anim.setDuration(self.theme.GAME_CARD_ANIMATION["gradient_anim_duration"])
self.gradient_anim.setStartValue(360) self.gradient_anim.setStartValue(self.theme.GAME_CARD_ANIMATION["gradient_start_angle"])
self.gradient_anim.setEndValue(0) self.gradient_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["gradient_end_angle"])
self.gradient_anim.setLoopCount(-1) self.gradient_anim.setLoopCount(-1)
self.gradient_anim.start() self.gradient_anim.start()
@ -545,22 +551,23 @@ class GameCard(QFrame):
def focusOutEvent(self, event): def focusOutEvent(self, event):
self._focused = False self._focused = False
if not self._hovered: # Сохраняем анимацию, если есть наведение self.focusChanged.emit(self.name, False)
if not self._hovered:
if self.gradient_anim: if self.gradient_anim:
self.gradient_anim.stop() self.gradient_anim.stop()
self.gradient_anim = None self.gradient_anim = None
if self.pulse_anim:
self.pulse_anim.stop()
self.pulse_anim = None
if self.thickness_anim:
self.thickness_anim.stop() self.thickness_anim.stop()
if self._isPulseAnimationConnected: if self._isPulseAnimationConnected:
self.thickness_anim.finished.disconnect(self.startPulseAnimation) self.thickness_anim.finished.disconnect(self.startPulseAnimation)
self._isPulseAnimationConnected = False self._isPulseAnimationConnected = False
if self.pulse_anim: self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION["thickness_easing_curve_out"]]))
self.pulse_anim.stop()
self.pulse_anim = None
self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type.InBack))
self.thickness_anim.setStartValue(self._borderWidth) self.thickness_anim.setStartValue(self._borderWidth)
self.thickness_anim.setEndValue(2) self.thickness_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["default_border_width"])
self.thickness_anim.start() self.thickness_anim.start()
super().focusOutEvent(event) super().focusOutEvent(event)
def mousePressEvent(self, event): def mousePressEvent(self, event):

View File

@ -56,6 +56,7 @@ class MainWindow(QMainWindow):
self.current_exec_line = None self.current_exec_line = None
self.currentDetailPage = None self.currentDetailPage = None
self.current_play_button = None self.current_play_button = None
self.current_focused_card = None
self.pending_games = [] self.pending_games = []
self.game_card_cache = {} self.game_card_cache = {}
self.pending_images = {} self.pending_images = {}
@ -242,10 +243,39 @@ class MainWindow(QMainWindow):
self.updateGameGrid() self.updateGameGrid()
self.progress_bar.setVisible(False) self.progress_bar.setVisible(False)
def _on_card_focused(self, game_name: str, is_focused: bool):
"""Обработчик сигнала focusChanged от GameCard."""
card_key = None
for key, card in self.game_card_cache.items():
if card.name == game_name:
card_key = key
break
if not card_key:
return
card = self.game_card_cache[card_key]
if is_focused:
# Если карточка получила фокус
if self.current_hovered_card and self.current_hovered_card != card:
# Сбрасываем текущую hovered карточку
self.current_hovered_card._hovered = False
self.current_hovered_card.leaveEvent(None)
self.current_hovered_card = None
if self.current_focused_card and self.current_focused_card != card:
# Сбрасываем текущую focused карточку
self.current_focused_card._focused = False
self.current_focused_card.clearFocus()
self.current_focused_card = card
else:
# Если карточка потеряла фокус
if self.current_focused_card == card:
self.current_focused_card = None
def _on_card_hovered(self, game_name: str, is_hovered: bool): def _on_card_hovered(self, game_name: str, is_hovered: bool):
"""Обработчик сигнала hoverChanged от GameCard.""" """Обработчик сигнала hoverChanged от GameCard."""
card_key = None card_key = None
# Находим ключ карточки по имени игры
for key, card in self.game_card_cache.items(): for key, card in self.game_card_cache.items():
if card.name == game_name: if card.name == game_name:
card_key = key card_key = key
@ -258,10 +288,14 @@ class MainWindow(QMainWindow):
if is_hovered: if is_hovered:
# Если мышь наведена на карточку # Если мышь наведена на карточку
if self.current_focused_card and self.current_focused_card != card:
# Сбрасываем текущую focused карточку
self.current_focused_card._focused = False
self.current_focused_card.clearFocus()
if self.current_hovered_card and self.current_hovered_card != card: if self.current_hovered_card and self.current_hovered_card != card:
# Сбрасываем предыдущую выделенную карточку # Сбрасываем предыдущую hovered карточку
self.current_hovered_card._hovered = False self.current_hovered_card._hovered = False
self.current_hovered_card.leaveEvent(None) # Принудительно вызываем leaveEvent self.current_hovered_card.leaveEvent(None)
self.current_hovered_card = card self.current_hovered_card = card
else: else:
# Если мышь покинула карточку # Если мышь покинула карточку
@ -709,6 +743,7 @@ class MainWindow(QMainWindow):
context_menu_manager=self.context_menu_manager context_menu_manager=self.context_menu_manager
) )
card.hoverChanged.connect(self._on_card_hovered) card.hoverChanged.connect(self._on_card_hovered)
card.focusChanged.connect(self._on_card_focused)
# Подключаем сигналы контекстного меню # Подключаем сигналы контекстного меню
card.editShortcutRequested.connect(self.context_menu_manager.edit_game_shortcut) card.editShortcutRequested.connect(self.context_menu_manager.edit_game_shortcut)
card.deleteGameRequested.connect(self.context_menu_manager.delete_game) card.deleteGameRequested.connect(self.context_menu_manager.delete_game)
@ -1881,7 +1916,6 @@ class MainWindow(QMainWindow):
def closeEvent(self, event): def closeEvent(self, event):
"""Завершает все дочерние процессы и сохраняет настройки при закрытии окна.""" """Завершает все дочерние процессы и сохраняет настройки при закрытии окна."""
# Завершаем все игровые процессы
for proc in self.game_processes: for proc in self.game_processes:
try: try:
parent = psutil.Process(proc.pid) parent = psutil.Process(proc.pid)
@ -1922,4 +1956,5 @@ class MainWindow(QMainWindow):
self.checkProcessTimer.deleteLater() self.checkProcessTimer.deleteLater()
self.checkProcessTimer = None self.checkProcessTimer = None
QApplication.quit()
event.accept() event.accept()

View File

@ -20,8 +20,12 @@ class SystemOverlay(QDialog):
self.theme_manager = ThemeManager() self.theme_manager = ThemeManager()
self.setStyleSheet(self.theme.OVERLAY_WINDOW_STYLE) self.setStyleSheet(self.theme.OVERLAY_WINDOW_STYLE)
# Убираем рамку окна # Make window stay on top and frameless
self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.Dialog) self.setWindowFlags(
Qt.WindowType.FramelessWindowHint |
Qt.WindowType.Dialog |
Qt.WindowType.WindowStaysOnTopHint
)
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
layout.setContentsMargins(20, 20, 20, 20) layout.setContentsMargins(20, 20, 20, 20)

View File

@ -8,6 +8,76 @@ current_theme_name = read_theme_from_config()
favoriteLabelSize = 48, 48 favoriteLabelSize = 48, 48
pixmapsScaledSize = 60, 60 pixmapsScaledSize = 60, 60
GAME_CARD_ANIMATION = {
# Ширина обводки карточки в состоянии покоя (без наведения или фокуса).
# Влияет на толщину рамки вокруг карточки, когда она не выделена.
# Значение в пикселях.
"default_border_width": 2,
# Ширина обводки при наведении курсора.
# Увеличивает толщину рамки, когда курсор находится над карточкой.
# Значение в пикселях.
"hover_border_width": 8,
# Ширина обводки при фокусе (например, при выборе с клавиатуры).
# Увеличивает толщину рамки, когда карточка в фокусе.
# Значение в пикселях.
"focus_border_width": 12,
# Минимальная ширина обводки во время пульсирующей анимации.
# Определяет минимальную толщину рамки при пульсации (анимация "дыхания").
# Значение в пикселях.
"pulse_min_border_width": 8,
# Максимальная ширина обводки во время пульсирующей анимации.
# Определяет максимальную толщину рамки при пульсации.
# Значение в пикселях.
"pulse_max_border_width": 10,
# Длительность анимации изменения толщины обводки (например, при наведении или фокусе).
# Влияет на скорость перехода от одной ширины обводки к другой.
# Значение в миллисекундах.
"thickness_anim_duration": 300,
# Длительность одного цикла пульсирующей анимации.
# Определяет, как быстро рамка "пульсирует" между min и max значениями.
# Значение в миллисекундах.
"pulse_anim_duration": 800,
# Длительность анимации вращения градиента.
# Влияет на скорость, с которой градиентная обводка вращается вокруг карточки.
# Значение в миллисекундах.
"gradient_anim_duration": 3000,
# Начальный угол градиента (в градусах).
# Определяет начальную точку вращения градиента при старте анимации.
"gradient_start_angle": 360,
# Конечный угол градиента (в градусах).
# Определяет конечную точку вращения градиента.
# Значение 0 означает полный поворот на 360 градусов.
"gradient_end_angle": 0,
# Тип кривой сглаживания для анимации увеличения обводки (при наведении/фокусе).
# Влияет на "чувство" анимации (например, плавное ускорение или замедление).
# Возможные значения: строки, соответствующие QEasingCurve.Type (например, "OutBack", "InOutQuad").
"thickness_easing_curve": "OutBack",
# Тип кривой сглаживания для анимации уменьшения обводки (при уходе курсора/потере фокуса).
# Влияет на "чувство" возврата к исходной ширине обводки.
"thickness_easing_curve_out": "InBack",
# Цвета градиента для анимированной обводки.
# Список словарей, где каждый словарь задает позицию (0.01.0) и цвет в формате hex.
# Влияет на внешний вид обводки при наведении или фокусе.
"gradient_colors": [
{"position": 0, "color": "#00fff5"}, # Начальный цвет (циан)
{"position": 0.33, "color": "#FF5733"}, # Цвет на 33% (оранжевый)
{"position": 0.66, "color": "#9B59B6"}, # Цвет на 66% (пурпурный)
{"position": 1, "color": "#00fff5"} # Конечный цвет (возвращение к циану)
]
}
# СТИЛЬ ШАПКИ ГЛАВНОГО ОКНА # СТИЛЬ ШАПКИ ГЛАВНОГО ОКНА
MAIN_WINDOW_HEADER_STYLE = """ MAIN_WINDOW_HEADER_STYLE = """
QFrame { QFrame {

View File

@ -8,6 +8,76 @@ current_theme_name = read_theme_from_config()
favoriteLabelSize = 48, 48 favoriteLabelSize = 48, 48
pixmapsScaledSize = 60, 60 pixmapsScaledSize = 60, 60
GAME_CARD_ANIMATION = {
# Ширина обводки карточки в состоянии покоя (без наведения или фокуса).
# Влияет на толщину рамки вокруг карточки, когда она не выделена.
# Значение в пикселях.
"default_border_width": 2,
# Ширина обводки при наведении курсора.
# Увеличивает толщину рамки, когда курсор находится над карточкой.
# Значение в пикселях.
"hover_border_width": 8,
# Ширина обводки при фокусе (например, при выборе с клавиатуры).
# Увеличивает толщину рамки, когда карточка в фокусе.
# Значение в пикселях.
"focus_border_width": 12,
# Минимальная ширина обводки во время пульсирующей анимации.
# Определяет минимальную толщину рамки при пульсации (анимация "дыхания").
# Значение в пикселях.
"pulse_min_border_width": 8,
# Максимальная ширина обводки во время пульсирующей анимации.
# Определяет максимальную толщину рамки при пульсации.
# Значение в пикселях.
"pulse_max_border_width": 10,
# Длительность анимации изменения толщины обводки (например, при наведении или фокусе).
# Влияет на скорость перехода от одной ширины обводки к другой.
# Значение в миллисекундах.
"thickness_anim_duration": 300,
# Длительность одного цикла пульсирующей анимации.
# Определяет, как быстро рамка "пульсирует" между min и max значениями.
# Значение в миллисекундах.
"pulse_anim_duration": 800,
# Длительность анимации вращения градиента.
# Влияет на скорость, с которой градиентная обводка вращается вокруг карточки.
# Значение в миллисекундах.
"gradient_anim_duration": 3000,
# Начальный угол градиента (в градусах).
# Определяет начальную точку вращения градиента при старте анимации.
"gradient_start_angle": 360,
# Конечный угол градиента (в градусах).
# Определяет конечную точку вращения градиента.
# Значение 0 означает полный поворот на 360 градусов.
"gradient_end_angle": 0,
# Тип кривой сглаживания для анимации увеличения обводки (при наведении/фокусе).
# Влияет на "чувство" анимации (например, плавное ускорение или замедление).
# Возможные значения: строки, соответствующие QEasingCurve.Type (например, "OutBack", "InOutQuad").
"thickness_easing_curve": "OutBack",
# Тип кривой сглаживания для анимации уменьшения обводки (при уходе курсора/потере фокуса).
# Влияет на "чувство" возврата к исходной ширине обводки.
"thickness_easing_curve_out": "InBack",
# Цвета градиента для анимированной обводки.
# Список словарей, где каждый словарь задает позицию (0.01.0) и цвет в формате hex.
# Влияет на внешний вид обводки при наведении или фокусе.
"gradient_colors": [
{"position": 0, "color": "#00fff5"}, # Начальный цвет (циан)
{"position": 0.33, "color": "#FF5733"}, # Цвет на 33% (оранжевый)
{"position": 0.66, "color": "#9B59B6"}, # Цвет на 66% (пурпурный)
{"position": 1, "color": "#00fff5"} # Конечный цвет (возвращение к циану)
]
}
CONTEXT_MENU_STYLE = """ CONTEXT_MENU_STYLE = """
QMenu { QMenu {
background: #282a33;; background: #282a33;;