feat: added animation to goBackDetailPage
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
@@ -1,8 +1,11 @@
|
|||||||
from PySide6.QtCore import QPropertyAnimation, QByteArray, QEasingCurve, QAbstractAnimation, QParallelAnimationGroup, QRect, Qt
|
from PySide6.QtCore import QPropertyAnimation, QByteArray, QEasingCurve, QAbstractAnimation, QParallelAnimationGroup, QRect, Qt, QPoint
|
||||||
from PySide6.QtGui import QPainter, QPen, QColor, QConicalGradient, QBrush
|
from PySide6.QtGui import QPainter, QPen, QColor, QConicalGradient, QBrush
|
||||||
from PySide6.QtWidgets import QWidget, QGraphicsOpacityEffect
|
from PySide6.QtWidgets import QWidget, QGraphicsOpacityEffect
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
import portprotonqt.themes.standart.styles as default_styles
|
import portprotonqt.themes.standart.styles as default_styles
|
||||||
|
from portprotonqt.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
class GameCardAnimations:
|
class GameCardAnimations:
|
||||||
def __init__(self, game_card, theme=None):
|
def __init__(self, game_card, theme=None):
|
||||||
@@ -38,7 +41,6 @@ class GameCardAnimations:
|
|||||||
self.game_card.hoverChanged.emit(self.game_card.name, True)
|
self.game_card.hoverChanged.emit(self.game_card.name, True)
|
||||||
self.game_card.setFocus(Qt.FocusReason.MouseFocusReason)
|
self.game_card.setFocus(Qt.FocusReason.MouseFocusReason)
|
||||||
|
|
||||||
# Ensure thickness_anim is initialized
|
|
||||||
if not self.thickness_anim:
|
if not self.thickness_anim:
|
||||||
self.setup_animations()
|
self.setup_animations()
|
||||||
|
|
||||||
@@ -90,7 +92,6 @@ class GameCardAnimations:
|
|||||||
self.game_card._focused = True
|
self.game_card._focused = True
|
||||||
self.game_card.focusChanged.emit(self.game_card.name, True)
|
self.game_card.focusChanged.emit(self.game_card.name, True)
|
||||||
|
|
||||||
# Ensure thickness_anim is initialized
|
|
||||||
if not self.thickness_anim:
|
if not self.thickness_anim:
|
||||||
self.setup_animations()
|
self.setup_animations()
|
||||||
|
|
||||||
@@ -178,56 +179,28 @@ class DetailPageAnimations:
|
|||||||
self.animations[detail_page] = animation
|
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(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)
|
animation.finished.connect(load_image_and_restore_effect)
|
||||||
elif animation_type == "slide_left":
|
elif animation_type in ["slide_left", "slide_right", "slide_up", "slide_down"]:
|
||||||
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_slide_duration", 500)
|
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_slide_duration", 500)
|
||||||
detail_page.move(self.main_window.width(), 0)
|
easing_curve = QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION.get("detail_page_easing_curve", "OutCubic")])
|
||||||
|
start_pos = {
|
||||||
|
"slide_left": QPoint(self.main_window.width(), 0),
|
||||||
|
"slide_right": QPoint(-self.main_window.width(), 0),
|
||||||
|
"slide_up": QPoint(0, self.main_window.height()),
|
||||||
|
"slide_down": QPoint(0, -self.main_window.height())
|
||||||
|
}[animation_type]
|
||||||
|
detail_page.move(start_pos)
|
||||||
animation = QPropertyAnimation(detail_page, QByteArray(b"pos"))
|
animation = QPropertyAnimation(detail_page, QByteArray(b"pos"))
|
||||||
animation.setDuration(duration)
|
animation.setDuration(duration)
|
||||||
animation.setStartValue(detail_page.pos())
|
animation.setStartValue(start_pos)
|
||||||
animation.setEndValue(self.main_window.stackedWidget.rect().topLeft())
|
animation.setEndValue(self.main_window.stackedWidget.rect().topLeft())
|
||||||
animation.setEasingCurve(QEasingCurve.Type.OutCubic)
|
animation.setEasingCurve(easing_curve)
|
||||||
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)
|
animation.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
|
||||||
self.animations[detail_page] = animation
|
self.animations[detail_page] = animation
|
||||||
animation.finished.connect(cleanup_animation)
|
animation.finished.connect(cleanup_animation)
|
||||||
animation.finished.connect(load_image_and_restore_effect)
|
animation.finished.connect(load_image_and_restore_effect)
|
||||||
elif animation_type == "bounce":
|
elif animation_type == "bounce":
|
||||||
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_zoom_duration", 400)
|
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_bounce_duration", 400)
|
||||||
|
easing_curve = QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION.get("detail_page_easing_curve", "OutCubic")])
|
||||||
detail_page.setWindowOpacity(0.0)
|
detail_page.setWindowOpacity(0.0)
|
||||||
opacity_anim = QPropertyAnimation(detail_page, QByteArray(b"windowOpacity"))
|
opacity_anim = QPropertyAnimation(detail_page, QByteArray(b"windowOpacity"))
|
||||||
opacity_anim.setDuration(duration)
|
opacity_anim.setDuration(duration)
|
||||||
@@ -240,7 +213,7 @@ class DetailPageAnimations:
|
|||||||
geometry_anim.setDuration(duration)
|
geometry_anim.setDuration(duration)
|
||||||
geometry_anim.setStartValue(initial_rect)
|
geometry_anim.setStartValue(initial_rect)
|
||||||
geometry_anim.setEndValue(final_rect)
|
geometry_anim.setEndValue(final_rect)
|
||||||
geometry_anim.setEasingCurve(QEasingCurve.Type.OutBack)
|
geometry_anim.setEasingCurve(easing_curve)
|
||||||
group_anim = QParallelAnimationGroup()
|
group_anim = QParallelAnimationGroup()
|
||||||
group_anim.addAnimation(opacity_anim)
|
group_anim.addAnimation(opacity_anim)
|
||||||
group_anim.addAnimation(geometry_anim)
|
group_anim.addAnimation(geometry_anim)
|
||||||
@@ -248,3 +221,75 @@ class DetailPageAnimations:
|
|||||||
group_anim.finished.connect(cleanup_animation)
|
group_anim.finished.connect(cleanup_animation)
|
||||||
group_anim.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
|
group_anim.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
|
||||||
self.animations[detail_page] = group_anim
|
self.animations[detail_page] = group_anim
|
||||||
|
|
||||||
|
def animate_detail_page_exit(self, detail_page: QWidget, cleanup_callback: Callable):
|
||||||
|
"""Animate the detail page exit based on theme settings."""
|
||||||
|
try:
|
||||||
|
animation_type = self.theme.GAME_CARD_ANIMATION.get("detail_page_animation_type", "fade")
|
||||||
|
|
||||||
|
# Safely stop and remove any existing animation
|
||||||
|
if detail_page in self.animations:
|
||||||
|
try:
|
||||||
|
animation = self.animations[detail_page]
|
||||||
|
if isinstance(animation, QAbstractAnimation) and animation.state() == QAbstractAnimation.State.Running:
|
||||||
|
animation.stop()
|
||||||
|
except RuntimeError:
|
||||||
|
logger.debug("Animation already deleted for page")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stopping existing animation: {e}", exc_info=True)
|
||||||
|
finally:
|
||||||
|
self.animations.pop(detail_page, None)
|
||||||
|
|
||||||
|
# Define animation based on type
|
||||||
|
if animation_type == "fade":
|
||||||
|
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_fade_duration_exit", 350)
|
||||||
|
opacity_effect = QGraphicsOpacityEffect(detail_page)
|
||||||
|
detail_page.setGraphicsEffect(opacity_effect)
|
||||||
|
animation = QPropertyAnimation(opacity_effect, QByteArray(b"opacity"))
|
||||||
|
animation.setDuration(duration)
|
||||||
|
animation.setStartValue(1)
|
||||||
|
animation.setEndValue(0)
|
||||||
|
animation.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
|
||||||
|
self.animations[detail_page] = animation
|
||||||
|
animation.finished.connect(cleanup_callback)
|
||||||
|
elif animation_type in ["slide_left", "slide_right", "slide_up", "slide_down"]:
|
||||||
|
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_slide_duration_exit", 500)
|
||||||
|
easing_curve = QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION.get("detail_page_easing_curve_exit", "InCubic")])
|
||||||
|
end_pos = {
|
||||||
|
"slide_left": QPoint(-self.main_window.width(), 0), # Exit to left (opposite of entry)
|
||||||
|
"slide_right": QPoint(self.main_window.width(), 0), # Exit to right
|
||||||
|
"slide_up": QPoint(0, self.main_window.height()), # Exit downward
|
||||||
|
"slide_down": QPoint(0, -self.main_window.height()) # Exit upward
|
||||||
|
}[animation_type]
|
||||||
|
animation = QPropertyAnimation(detail_page, QByteArray(b"pos"))
|
||||||
|
animation.setDuration(duration)
|
||||||
|
animation.setStartValue(detail_page.pos())
|
||||||
|
animation.setEndValue(end_pos)
|
||||||
|
animation.setEasingCurve(easing_curve)
|
||||||
|
animation.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
|
||||||
|
self.animations[detail_page] = animation
|
||||||
|
animation.finished.connect(cleanup_callback)
|
||||||
|
elif animation_type == "bounce":
|
||||||
|
duration = self.theme.GAME_CARD_ANIMATION.get("detail_page_bounce_duration_exit", 400)
|
||||||
|
easing_curve = QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION.get("detail_page_easing_curve_exit", "InCubic")])
|
||||||
|
opacity_anim = QPropertyAnimation(detail_page, QByteArray(b"windowOpacity"))
|
||||||
|
opacity_anim.setDuration(duration)
|
||||||
|
opacity_anim.setStartValue(1.0)
|
||||||
|
opacity_anim.setEndValue(0.0)
|
||||||
|
final_rect = QRect(detail_page.x() + detail_page.width() // 4, detail_page.y() + detail_page.height() // 4,
|
||||||
|
detail_page.width() // 2, detail_page.height() // 2)
|
||||||
|
geometry_anim = QPropertyAnimation(detail_page, QByteArray(b"geometry"))
|
||||||
|
geometry_anim.setDuration(duration)
|
||||||
|
geometry_anim.setStartValue(detail_page.geometry())
|
||||||
|
geometry_anim.setEndValue(final_rect)
|
||||||
|
geometry_anim.setEasingCurve(easing_curve)
|
||||||
|
group_anim = QParallelAnimationGroup()
|
||||||
|
group_anim.addAnimation(opacity_anim)
|
||||||
|
group_anim.addAnimation(geometry_anim)
|
||||||
|
group_anim.finished.connect(cleanup_callback)
|
||||||
|
group_anim.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
|
||||||
|
self.animations[detail_page] = group_anim
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in animate_detail_page_exit: {e}", exc_info=True)
|
||||||
|
self.animations.pop(detail_page, None)
|
||||||
|
cleanup_callback() # Fallback to cleanup if animation setup fails
|
||||||
|
@@ -1967,24 +1967,42 @@ class MainWindow(QMainWindow):
|
|||||||
parent = parent.parent()
|
parent = parent.parent()
|
||||||
|
|
||||||
def goBackDetailPage(self, page: QWidget | None) -> None:
|
def goBackDetailPage(self, page: QWidget | None) -> None:
|
||||||
if page is None or page != self.stackedWidget.currentWidget():
|
if page is None or page != self.stackedWidget.currentWidget() or getattr(self, '_exit_animation_in_progress', False):
|
||||||
return
|
return
|
||||||
|
self._exit_animation_in_progress = True
|
||||||
self._detail_page_active = False
|
self._detail_page_active = False
|
||||||
self._current_detail_page = None
|
self._current_detail_page = None
|
||||||
if hasattr(self, '_animations') and page in self._animations:
|
|
||||||
|
def cleanup():
|
||||||
|
"""Helper function to clean up after animation."""
|
||||||
try:
|
try:
|
||||||
animation = self._animations[page]
|
if page in self._animations:
|
||||||
if animation.state() == QAbstractAnimation.State.Running:
|
animation = self._animations[page]
|
||||||
animation.stop()
|
try:
|
||||||
del self._animations[page]
|
if animation.state() == QAbstractAnimation.State.Running:
|
||||||
except RuntimeError:
|
animation.stop()
|
||||||
del self._animations[page]
|
except RuntimeError:
|
||||||
self.stackedWidget.setCurrentIndex(0)
|
pass # Animation already deleted
|
||||||
self.stackedWidget.removeWidget(page)
|
finally:
|
||||||
page.deleteLater()
|
del self._animations[page]
|
||||||
self.currentDetailPage = None
|
self.stackedWidget.setCurrentIndex(0)
|
||||||
self.current_exec_line = None
|
self.stackedWidget.removeWidget(page)
|
||||||
self.current_play_button = None
|
page.deleteLater()
|
||||||
|
self.currentDetailPage = None
|
||||||
|
self.current_exec_line = None
|
||||||
|
self.current_play_button = None
|
||||||
|
self._exit_animation_in_progress = False
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in cleanup: {e}", exc_info=True)
|
||||||
|
self._exit_animation_in_progress = False
|
||||||
|
|
||||||
|
# Start exit animation
|
||||||
|
try:
|
||||||
|
self.detail_animations.animate_detail_page_exit(page, cleanup)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error starting exit animation: {e}", exc_info=True)
|
||||||
|
self._exit_animation_in_progress = False
|
||||||
|
cleanup() # Fallback to cleanup if animation fails
|
||||||
|
|
||||||
def is_target_exe_running(self):
|
def is_target_exe_running(self):
|
||||||
"""Проверяет, запущен ли процесс с именем self.target_exe через psutil."""
|
"""Проверяет, запущен ли процесс с именем self.target_exe через psutil."""
|
||||||
|
@@ -27,7 +27,7 @@ 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"
|
# Возможные значения: "fade", "slide_left", "slide_right", "slide_up", "slide_down", "bounce"
|
||||||
"detail_page_animation_type": "fade",
|
"detail_page_animation_type": "fade",
|
||||||
|
|
||||||
@@ -105,8 +105,25 @@ GAME_CARD_ANIMATION = {
|
|||||||
# Длительность анимации slide при входе на детальную страницу
|
# Длительность анимации slide при входе на детальную страницу
|
||||||
"detail_page_slide_duration": 500,
|
"detail_page_slide_duration": 500,
|
||||||
|
|
||||||
# Длительность анимации zoom при входе на детальную страницу
|
# Длительность анимации bounce при входе на детальную страницу
|
||||||
"detail_page_zoom_duration": 400
|
"detail_page_bounce_duration": 400,
|
||||||
|
|
||||||
|
# Длительность анимации fade при выходе из детальной страницы
|
||||||
|
"detail_page_fade_duration_exit": 350,
|
||||||
|
|
||||||
|
# Длительность анимации slide при выходе из детальной страницы
|
||||||
|
"detail_page_slide_duration_exit": 500,
|
||||||
|
|
||||||
|
# Длительность анимации bounce при выходе из детальной страницы
|
||||||
|
"detail_page_bounce_duration_exit": 400,
|
||||||
|
|
||||||
|
# Тип кривой сглаживания для анимации при входе на детальную страницу
|
||||||
|
# Применяется к slide и bounce анимациям
|
||||||
|
"detail_page_easing_curve": "OutCubic",
|
||||||
|
|
||||||
|
# Тип кривой сглаживания для анимации при выходе из детальной страницы
|
||||||
|
# Применяется к slide и bounce анимациям
|
||||||
|
"detail_page_easing_curve_exit": "InCubic"
|
||||||
}
|
}
|
||||||
|
|
||||||
CONTEXT_MENU_STYLE = f"""
|
CONTEXT_MENU_STYLE = f"""
|
||||||
|
Reference in New Issue
Block a user