optimization of icon animation in the _start_icon_fade_animation method

This commit is contained in:
Sergey Palcheh
2025-08-11 17:40:04 +06:00
parent 159fc26eca
commit a27832329d

View File

@ -13,7 +13,7 @@ from functools import partial
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,QPushButton, QLabel, QTabWidget,
QTextEdit, QFileDialog, QMessageBox, QLineEdit, QCheckBox, QStackedWidget, QScrollArea,
QListWidget, QListWidgetItem, QGridLayout, QFrame, QDialog, QTextBrowser)
from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment
from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment, QPropertyAnimation, QEasingCurve
from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter
@ -908,68 +908,79 @@ class WineHelperGUI(QMainWindow):
print(f"Ошибка чтения файла для извлечения PROG_URL: {str(e)}")
return None
def _ease_in_out_quad(self, t):
"""Простая квадратичная функция сглаживания (ease-in-out)."""
# t - значение от 0.0 до 1.0
if t < 0.5:
return 2 * t * t
return 1 - pow(-2 * t + 2, 2) / 2
def _start_icon_fade_animation(self, button):
"""Запускает анимацию плавного перехода для иконки на кнопке."""
"""Запускает анимацию плавного перехода для иконки на кнопке с помощью QPropertyAnimation."""
if button not in self.icon_animators:
return
anim_data = self.icon_animators[button]
# Получаем или создаем объект анимации один раз
animation = anim_data.get('animation')
if not animation:
# Устанавливаем динамическое свойство, чтобы избежать предупреждений
button.setProperty("fadeProgress", 0.0)
animation = QPropertyAnimation(button, b"fadeProgress", self)
animation.setDuration(700)
animation.setEasingCurve(QEasingCurve.InOutQuad)
# Сигналы подключаются только один раз при создании
animation.valueChanged.connect(
lambda value, b=button: self._update_icon_frame(b, value)
)
animation.finished.connect(
lambda b=button: self._on_fade_animation_finished(b)
)
anim_data['animation'] = animation
# Останавливаем предыдущую анимацию, если она еще идет
if anim_data.get('fade_timer') and anim_data['fade_timer'].isActive():
anim_data['fade_timer'].stop()
if animation.state() == QPropertyAnimation.Running:
animation.stop()
# Определяем текущую и следующую иконки
current_icon_path = anim_data['icons'][anim_data['current_index']]
next_icon_index = (anim_data['current_index'] + 1) % len(anim_data['icons'])
next_icon_path = anim_data['icons'][next_icon_index]
current_pixmap = QPixmap(current_icon_path)
next_pixmap = QPixmap(next_icon_path)
# Сохраняем QPixmap для использования в функции обновления кадра
anim_data['pixmaps'] = (QPixmap(current_icon_path), QPixmap(next_icon_path))
# Запускаем таймер для покадровой анимации
anim_data['fade_step'] = 0
fade_timer = QTimer(button)
anim_data['fade_timer'] = fade_timer
fade_timer.timeout.connect(
lambda b=button, p1=current_pixmap, p2=next_pixmap: self._update_fade_frame(b, p1, p2)
)
fade_timer.start(16) # ~60 кадров в секунду
# Устанавливаем начальное и конечное значения и запускаем
animation.setStartValue(0.0)
animation.setEndValue(1.0)
animation.start() # Без DeleteWhenStopped
def _update_fade_frame(self, button, old_pixmap, new_pixmap):
"""Обновляет кадр анимации перехода иконок."""
if button not in self.icon_animators:
def _on_fade_animation_finished(self, button):
"""Вызывается по завершении анимации для обновления индекса иконки."""
if button in self.icon_animators:
anim_data = self.icon_animators[button]
anim_data['current_index'] = (anim_data['current_index'] + 1) % len(anim_data['icons'])
def _update_icon_frame(self, button, progress):
"""Обновляет кадр анимации, смешивая две иконки в зависимости от прогресса."""
anim_data = self.icon_animators.get(button)
if not anim_data or 'pixmaps' not in anim_data:
return
anim_data = self.icon_animators[button]
# Длительность анимации: 44 шага * 16 мс ~= 700 мс
FADE_DURATION_STEPS = 44
old_pixmap, new_pixmap = anim_data['pixmaps']
anim_data['fade_step'] += 1
progress_linear = anim_data['fade_step'] / FADE_DURATION_STEPS
# Применяем функцию сглаживания для более плавного старта и завершения
progress = self._ease_in_out_quad(progress_linear)
if progress_linear >= 1.0:
anim_data['fade_timer'].stop()
anim_data['current_index'] = (anim_data['current_index'] + 1) % len(anim_data['icons'])
# На последнем кадре просто устанавливаем новую иконку
if progress >= 1.0:
button.setIcon(QIcon(new_pixmap))
return
# Создаем холст для отрисовки смешанной иконки
canvas = QPixmap(button.iconSize())
canvas.fill(Qt.transparent)
painter = QPainter(canvas)
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
# Плавно скрываем старую иконку
painter.setOpacity(1.0 - progress)
painter.drawPixmap(canvas.rect(), old_pixmap)
# Плавно проявляем новую иконку
painter.setOpacity(progress)
painter.drawPixmap(canvas.rect(), new_pixmap)
painter.end()