All checks were successful
Code check / Check code (push) Successful in 1m50s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
251 lines
14 KiB
Python
251 lines
14 KiB
Python
from PySide6.QtCore import QPropertyAnimation, QByteArray, QEasingCurve, QAbstractAnimation, QParallelAnimationGroup, QRect, Qt
|
|
from PySide6.QtGui import QPainter, QPen, QColor, QConicalGradient, QBrush
|
|
from PySide6.QtWidgets import QWidget, QGraphicsOpacityEffect
|
|
from collections.abc import Callable
|
|
import portprotonqt.themes.standart.styles as default_styles
|
|
|
|
class GameCardAnimations:
|
|
def __init__(self, game_card, theme=None):
|
|
self.game_card = game_card
|
|
self.theme = theme if theme is not None else default_styles
|
|
self.thickness_anim: QPropertyAnimation | None = None
|
|
self.gradient_anim: QPropertyAnimation | None = None
|
|
self.pulse_anim: QPropertyAnimation | None = None
|
|
self._isPulseAnimationConnected = False
|
|
|
|
def setup_animations(self):
|
|
"""Initialize animation properties."""
|
|
self.thickness_anim = QPropertyAnimation(self.game_card, QByteArray(b"borderWidth"))
|
|
self.thickness_anim.setDuration(self.theme.GAME_CARD_ANIMATION["thickness_anim_duration"])
|
|
|
|
def start_pulse_animation(self):
|
|
"""Start pulse animation for border width when hovered or focused."""
|
|
if not (self.game_card._hovered or self.game_card._focused):
|
|
return
|
|
if self.pulse_anim:
|
|
self.pulse_anim.stop()
|
|
self.pulse_anim = QPropertyAnimation(self.game_card, QByteArray(b"borderWidth"))
|
|
self.pulse_anim.setDuration(self.theme.GAME_CARD_ANIMATION["pulse_anim_duration"])
|
|
self.pulse_anim.setLoopCount(0)
|
|
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 handle_enter_event(self):
|
|
"""Handle mouse enter event animations."""
|
|
self.game_card._hovered = True
|
|
self.game_card.hoverChanged.emit(self.game_card.name, True)
|
|
self.game_card.setFocus(Qt.FocusReason.MouseFocusReason)
|
|
|
|
# Ensure thickness_anim is initialized
|
|
if not self.thickness_anim:
|
|
self.setup_animations()
|
|
|
|
if self.thickness_anim:
|
|
self.thickness_anim.stop()
|
|
if self._isPulseAnimationConnected:
|
|
self.thickness_anim.finished.disconnect(self.start_pulse_animation)
|
|
self._isPulseAnimationConnected = False
|
|
self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION["thickness_easing_curve"]]))
|
|
self.thickness_anim.setStartValue(self.game_card._borderWidth)
|
|
self.thickness_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["hover_border_width"])
|
|
self.thickness_anim.finished.connect(self.start_pulse_animation)
|
|
self._isPulseAnimationConnected = True
|
|
self.thickness_anim.start()
|
|
|
|
if self.gradient_anim:
|
|
self.gradient_anim.stop()
|
|
self.gradient_anim = QPropertyAnimation(self.game_card, QByteArray(b"gradientAngle"))
|
|
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()
|
|
|
|
def handle_leave_event(self):
|
|
"""Handle mouse leave event animations."""
|
|
self.game_card._hovered = False
|
|
self.game_card.hoverChanged.emit(self.game_card.name, False)
|
|
if not self.game_card._focused:
|
|
if self.gradient_anim:
|
|
self.gradient_anim.stop()
|
|
self.gradient_anim = None
|
|
if self.pulse_anim:
|
|
self.pulse_anim.stop()
|
|
self.pulse_anim = None
|
|
if self.thickness_anim:
|
|
self.thickness_anim.stop()
|
|
if self._isPulseAnimationConnected:
|
|
self.thickness_anim.finished.disconnect(self.start_pulse_animation)
|
|
self._isPulseAnimationConnected = False
|
|
self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION["thickness_easing_curve_out"]]))
|
|
self.thickness_anim.setStartValue(self.game_card._borderWidth)
|
|
self.thickness_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["default_border_width"])
|
|
self.thickness_anim.start()
|
|
|
|
def handle_focus_in_event(self):
|
|
"""Handle focus in event animations."""
|
|
if not self.game_card._hovered:
|
|
self.game_card._focused = True
|
|
self.game_card.focusChanged.emit(self.game_card.name, True)
|
|
|
|
# Ensure thickness_anim is initialized
|
|
if not self.thickness_anim:
|
|
self.setup_animations()
|
|
|
|
if self.thickness_anim:
|
|
self.thickness_anim.stop()
|
|
if self._isPulseAnimationConnected:
|
|
self.thickness_anim.finished.disconnect(self.start_pulse_animation)
|
|
self._isPulseAnimationConnected = False
|
|
self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION["thickness_easing_curve"]]))
|
|
self.thickness_anim.setStartValue(self.game_card._borderWidth)
|
|
self.thickness_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["focus_border_width"])
|
|
self.thickness_anim.finished.connect(self.start_pulse_animation)
|
|
self._isPulseAnimationConnected = True
|
|
self.thickness_anim.start()
|
|
|
|
if self.gradient_anim:
|
|
self.gradient_anim.stop()
|
|
self.gradient_anim = QPropertyAnimation(self.game_card, QByteArray(b"gradientAngle"))
|
|
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()
|
|
|
|
def handle_focus_out_event(self):
|
|
"""Handle focus out event animations."""
|
|
self.game_card._focused = False
|
|
self.game_card.focusChanged.emit(self.game_card.name, False)
|
|
if not self.game_card._hovered:
|
|
if self.gradient_anim:
|
|
self.gradient_anim.stop()
|
|
self.gradient_anim = None
|
|
if self.pulse_anim:
|
|
self.pulse_anim.stop()
|
|
self.pulse_anim = None
|
|
if self.thickness_anim:
|
|
self.thickness_anim.stop()
|
|
if self._isPulseAnimationConnected:
|
|
self.thickness_anim.finished.disconnect(self.start_pulse_animation)
|
|
self._isPulseAnimationConnected = False
|
|
self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION["thickness_easing_curve_out"]]))
|
|
self.thickness_anim.setStartValue(self.game_card._borderWidth)
|
|
self.thickness_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["default_border_width"])
|
|
self.thickness_anim.start()
|
|
|
|
def paint_border(self, painter: QPainter):
|
|
"""Paint the animated border for the GameCard."""
|
|
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
pen = QPen()
|
|
pen.setWidth(self.game_card._borderWidth)
|
|
if self.game_card._hovered or self.game_card._focused:
|
|
center = self.game_card.rect().center()
|
|
gradient = QConicalGradient(center, self.game_card._gradientAngle)
|
|
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))
|
|
painter.setPen(pen)
|
|
radius = 18
|
|
bw = round(self.game_card._borderWidth / 2)
|
|
rect = self.game_card.rect().adjusted(bw, bw, -bw, -bw)
|
|
painter.drawRoundedRect(rect, radius, radius)
|
|
|
|
class DetailPageAnimations:
|
|
def __init__(self, main_window, theme=None):
|
|
self.main_window = main_window
|
|
self.theme = theme if theme is not None else default_styles
|
|
self.animations = main_window._animations if hasattr(main_window, '_animations') else {}
|
|
|
|
def animate_detail_page(self, detail_page: QWidget, load_image_and_restore_effect: Callable, cleanup_animation: Callable):
|
|
"""Animate the detail page based on theme settings."""
|
|
shadow = detail_page.graphicsEffect()
|
|
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(detail_page)
|
|
detail_page.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[detail_page] = animation
|
|
animation.finished.connect(lambda: detail_page.setGraphicsEffect(shadow) if shadow is not None else detail_page.setGraphicsEffect(None)) # type: ignore
|
|
animation.finished.connect(load_image_and_restore_effect)
|
|
elif animation_type == "slide_left":
|
|
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_slide_duration", 500)
|
|
detail_page.move(self.main_window.width(), 0)
|
|
animation = QPropertyAnimation(detail_page, QByteArray(b"pos"))
|
|
animation.setDuration(duration)
|
|
animation.setStartValue(detail_page.pos())
|
|
animation.setEndValue(self.main_window.stackedWidget.rect().topLeft())
|
|
animation.setEasingCurve(QEasingCurve.Type.OutCubic)
|
|
animation.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
|
|
self.animations[detail_page] = animation
|
|
animation.finished.connect(cleanup_animation)
|
|
animation.finished.connect(load_image_and_restore_effect)
|
|
elif animation_type == "slide_right":
|
|
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_slide_duration", 500)
|
|
detail_page.move(-self.main_window.width(), 0)
|
|
animation = QPropertyAnimation(detail_page, QByteArray(b"pos"))
|
|
animation.setDuration(duration)
|
|
animation.setStartValue(detail_page.pos())
|
|
animation.setEndValue(self.main_window.stackedWidget.rect().topLeft())
|
|
animation.setEasingCurve(QEasingCurve.Type.OutCubic)
|
|
animation.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
|
|
self.animations[detail_page] = animation
|
|
animation.finished.connect(cleanup_animation)
|
|
animation.finished.connect(load_image_and_restore_effect)
|
|
elif animation_type == "slide_up":
|
|
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_slide_duration", 500)
|
|
detail_page.move(0, self.main_window.height())
|
|
animation = QPropertyAnimation(detail_page, QByteArray(b"pos"))
|
|
animation.setDuration(duration)
|
|
animation.setStartValue(detail_page.pos())
|
|
animation.setEndValue(self.main_window.stackedWidget.rect().topLeft())
|
|
animation.setEasingCurve(QEasingCurve.Type.OutCubic)
|
|
animation.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
|
|
self.animations[detail_page] = animation
|
|
animation.finished.connect(cleanup_animation)
|
|
animation.finished.connect(load_image_and_restore_effect)
|
|
elif animation_type == "slide_down":
|
|
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_slide_duration", 500)
|
|
detail_page.move(0, -self.main_window.height())
|
|
animation = QPropertyAnimation(detail_page, QByteArray(b"pos"))
|
|
animation.setDuration(duration)
|
|
animation.setStartValue(detail_page.pos())
|
|
animation.setEndValue(self.main_window.stackedWidget.rect().topLeft())
|
|
animation.setEasingCurve(QEasingCurve.Type.OutCubic)
|
|
animation.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
|
|
self.animations[detail_page] = animation
|
|
animation.finished.connect(cleanup_animation)
|
|
animation.finished.connect(load_image_and_restore_effect)
|
|
elif animation_type == "bounce":
|
|
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_zoom_duration", 400)
|
|
detail_page.setWindowOpacity(0.0)
|
|
opacity_anim = QPropertyAnimation(detail_page, QByteArray(b"windowOpacity"))
|
|
opacity_anim.setDuration(duration)
|
|
opacity_anim.setStartValue(0.0)
|
|
opacity_anim.setEndValue(1.0)
|
|
initial_rect = QRect(detail_page.x() + detail_page.width() // 4, detail_page.y() + detail_page.height() // 4,
|
|
detail_page.width() // 2, detail_page.height() // 2)
|
|
final_rect = detail_page.geometry()
|
|
geometry_anim = QPropertyAnimation(detail_page, 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)
|
|
group_anim.finished.connect(load_image_and_restore_effect)
|
|
group_anim.finished.connect(cleanup_animation)
|
|
group_anim.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
|
|
self.animations[detail_page] = group_anim
|