forked from Boria138/PortProtonQt
added more animation to detail_page
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
@@ -35,14 +35,13 @@ from portprotonqt.howlongtobeat_api import HowLongToBeat
|
|||||||
from portprotonqt.downloader import Downloader
|
from portprotonqt.downloader import Downloader
|
||||||
|
|
||||||
from PySide6.QtWidgets import (QLineEdit, QMainWindow, QStatusBar, QWidget, QVBoxLayout, QLabel, QHBoxLayout, QStackedWidget, QComboBox, QScrollArea, QSlider,
|
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.QtGui import QIcon, QPixmap, QColor, QDesktopServices
|
||||||
from PySide6.QtCore import Qt, QAbstractAnimation, QPropertyAnimation, QByteArray, QUrl, Signal, QTimer, Slot
|
|
||||||
from typing import cast
|
from typing import cast
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from PySide6.QtWidgets import QSizePolicy
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -1880,17 +1879,126 @@ class MainWindow(QMainWindow):
|
|||||||
self.current_play_button = playButton
|
self.current_play_button = playButton
|
||||||
|
|
||||||
# Анимация
|
# Анимация
|
||||||
opacityEffect = QGraphicsOpacityEffect(detailPage)
|
animation_type = self.theme.GAME_CARD_ANIMATION.get("detail_page_animation_type", "fade")
|
||||||
detailPage.setGraphicsEffect(opacityEffect)
|
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_fade_duration", 350)
|
||||||
animation = QPropertyAnimation(opacityEffect, QByteArray(b"opacity"))
|
|
||||||
animation.setDuration(350)
|
if animation_type == "fade":
|
||||||
animation.setStartValue(0)
|
opacity_effect = QGraphicsOpacityEffect(detailPage)
|
||||||
animation.setEndValue(1)
|
detailPage.setGraphicsEffect(opacity_effect)
|
||||||
animation.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
|
animation = QPropertyAnimation(opacity_effect, QByteArray(b"opacity"))
|
||||||
self._animations[detailPage] = animation
|
animation.setDuration(duration)
|
||||||
animation.finished.connect(
|
animation.setStartValue(0)
|
||||||
lambda: detailPage.setGraphicsEffect(cast(QGraphicsEffect, None))
|
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):
|
def toggleFavoriteInDetailPage(self, game_name, label):
|
||||||
favorites = read_favorites()
|
favorites = read_favorites()
|
||||||
|
@@ -9,6 +9,10 @@ favoriteLabelSize = 48, 48
|
|||||||
pixmapsScaledSize = 60, 60
|
pixmapsScaledSize = 60, 60
|
||||||
|
|
||||||
GAME_CARD_ANIMATION = {
|
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.33, "color": "#FF5733"}, # Цвет на 33% (оранжевый)
|
||||||
{"position": 0.66, "color": "#9B59B6"}, # Цвет на 66% (пурпурный)
|
{"position": 0.66, "color": "#9B59B6"}, # Цвет на 66% (пурпурный)
|
||||||
{"position": 1, "color": "#00fff5"} # Конечный цвет (возвращение к циану)
|
{"position": 1, "color": "#00fff5"} # Конечный цвет (возвращение к циану)
|
||||||
]
|
],
|
||||||
|
|
||||||
|
# Длительность анимации fade при входе на детальную страницу
|
||||||
|
"detail_page_fade_duration": 350,
|
||||||
|
|
||||||
|
# Длительность анимации slide при входе на детальную страницу
|
||||||
|
"detail_page_slide_duration": 500,
|
||||||
|
|
||||||
|
# Длительность анимации zoom при входе на детальную страницу
|
||||||
|
"detail_page_zoom_duration": 400
|
||||||
}
|
}
|
||||||
|
|
||||||
# СТИЛЬ ШАПКИ ГЛАВНОГО ОКНА
|
# СТИЛЬ ШАПКИ ГЛАВНОГО ОКНА
|
||||||
|
@@ -27,6 +27,10 @@ color_g = "rgba(0, 0, 0, 0)"
|
|||||||
color_h = "transparent"
|
color_h = "transparent"
|
||||||
|
|
||||||
GAME_CARD_ANIMATION = {
|
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.33, "color": "#FF5733"}, # Цвет на 33% (оранжевый)
|
||||||
{"position": 0.66, "color": "#9B59B6"}, # Цвет на 66% (пурпурный)
|
{"position": 0.66, "color": "#9B59B6"}, # Цвет на 66% (пурпурный)
|
||||||
{"position": 1, "color": "#00fff5"} # Конечный цвет (возвращение к циану)
|
{"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"""
|
CONTEXT_MENU_STYLE = f"""
|
||||||
|
Reference in New Issue
Block a user