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