Compare commits
	
		
			8 Commits
		
	
	
		
			v0.1.2
			...
			593db00166
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						593db00166
	
				 | 
					
					
						|||
| 
						
						
							
						
						79a78c785b
	
				 | 
					
					
						|||
| 
						
						
							
						
						0b92d058a9
	
				 | 
					
					
						|||
| 
						
						
							
						
						9df22edfc9
	
				 | 
					
					
						|||
| 
						
						
							
						
						4559231712
	
				 | 
					
					
						|||
| 
						
						
							
						
						18dbd42369
	
				 | 
					
					
						|||
| 
						
						
							
						
						76c0e607c5
	
				 | 
					
					
						|||
| 
						
						
							
						
						a91c9dacd8
	
				 | 
					
					
						
@@ -40,7 +40,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        fedora_version: [40, 41, 42, rawhide]
 | 
			
		||||
        fedora_version: [41, 42, rawhide]
 | 
			
		||||
 | 
			
		||||
    container:
 | 
			
		||||
      image: fedora:${{ matrix.fedora_version }}
 | 
			
		||||
 
 | 
			
		||||
@@ -97,7 +97,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        fedora_version: [40, 41, 42, rawhide]
 | 
			
		||||
        fedora_version: [41, 42, rawhide]
 | 
			
		||||
 | 
			
		||||
    container:
 | 
			
		||||
      image: fedora:${{ matrix.fedora_version }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -3,6 +3,19 @@
 | 
			
		||||
Все заметные изменения в этом проекте фиксируются в этом файле.
 | 
			
		||||
Формат основан на [Keep a Changelog](https://keepachangelog.com/) и придерживается принципов [Semantic Versioning](https://semver.org/).
 | 
			
		||||
 | 
			
		||||
## [Unreleased]
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
- Удалены сборки для Fedora 40
 | 
			
		||||
- Перенесены параметры анимации GameCard в `styles.py` с подробной документацией для поддержки кастомизации тем.
 | 
			
		||||
 | 
			
		||||
### Fixed
 | 
			
		||||
- Дублирование обводки выделения карточек при быстром перемешении мыши
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## [0.1.2] - 2025-06-15
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 
 | 
			
		||||
@@ -28,19 +28,19 @@ BuildRequires:  git
 | 
			
		||||
%package -n     python3-%{pypi_name}-git
 | 
			
		||||
Summary:        %{summary}
 | 
			
		||||
%{?python_provide:%python_provide python3-%{pypi_name}}
 | 
			
		||||
Requires:       python3dist(babel)
 | 
			
		||||
Requires:       python3dist(evdev)
 | 
			
		||||
Requires:       python3dist(icoextract)
 | 
			
		||||
Requires:       python3dist(numpy)
 | 
			
		||||
Requires:       python3dist(orjson)
 | 
			
		||||
Requires:       python3dist(psutil)
 | 
			
		||||
Requires:       python3dist(pyside6)
 | 
			
		||||
Requires:       python3dist(pyudev)
 | 
			
		||||
Requires:       python3dist(requests)
 | 
			
		||||
Requires:       python3dist(tqdm)
 | 
			
		||||
Requires:       python3dist(vdf)
 | 
			
		||||
Requires:       python3dist(pefile)
 | 
			
		||||
Requires:       python3dist(pillow)
 | 
			
		||||
Requires:       python3-babel
 | 
			
		||||
Requires:       python3-evdev
 | 
			
		||||
Requires:       python3-icoextract
 | 
			
		||||
Requires:       python3-numpy
 | 
			
		||||
Requires:       python3-orjson
 | 
			
		||||
Requires:       python3-psutil
 | 
			
		||||
Requires:       python3-pyside6
 | 
			
		||||
Requires:       python3-pyudev
 | 
			
		||||
Requires:       python3-requests
 | 
			
		||||
Requires:       python3-tqdm
 | 
			
		||||
Requires:       python3-vdf
 | 
			
		||||
Requires:       python3-pefile
 | 
			
		||||
Requires:       python3-pillow
 | 
			
		||||
Requires:       perl-Image-ExifTool
 | 
			
		||||
Requires:       xdg-utils
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,19 +25,19 @@ BuildRequires:  git
 | 
			
		||||
%package -n     python3-%{pypi_name}
 | 
			
		||||
Summary:        %{summary}
 | 
			
		||||
%{?python_provide:%python_provide python3-%{pypi_name}}
 | 
			
		||||
Requires:       python3dist(babel)
 | 
			
		||||
Requires:       python3dist(evdev)
 | 
			
		||||
Requires:       python3dist(icoextract)
 | 
			
		||||
Requires:       python3dist(numpy)
 | 
			
		||||
Requires:       python3dist(orjson)
 | 
			
		||||
Requires:       python3dist(psutil)
 | 
			
		||||
Requires:       python3dist(pyside6)
 | 
			
		||||
Requires:       python3dist(pyudev)
 | 
			
		||||
Requires:       python3dist(requests)
 | 
			
		||||
Requires:       python3dist(tqdm)
 | 
			
		||||
Requires:       python3dist(vdf)
 | 
			
		||||
Requires:       python3dist(pefile)
 | 
			
		||||
Requires:       python3dist(pillow)
 | 
			
		||||
Requires:       python3-babel
 | 
			
		||||
Requires:       python3-evdev
 | 
			
		||||
Requires:       python3-icoextract
 | 
			
		||||
Requires:       python3-numpy
 | 
			
		||||
Requires:       python3-orjson
 | 
			
		||||
Requires:       python3-psutil
 | 
			
		||||
Requires:       python3-pyside6
 | 
			
		||||
Requires:       python3-pyudev
 | 
			
		||||
Requires:       python3-requests
 | 
			
		||||
Requires:       python3-tqdm
 | 
			
		||||
Requires:       python3-vdf
 | 
			
		||||
Requires:       python3-pefile
 | 
			
		||||
Requires:       python3-pillow
 | 
			
		||||
Requires:       perl-Image-ExifTool
 | 
			
		||||
Requires:       xdg-utils
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ class GameCard(QFrame):
 | 
			
		||||
    addToSteamRequested = Signal(str, str, str)   # name, exec_line, cover_path
 | 
			
		||||
    removeFromSteamRequested = Signal(str, str)   # name, exec_line
 | 
			
		||||
    openGameFolderRequested = Signal(str, str)    # name, exec_line
 | 
			
		||||
    hoverChanged = Signal(str, bool)
 | 
			
		||||
 | 
			
		||||
    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,
 | 
			
		||||
@@ -66,14 +67,14 @@ class GameCard(QFrame):
 | 
			
		||||
        self.setStyleSheet(self.theme.GAME_CARD_WINDOW_STYLE)
 | 
			
		||||
 | 
			
		||||
        # Параметры анимации обводки
 | 
			
		||||
        self._borderWidth = 2
 | 
			
		||||
        self._gradientAngle = 0.0
 | 
			
		||||
        self._borderWidth = self.theme.GAME_CARD_ANIMATION["default_border_width"]
 | 
			
		||||
        self._gradientAngle = self.theme.GAME_CARD_ANIMATION["gradient_start_angle"]
 | 
			
		||||
        self._hovered = False
 | 
			
		||||
        self._focused = False
 | 
			
		||||
 | 
			
		||||
        # Анимации
 | 
			
		||||
        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.pulse_anim = None
 | 
			
		||||
 | 
			
		||||
@@ -447,10 +448,8 @@ class GameCard(QFrame):
 | 
			
		||||
        if self._hovered or self._focused:
 | 
			
		||||
            center = self.rect().center()
 | 
			
		||||
            gradient = QConicalGradient(center, self._gradientAngle)
 | 
			
		||||
            gradient.setColorAt(0, QColor("#00fff5"))
 | 
			
		||||
            gradient.setColorAt(0.33, QColor("#FF5733"))
 | 
			
		||||
            gradient.setColorAt(0.66, QColor("#9B59B6"))
 | 
			
		||||
            gradient.setColorAt(1, QColor("#00fff5"))
 | 
			
		||||
            for stop in self.theme.GAME_CARD_ANIMATION["gradient_colors"]:
 | 
			
		||||
                gradient.setColorAt(stop["position"], QColor(stop["color"]))
 | 
			
		||||
            pen.setBrush(QBrush(gradient))
 | 
			
		||||
        else:
 | 
			
		||||
            pen.setColor(QColor(0, 0, 0, 0))
 | 
			
		||||
@@ -467,22 +466,23 @@ class GameCard(QFrame):
 | 
			
		||||
        if self.pulse_anim:
 | 
			
		||||
            self.pulse_anim.stop()
 | 
			
		||||
        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.setKeyValueAt(0, 8)
 | 
			
		||||
        self.pulse_anim.setKeyValueAt(0.5, 10)
 | 
			
		||||
        self.pulse_anim.setKeyValueAt(1, 8)
 | 
			
		||||
        self.pulse_anim.setKeyValueAt(0, self.theme.GAME_CARD_ANIMATION["pulse_min_border_width"])
 | 
			
		||||
        self.pulse_anim.setKeyValueAt(0.5, self.theme.GAME_CARD_ANIMATION["pulse_max_border_width"])
 | 
			
		||||
        self.pulse_anim.setKeyValueAt(1, self.theme.GAME_CARD_ANIMATION["pulse_min_border_width"])
 | 
			
		||||
        self.pulse_anim.start()
 | 
			
		||||
 | 
			
		||||
    def enterEvent(self, event):
 | 
			
		||||
        self._hovered = True
 | 
			
		||||
        self.hoverChanged.emit(self.name, True)
 | 
			
		||||
        self.thickness_anim.stop()
 | 
			
		||||
        if self._isPulseAnimationConnected:
 | 
			
		||||
            self.thickness_anim.finished.disconnect(self.startPulseAnimation)
 | 
			
		||||
            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.setEndValue(8)
 | 
			
		||||
        self.thickness_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["hover_border_width"])
 | 
			
		||||
        self.thickness_anim.finished.connect(self.startPulseAnimation)
 | 
			
		||||
        self._isPulseAnimationConnected = True
 | 
			
		||||
        self.thickness_anim.start()
 | 
			
		||||
@@ -490,9 +490,9 @@ class GameCard(QFrame):
 | 
			
		||||
        if self.gradient_anim:
 | 
			
		||||
            self.gradient_anim.stop()
 | 
			
		||||
        self.gradient_anim = QPropertyAnimation(self, QByteArray(b"gradientAngle"))
 | 
			
		||||
        self.gradient_anim.setDuration(3000)
 | 
			
		||||
        self.gradient_anim.setStartValue(360)
 | 
			
		||||
        self.gradient_anim.setEndValue(0)
 | 
			
		||||
        self.gradient_anim.setDuration(self.theme.GAME_CARD_ANIMATION["gradient_anim_duration"])
 | 
			
		||||
        self.gradient_anim.setStartValue(self.theme.GAME_CARD_ANIMATION["gradient_start_angle"])
 | 
			
		||||
        self.gradient_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["gradient_end_angle"])
 | 
			
		||||
        self.gradient_anim.setLoopCount(-1)
 | 
			
		||||
        self.gradient_anim.start()
 | 
			
		||||
 | 
			
		||||
@@ -500,22 +500,23 @@ class GameCard(QFrame):
 | 
			
		||||
 | 
			
		||||
    def leaveEvent(self, event):
 | 
			
		||||
        self._hovered = False
 | 
			
		||||
        if not self._focused:  # Сохраняем анимацию, если есть фокус
 | 
			
		||||
        self.hoverChanged.emit(self.name, False)
 | 
			
		||||
        if not self._focused:
 | 
			
		||||
            if self.gradient_anim:
 | 
			
		||||
                self.gradient_anim.stop()
 | 
			
		||||
                self.gradient_anim = None
 | 
			
		||||
            self.thickness_anim.stop()
 | 
			
		||||
            if self._isPulseAnimationConnected:
 | 
			
		||||
                self.thickness_anim.finished.disconnect(self.startPulseAnimation)
 | 
			
		||||
                self._isPulseAnimationConnected = False
 | 
			
		||||
            if self.pulse_anim:
 | 
			
		||||
                self.pulse_anim.stop()
 | 
			
		||||
                self.pulse_anim = None
 | 
			
		||||
            self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type.InBack))
 | 
			
		||||
            if self.thickness_anim:
 | 
			
		||||
                self.thickness_anim.stop()
 | 
			
		||||
            if self._isPulseAnimationConnected:
 | 
			
		||||
                self.thickness_anim.finished.disconnect(self.startPulseAnimation)
 | 
			
		||||
                self._isPulseAnimationConnected = False
 | 
			
		||||
            self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION["thickness_easing_curve_out"]]))
 | 
			
		||||
            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()
 | 
			
		||||
 | 
			
		||||
        super().leaveEvent(event)
 | 
			
		||||
 | 
			
		||||
    def focusInEvent(self, event):
 | 
			
		||||
@@ -524,9 +525,9 @@ class GameCard(QFrame):
 | 
			
		||||
        if self._isPulseAnimationConnected:
 | 
			
		||||
            self.thickness_anim.finished.disconnect(self.startPulseAnimation)
 | 
			
		||||
            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.setEndValue(12)
 | 
			
		||||
        self.thickness_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["focus_border_width"])
 | 
			
		||||
        self.thickness_anim.finished.connect(self.startPulseAnimation)
 | 
			
		||||
        self._isPulseAnimationConnected = True
 | 
			
		||||
        self.thickness_anim.start()
 | 
			
		||||
@@ -534,9 +535,9 @@ class GameCard(QFrame):
 | 
			
		||||
        if self.gradient_anim:
 | 
			
		||||
            self.gradient_anim.stop()
 | 
			
		||||
        self.gradient_anim = QPropertyAnimation(self, QByteArray(b"gradientAngle"))
 | 
			
		||||
        self.gradient_anim.setDuration(3000)
 | 
			
		||||
        self.gradient_anim.setStartValue(360)
 | 
			
		||||
        self.gradient_anim.setEndValue(0)
 | 
			
		||||
        self.gradient_anim.setDuration(self.theme.GAME_CARD_ANIMATION["gradient_anim_duration"])
 | 
			
		||||
        self.gradient_anim.setStartValue(self.theme.GAME_CARD_ANIMATION["gradient_start_angle"])
 | 
			
		||||
        self.gradient_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["gradient_end_angle"])
 | 
			
		||||
        self.gradient_anim.setLoopCount(-1)
 | 
			
		||||
        self.gradient_anim.start()
 | 
			
		||||
 | 
			
		||||
@@ -555,11 +556,10 @@ class GameCard(QFrame):
 | 
			
		||||
            if self.pulse_anim:
 | 
			
		||||
                self.pulse_anim.stop()
 | 
			
		||||
                self.pulse_anim = None
 | 
			
		||||
            self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type.InBack))
 | 
			
		||||
            self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION["thickness_easing_curve_out"]]))
 | 
			
		||||
            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()
 | 
			
		||||
 | 
			
		||||
        super().focusOutEvent(event)
 | 
			
		||||
 | 
			
		||||
    def mousePressEvent(self, event):
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,7 @@ class MainWindow(QMainWindow):
 | 
			
		||||
        self.games_load_timer.timeout.connect(self.finalize_game_loading)
 | 
			
		||||
        self.games_loaded.connect(self.on_games_loaded)
 | 
			
		||||
        self.current_add_game_dialog = None
 | 
			
		||||
        self.current_hovered_card = None
 | 
			
		||||
 | 
			
		||||
        # Добавляем таймер для дебаунсинга сохранения настроек
 | 
			
		||||
        self.settingsDebounceTimer = QTimer(self)
 | 
			
		||||
@@ -241,6 +242,32 @@ class MainWindow(QMainWindow):
 | 
			
		||||
        self.updateGameGrid()
 | 
			
		||||
        self.progress_bar.setVisible(False)
 | 
			
		||||
 | 
			
		||||
    def _on_card_hovered(self, game_name: str, is_hovered: bool):
 | 
			
		||||
        """Обработчик сигнала hoverChanged от 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_hovered:
 | 
			
		||||
            # Если мышь наведена на карточку
 | 
			
		||||
            if self.current_hovered_card and self.current_hovered_card != card:
 | 
			
		||||
                # Сбрасываем предыдущую выделенную карточку
 | 
			
		||||
                self.current_hovered_card._hovered = False
 | 
			
		||||
                self.current_hovered_card.leaveEvent(None)  # Принудительно вызываем leaveEvent
 | 
			
		||||
            self.current_hovered_card = card
 | 
			
		||||
        else:
 | 
			
		||||
            # Если мышь покинула карточку
 | 
			
		||||
            if self.current_hovered_card == card:
 | 
			
		||||
                self.current_hovered_card = None
 | 
			
		||||
 | 
			
		||||
    def loadGames(self):
 | 
			
		||||
        display_filter = read_display_filter()
 | 
			
		||||
        favorites = read_favorites()
 | 
			
		||||
@@ -681,6 +708,7 @@ class MainWindow(QMainWindow):
 | 
			
		||||
                    card_width=self.card_width,
 | 
			
		||||
                    context_menu_manager=self.context_menu_manager
 | 
			
		||||
                )
 | 
			
		||||
                card.hoverChanged.connect(self._on_card_hovered)
 | 
			
		||||
                # Подключаем сигналы контекстного меню
 | 
			
		||||
                card.editShortcutRequested.connect(self.context_menu_manager.edit_game_shortcut)
 | 
			
		||||
                card.deleteGameRequested.connect(self.context_menu_manager.delete_game)
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,76 @@ current_theme_name = read_theme_from_config()
 | 
			
		||||
favoriteLabelSize = 48, 48
 | 
			
		||||
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.0–1.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 = """
 | 
			
		||||
    QFrame {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,76 @@ current_theme_name = read_theme_from_config()
 | 
			
		||||
favoriteLabelSize = 48, 48
 | 
			
		||||
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.0–1.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 = """
 | 
			
		||||
    QMenu {
 | 
			
		||||
        background: #282a33;;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user