added more animation to detail_page
All checks were successful
Code check / Check code (push) Successful in 1m37s

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
2025-08-02 11:23:31 +05:00
parent 9cc4b8c51d
commit 0a8a7c538c
3 changed files with 150 additions and 16 deletions

View File

@ -35,14 +35,13 @@ from portprotonqt.howlongtobeat_api import HowLongToBeat
from portprotonqt.downloader import Downloader
from PySide6.QtWidgets import (QLineEdit, QMainWindow, QStatusBar, QWidget, QVBoxLayout, QLabel, QHBoxLayout, QStackedWidget, QComboBox, QScrollArea, QSlider,
QDialog, QFormLayout, QFrame, QGraphicsDropShadowEffect, QMessageBox, QGraphicsEffect, QGraphicsOpacityEffect, QApplication, QPushButton, QProgressBar, QCheckBox)
QDialog, QFormLayout, QFrame, QGraphicsDropShadowEffect, QMessageBox, QGraphicsOpacityEffect, QApplication, QPushButton, QProgressBar, QCheckBox, QSizePolicy)
from PySide6.QtCore import Qt, QAbstractAnimation, QPropertyAnimation, QByteArray, QUrl, Signal, QTimer, Slot, QEasingCurve, QParallelAnimationGroup, QRect
from PySide6.QtGui import QIcon, QPixmap, QColor, QDesktopServices
from PySide6.QtCore import Qt, QAbstractAnimation, QPropertyAnimation, QByteArray, QUrl, Signal, QTimer, Slot
from typing import cast
from collections.abc import Callable
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
from PySide6.QtWidgets import QSizePolicy
logger = get_logger(__name__)
@ -1880,17 +1879,126 @@ class MainWindow(QMainWindow):
self.current_play_button = playButton
# Анимация
opacityEffect = QGraphicsOpacityEffect(detailPage)
detailPage.setGraphicsEffect(opacityEffect)
animation = QPropertyAnimation(opacityEffect, QByteArray(b"opacity"))
animation.setDuration(350)
animation.setStartValue(0)
animation.setEndValue(1)
animation.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
self._animations[detailPage] = animation
animation.finished.connect(
lambda: detailPage.setGraphicsEffect(cast(QGraphicsEffect, None))
)
animation_type = self.theme.GAME_CARD_ANIMATION.get("detail_page_animation_type", "fade")
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_fade_duration", 350)
if animation_type == "fade":
opacity_effect = QGraphicsOpacityEffect(detailPage)
detailPage.setGraphicsEffect(opacity_effect)
animation = QPropertyAnimation(opacity_effect, QByteArray(b"opacity"))
animation.setDuration(duration)
animation.setStartValue(0)
animation.setEndValue(1)
animation.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
self._animations[detailPage] = animation
animation.finished.connect(lambda: None)
elif animation_type == "slide_left":
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_slide_duration", 500)
detailPage.move(self.width(), 0)
animation = QPropertyAnimation(detailPage, QByteArray(b"pos"))
animation.setDuration(duration)
animation.setStartValue(detailPage.pos())
animation.setEndValue(self.stackedWidget.rect().topLeft())
animation.setEasingCurve(QEasingCurve.Type.OutCubic)
animation.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
self._animations[detailPage] = animation
elif animation_type == "slide_right":
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_slide_duration", 500)
detailPage.move(-self.width(), 0)
animation = QPropertyAnimation(detailPage, QByteArray(b"pos"))
animation.setDuration(duration)
animation.setStartValue(detailPage.pos())
animation.setEndValue(self.stackedWidget.rect().topLeft())
animation.setEasingCurve(QEasingCurve.Type.OutCubic)
animation.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
self._animations[detailPage] = animation
elif animation_type == "slide_up":
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_slide_duration", 500)
detailPage.move(0, self.height())
animation = QPropertyAnimation(detailPage, QByteArray(b"pos"))
animation.setDuration(duration)
animation.setStartValue(detailPage.pos())
animation.setEndValue(self.stackedWidget.rect().topLeft())
animation.setEasingCurve(QEasingCurve.Type.OutCubic)
animation.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
self._animations[detailPage] = animation
elif animation_type == "slide_down":
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_slide_duration", 500)
detailPage.move(0, -self.height())
animation = QPropertyAnimation(detailPage, QByteArray(b"pos"))
animation.setDuration(duration)
animation.setStartValue(detailPage.pos())
animation.setEndValue(self.stackedWidget.rect().topLeft())
animation.setEasingCurve(QEasingCurve.Type.OutCubic)
animation.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
self._animations[detailPage] = animation
elif animation_type == "bounce":
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_zoom_duration", 400)
detailPage.setWindowOpacity(0.0)
opacity_anim = QPropertyAnimation(detailPage, QByteArray(b"windowOpacity"))
opacity_anim.setDuration(duration)
opacity_anim.setStartValue(0.0)
opacity_anim.setEndValue(1.0)
# Animate geometry
initial_rect = QRect(detailPage.x() + detailPage.width() // 4, detailPage.y() + detailPage.height() // 4,
detailPage.width() // 2, detailPage.height() // 2)
final_rect = detailPage.geometry()
geometry_anim = QPropertyAnimation(detailPage, QByteArray(b"geometry"))
geometry_anim.setDuration(duration)
geometry_anim.setStartValue(initial_rect)
geometry_anim.setEndValue(final_rect)
geometry_anim.setEasingCurve(QEasingCurve.Type.OutBack)
group_anim = QParallelAnimationGroup()
group_anim.addAnimation(opacity_anim)
group_anim.addAnimation(geometry_anim)
def load_image_and_restore_effect():
if not detailPage or detailPage.isHidden():
logger.warning("Detail page is None or hidden, skipping image load")
return
# No need to restore graphics effect, just ensure full opacity
detailPage.setWindowOpacity(1.0)
if cover_path:
def on_pixmap_ready(pixmap):
if not detailPage or detailPage.isHidden():
logger.warning("Detail page is None or hidden, skipping pixmap update")
return
rounded = round_corners(pixmap, 10)
imageLabel.setPixmap(rounded)
logger.debug("Pixmap set for imageLabel")
def on_palette_ready(palette):
if not detailPage or detailPage.isHidden():
logger.warning("Detail page is None or hidden, skipping palette update")
return
dark_palette = [self.darkenColor(color, factor=200) for color in palette]
stops = ",\n".join(
[f"stop:{i/(len(dark_palette)-1):.2f} {dark_palette[i].name()}" for i in range(len(dark_palette))]
)
detailPage.setStyleSheet(self.theme.detail_page_style(stops))
logger.debug("Stylesheet updated with palette")
self.getColorPalette_async(cover_path, num_colors=5, callback=on_palette_ready)
load_pixmap_async(cover_path, 300, 400, on_pixmap_ready)
# Clean up function
def cleanup_animation():
if detailPage in self._animations:
del self._animations[detailPage]
group_anim.finished.connect(load_image_and_restore_effect)
group_anim.finished.connect(cleanup_animation)
group_anim.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
self._animations[detailPage] = group_anim
elif animation_type == "none":
pass
def toggleFavoriteInDetailPage(self, game_name, label):
favorites = read_favorites()

View File

@ -9,6 +9,10 @@ favoriteLabelSize = 48, 48
pixmapsScaledSize = 60, 60
GAME_CARD_ANIMATION = {
# Тип анимации fade при входе на детальную страницу
# Возможные значения: "fade", "slide_left", "slide_right", "slide_up", "slide_down", "bounce", "none"
"detail_page_animation_type": "fade",
# Ширина обводки карточки в состоянии покоя (без наведения или фокуса).
# Влияет на толщину рамки вокруг карточки, когда она не выделена.
# Значение в пикселях.
@ -75,7 +79,16 @@ GAME_CARD_ANIMATION = {
{"position": 0.33, "color": "#FF5733"}, # Цвет на 33% (оранжевый)
{"position": 0.66, "color": "#9B59B6"}, # Цвет на 66% (пурпурный)
{"position": 1, "color": "#00fff5"} # Конечный цвет (возвращение к циану)
]
],
# Длительность анимации fade при входе на детальную страницу
"detail_page_fade_duration": 350,
# Длительность анимации slide при входе на детальную страницу
"detail_page_slide_duration": 500,
# Длительность анимации zoom при входе на детальную страницу
"detail_page_zoom_duration": 400
}
# СТИЛЬ ШАПКИ ГЛАВНОГО ОКНА

View File

@ -27,6 +27,10 @@ color_g = "rgba(0, 0, 0, 0)"
color_h = "transparent"
GAME_CARD_ANIMATION = {
# Тип анимации fade при входе на детальную страницу
# Возможные значения: "fade", "slide_left", "slide_right", "slide_up", "slide_down", "bounce", "none"
"detail_page_animation_type": "fade",
# Ширина обводки карточки в состоянии покоя (без наведения или фокуса).
# Влияет на толщину рамки вокруг карточки, когда она не выделена.
# Значение в пикселях.
@ -93,7 +97,16 @@ GAME_CARD_ANIMATION = {
{"position": 0.33, "color": "#FF5733"}, # Цвет на 33% (оранжевый)
{"position": 0.66, "color": "#9B59B6"}, # Цвет на 66% (пурпурный)
{"position": 1, "color": "#00fff5"} # Конечный цвет (возвращение к циану)
]
],
# Длительность анимации fade при входе на детальную страницу
"detail_page_fade_duration": 350,
# Длительность анимации slide при входе на детальную страницу
"detail_page_slide_duration": 500,
# Длительность анимации zoom при входе на детальную страницу
"detail_page_zoom_duration": 400
}
CONTEXT_MENU_STYLE = f"""